mirror of
https://github.com/balkian/hookio-sparql-demo.git
synced 2024-11-21 19:02:28 +00:00
Added chatlike app
This commit is contained in:
parent
691b417c55
commit
aeb26b791a
@ -1,9 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "Web4.0",
|
"name": "Hookio-sparql-example",
|
||||||
"version" : "0.0.1",
|
"version" : "0.0.1",
|
||||||
"dependencies":{
|
"dependencies":{
|
||||||
"socket.io":"*",
|
"socket.io":"*",
|
||||||
"hook.io":"*",
|
"hook.io":"*",
|
||||||
"Hook.io-mailer":"git+https://github.com/balkian/Hookio-Mailer.git"
|
"Hook.io-mailer":"git+https://github.com/balkian/Hookio-Mailer.git"
|
||||||
|
"express": "2.5.5",
|
||||||
|
"jade": "0.16.4",
|
||||||
|
"stylus": "0.19.0",
|
||||||
|
"nib": "0.2.0"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
81
web/app.js
Normal file
81
web/app.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var express = require('express')
|
||||||
|
, stylus = require('stylus')
|
||||||
|
, nib = require('nib')
|
||||||
|
, sio = require('../../lib/socket.io');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var app = express.createServer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
app.configure(function () {
|
||||||
|
app.use(stylus.middleware({ src: __dirname + '/public', compile: compile }));
|
||||||
|
app.use(express.static(__dirname + '/public'));
|
||||||
|
app.set('views', __dirname);
|
||||||
|
app.set('view engine', 'jade');
|
||||||
|
|
||||||
|
function compile (str, path) {
|
||||||
|
return stylus(str)
|
||||||
|
.set('filename', path)
|
||||||
|
.use(nib());
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App routes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
app.get('/', function (req, res) {
|
||||||
|
res.render('index', { layout: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App listen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
app.listen(3000, function () {
|
||||||
|
var addr = app.address();
|
||||||
|
console.log(' app listening on http://' + addr.address + ':' + addr.port);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket.IO server (single process only)
|
||||||
|
*/
|
||||||
|
|
||||||
|
var io = sio.listen(app)
|
||||||
|
, nicknames = {};
|
||||||
|
|
||||||
|
io.sockets.on('connection', function (socket) {
|
||||||
|
socket.on('user message', function (msg) {
|
||||||
|
console.log('User message:'+msg);
|
||||||
|
//socket.broadcast.emit('user message', socket.nickname, msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('nickname', function (nick, fn) {
|
||||||
|
if (nicknames[nick]) {
|
||||||
|
fn(true);
|
||||||
|
} else {
|
||||||
|
fn(false);
|
||||||
|
nicknames[nick] = socket.nickname = nick;
|
||||||
|
//socket.broadcast.emit('announcement', nick + ' connected');
|
||||||
|
//io.sockets.emit('nicknames', nicknames);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function () {
|
||||||
|
if (!socket.nickname) return;
|
||||||
|
|
||||||
|
delete nicknames[socket.nickname];
|
||||||
|
// socket.broadcast.emit('announcement', socket.nickname + ' disconnected');
|
||||||
|
// socket.broadcast.emit('nicknames', nicknames);
|
||||||
|
});
|
||||||
|
});
|
83
web/index.jade
Normal file
83
web/index.jade
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
doctype 5
|
||||||
|
html
|
||||||
|
head
|
||||||
|
link(href='/stylesheets/style.css', rel='stylesheet')
|
||||||
|
script(src='http://code.jquery.com/jquery-1.6.1.min.js')
|
||||||
|
script(src='/socket.io/socket.io.js')
|
||||||
|
script
|
||||||
|
// socket.io specific code
|
||||||
|
var socket = io.connect();
|
||||||
|
|
||||||
|
socket.on('connect', function () {
|
||||||
|
$('#chat').addClass('connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('announcement', function (msg) {
|
||||||
|
$('#lines').append($('<p>').append($('<em>').text(msg)));
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('nicknames', function (nicknames) {
|
||||||
|
$('#nicknames').empty().append($('<span>Online: </span>'));
|
||||||
|
for (var i in nicknames) {
|
||||||
|
$('#nicknames').append($('<b>').text(nicknames[i]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user message', message);
|
||||||
|
socket.on('reconnect', function () {
|
||||||
|
$('#lines').remove();
|
||||||
|
message('System', 'Reconnected to the server');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('reconnecting', function () {
|
||||||
|
message('System', 'Attempting to re-connect to the server');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', function (e) {
|
||||||
|
message('System', e ? e : 'A unknown error occurred');
|
||||||
|
});
|
||||||
|
|
||||||
|
function message (from, msg) {
|
||||||
|
$('#lines').append($('<p>').append($('<b>').text(from), msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// dom manipulation
|
||||||
|
$(function () {
|
||||||
|
$('#set-nickname').submit(function (ev) {
|
||||||
|
socket.emit('nickname', $('#nick').val(), function (set) {
|
||||||
|
if (!set) {
|
||||||
|
clear();
|
||||||
|
return $('#chat').addClass('nickname-set');
|
||||||
|
}
|
||||||
|
$('#nickname-err').css('visibility', 'visible');
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#send-message').submit(function () {
|
||||||
|
message('me', $('#message').val());
|
||||||
|
socket.emit('user message', $('#message').val());
|
||||||
|
clear();
|
||||||
|
$('#lines').get(0).scrollTop = 10000000;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
function clear () {
|
||||||
|
$('#message').val('').focus();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
body
|
||||||
|
#chat
|
||||||
|
#nickname
|
||||||
|
form.wrap#set-nickname
|
||||||
|
p Please type in your nickname and press enter.
|
||||||
|
input#nick
|
||||||
|
p#nickname-err Nickname already in use
|
||||||
|
#connecting
|
||||||
|
.wrap Connecting to socket.io server
|
||||||
|
#messages
|
||||||
|
#nicknames
|
||||||
|
#lines
|
||||||
|
form#send-message
|
||||||
|
input#message
|
||||||
|
button Send
|
96
web/public/stylesheets/mixins.styl
Normal file
96
web/public/stylesheets/mixins.styl
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
border-radius(n)
|
||||||
|
-webkit-border-radius n
|
||||||
|
-moz-border-radius n
|
||||||
|
border-radius n
|
||||||
|
|
||||||
|
// replace str with val
|
||||||
|
|
||||||
|
replace(expr, str, val)
|
||||||
|
expr = clone(expr)
|
||||||
|
for e, i in expr
|
||||||
|
if str == e
|
||||||
|
expr[i] = val
|
||||||
|
expr
|
||||||
|
|
||||||
|
// normalize gradient point (webkit)
|
||||||
|
|
||||||
|
grad-point(pos)
|
||||||
|
if length(pos) == 1
|
||||||
|
return left pos if pos in (top bottom)
|
||||||
|
return pos top if pos in (left right)
|
||||||
|
else if pos[0] in (top bottom)
|
||||||
|
pos[1] pos[0]
|
||||||
|
else
|
||||||
|
pos
|
||||||
|
|
||||||
|
// implicit color stop position
|
||||||
|
|
||||||
|
pos-in-stops(i, stops)
|
||||||
|
len = length(stops)
|
||||||
|
if len - 1 == i
|
||||||
|
100%
|
||||||
|
else if i
|
||||||
|
unit(i / len * 100, '%')
|
||||||
|
else
|
||||||
|
0%
|
||||||
|
|
||||||
|
// normalize color stops
|
||||||
|
// - (color pos) -> (pos color)
|
||||||
|
// - (color) -> (implied-pos color)
|
||||||
|
|
||||||
|
normalize-stops(stops)
|
||||||
|
stops = clone(stops)
|
||||||
|
for stop, i in stops
|
||||||
|
if length(stop) == 1
|
||||||
|
color = stop[0]
|
||||||
|
stop[0] = pos-in-stops(i, stops)
|
||||||
|
stop[1] = color
|
||||||
|
else if typeof(stop[1]) == 'unit'
|
||||||
|
pos = stop[1]
|
||||||
|
stop[1] = stop[0]
|
||||||
|
stop[0] = pos
|
||||||
|
stops
|
||||||
|
|
||||||
|
// join color stops with the given translation function
|
||||||
|
|
||||||
|
join-stops(stops, translate)
|
||||||
|
str = ''
|
||||||
|
len = length(stops)
|
||||||
|
for stop, i in stops
|
||||||
|
str += ', ' if i
|
||||||
|
pos = stop[0]
|
||||||
|
color = stop[1]
|
||||||
|
str += translate(color, pos)
|
||||||
|
unquote(str)
|
||||||
|
|
||||||
|
// webkit translation function
|
||||||
|
|
||||||
|
webkit-stop(color, pos)
|
||||||
|
s('color-stop(%d, %s)', pos / 100, color)
|
||||||
|
|
||||||
|
// mozilla translation function
|
||||||
|
|
||||||
|
moz-stop(color, pos)
|
||||||
|
s('%s %s', color, pos)
|
||||||
|
|
||||||
|
// create a linear gradient with the given start
|
||||||
|
// position, followed by color stops
|
||||||
|
|
||||||
|
linear-gradient(start, stops...)
|
||||||
|
error('color stops required') unless length(stops)
|
||||||
|
prop = current-property[0]
|
||||||
|
val = current-property[1]
|
||||||
|
stops = normalize-stops(stops)
|
||||||
|
|
||||||
|
// webkit
|
||||||
|
end = grad-point(opposite-position(start))
|
||||||
|
webkit = s('-webkit-gradient(linear, %s, %s, %s)', grad-point(start), end, join-stops(stops, webkit-stop))
|
||||||
|
add-property(prop, replace(val, '__CALL__', webkit))
|
||||||
|
|
||||||
|
// moz
|
||||||
|
stops = join-stops(stops, moz-stop)
|
||||||
|
moz = s('-moz-linear-gradient(%s, %s)', start, stops)
|
||||||
|
add-property(prop, replace(val, '__CALL__', moz))
|
||||||
|
|
||||||
|
// literal
|
||||||
|
s('linear-gradient(%s, %s)', start, stops)
|
188
web/public/stylesheets/style.css
Normal file
188
web/public/stylesheets/style.css
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
#chat,
|
||||||
|
#nickname,
|
||||||
|
#messages {
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
#chat {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
#nickname,
|
||||||
|
#connecting {
|
||||||
|
position: absolute;
|
||||||
|
height: 410px;
|
||||||
|
z-index: 100;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: #fff;
|
||||||
|
text-align: center;
|
||||||
|
width: 600px;
|
||||||
|
font: 15px Georgia;
|
||||||
|
color: #666;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#nickname .wrap,
|
||||||
|
#connecting .wrap {
|
||||||
|
padding-top: 150px;
|
||||||
|
}
|
||||||
|
#nickname input {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#nickname input:focus {
|
||||||
|
border-color: #999;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
#nickname #nickname-err {
|
||||||
|
color: #8b0000;
|
||||||
|
font-size: 12px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.connected #connecting {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.nickname-set #nickname {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#messages {
|
||||||
|
height: 380px;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
#messages em {
|
||||||
|
text-shadow: 0 1px 0 #fff;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
#messages p {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font: 12px Helvetica, Arial;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
#messages p b {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
#messages p:nth-child(even) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
#messages #nicknames {
|
||||||
|
background: #ccc;
|
||||||
|
padding: 2px 4px 4px;
|
||||||
|
font: 11px Helvetica;
|
||||||
|
}
|
||||||
|
#messages #nicknames span {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
#messages #nicknames b {
|
||||||
|
display: inline-block;
|
||||||
|
color: #fff;
|
||||||
|
background: #999;
|
||||||
|
padding: 3px 6px;
|
||||||
|
margin-right: 5px;
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
|
-moz-border-radius: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-shadow: 0 1px 0 #666;
|
||||||
|
}
|
||||||
|
#messages #lines {
|
||||||
|
height: 355px;
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
#messages #lines::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
#messages #lines::-webkit-scrollbar-button:start:decrement,
|
||||||
|
#messages #lines ::-webkit-scrollbar-button:end:increment {
|
||||||
|
display: block;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
#messages #lines::-webkit-scrollbar-button:vertical:increment {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
#messages #lines::-webkit-scrollbar-track-piece {
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
}
|
||||||
|
#messages #lines::-webkit-scrollbar-thumb:vertical {
|
||||||
|
height: 50px;
|
||||||
|
background-color: #ccc;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
}
|
||||||
|
#messages #lines::-webkit-scrollbar-thumb:horizontal {
|
||||||
|
width: 50px;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
}
|
||||||
|
#send-message {
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#send-message input {
|
||||||
|
border: none;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
line-height: 30px;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 580px;
|
||||||
|
}
|
||||||
|
#send-message input:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
#send-message button {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
background: #43a1f7;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #377ad0));
|
||||||
|
background: -webkit-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||||
|
background: -moz-linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||||
|
background: linear-gradient(top, #43a1f7 0%, #377ad0 100%);
|
||||||
|
border: 1px solid #2e70c4;
|
||||||
|
-webkit-border-radius: 16px;
|
||||||
|
-moz-border-radius: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
color: #fff;
|
||||||
|
font-family: "lucida grande", sans-serif;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 3px 10px 5px 10px;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 -1px 1px #2d6dc0;
|
||||||
|
}
|
||||||
|
button:hover,
|
||||||
|
button.hover {
|
||||||
|
background: darker;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #43a1f7), color-stop(1, #2e70c4));
|
||||||
|
background: -webkit-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||||
|
background: -moz-linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||||
|
background: linear-gradient(top, #43a1f7 0%, #2e70c4 100%);
|
||||||
|
border: 1px solid #2e70c4;
|
||||||
|
cursor: pointer;
|
||||||
|
text-shadow: 0 -1px 1px #2c6bbb;
|
||||||
|
}
|
||||||
|
button:active,
|
||||||
|
button.active {
|
||||||
|
background: #2e70c4;
|
||||||
|
border: 1px solid #2e70c4;
|
||||||
|
border-bottom: 1px solid #2861aa;
|
||||||
|
text-shadow: 0 -1px 1px #2b67b5;
|
||||||
|
}
|
||||||
|
button:focus,
|
||||||
|
button.focus {
|
||||||
|
outline: none;
|
||||||
|
-webkit-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||||
|
-moz-box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||||
|
box-shadow: 0 1px 0 0 rgba(255,255,255,0.4), 0 0 4px 0 #377ad0;
|
||||||
|
}
|
118
web/public/stylesheets/style.styl
Normal file
118
web/public/stylesheets/style.styl
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
@import 'nib'
|
||||||
|
|
||||||
|
#chat, #nickname, #messages
|
||||||
|
width 600px
|
||||||
|
|
||||||
|
#chat
|
||||||
|
position relative
|
||||||
|
border 1px solid #ccc
|
||||||
|
|
||||||
|
#nickname, #connecting
|
||||||
|
position absolute
|
||||||
|
height 410px
|
||||||
|
z-index 100
|
||||||
|
left 0
|
||||||
|
top 0
|
||||||
|
background #fff
|
||||||
|
text-align center
|
||||||
|
width 600px
|
||||||
|
font 15px Georgia
|
||||||
|
color #666
|
||||||
|
display block
|
||||||
|
.wrap
|
||||||
|
padding-top 150px
|
||||||
|
|
||||||
|
#nickname
|
||||||
|
input
|
||||||
|
border 1px solid #ccc
|
||||||
|
padding 10px
|
||||||
|
&:focus
|
||||||
|
border-color #999
|
||||||
|
outline 0
|
||||||
|
#nickname-err
|
||||||
|
color darkred
|
||||||
|
font-size 12px
|
||||||
|
visibility hidden
|
||||||
|
|
||||||
|
.connected
|
||||||
|
#connecting
|
||||||
|
display none
|
||||||
|
|
||||||
|
.nickname-set
|
||||||
|
#nickname
|
||||||
|
display none
|
||||||
|
|
||||||
|
#messages
|
||||||
|
height 380px
|
||||||
|
background #eee
|
||||||
|
em
|
||||||
|
text-shadow 0 1px 0 #fff
|
||||||
|
color #999
|
||||||
|
p
|
||||||
|
padding 0
|
||||||
|
margin 0
|
||||||
|
font 12px Helvetica, Arial
|
||||||
|
padding 5px 10px
|
||||||
|
b
|
||||||
|
display inline-block
|
||||||
|
padding-right 10px
|
||||||
|
p:nth-child(even)
|
||||||
|
background #fafafa
|
||||||
|
#nicknames
|
||||||
|
background #ccc
|
||||||
|
padding 2px 4px 4px
|
||||||
|
font 11px Helvetica
|
||||||
|
span
|
||||||
|
color black
|
||||||
|
b
|
||||||
|
display inline-block
|
||||||
|
color #fff
|
||||||
|
background #999
|
||||||
|
padding 3px 6px
|
||||||
|
margin-right 5px
|
||||||
|
border-radius 10px
|
||||||
|
text-shadow 0 1px 0 #666
|
||||||
|
#lines
|
||||||
|
height 355px
|
||||||
|
overflow auto
|
||||||
|
overflow-x hidden
|
||||||
|
overflow-y auto
|
||||||
|
&::-webkit-scrollbar
|
||||||
|
width 6px
|
||||||
|
height 6px
|
||||||
|
&::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment
|
||||||
|
display block
|
||||||
|
height 10px
|
||||||
|
&::-webkit-scrollbar-button:vertical:increment
|
||||||
|
background-color #fff
|
||||||
|
&::-webkit-scrollbar-track-piece
|
||||||
|
background-color #fff
|
||||||
|
-webkit-border-radius 3px
|
||||||
|
&::-webkit-scrollbar-thumb:vertical
|
||||||
|
height 50px
|
||||||
|
background-color #ccc
|
||||||
|
-webkit-border-radius 3px
|
||||||
|
&::-webkit-scrollbar-thumb:horizontal
|
||||||
|
width 50px
|
||||||
|
background-color #fff
|
||||||
|
-webkit-border-radius 3px
|
||||||
|
|
||||||
|
#send-message
|
||||||
|
background #fff
|
||||||
|
position relative
|
||||||
|
input
|
||||||
|
border none
|
||||||
|
height 30px
|
||||||
|
padding 0 10px
|
||||||
|
line-height 30px
|
||||||
|
vertical-align middle
|
||||||
|
width 580px
|
||||||
|
&:focus
|
||||||
|
outline 0
|
||||||
|
button
|
||||||
|
position absolute
|
||||||
|
top 5px
|
||||||
|
right 5px
|
||||||
|
|
||||||
|
button
|
||||||
|
download-button()
|
Loading…
Reference in New Issue
Block a user