Media Tag integration, image, audio, video, pdf
This commit is contained in:
1 parent
92a7970eb8
commit
cb2efe138b
4 files changed
+264
-3
No files matched your search
@@ -0,0 +1,233 @@
|
||||
/*@flow*/
|
||||
/*
|
||||
globals module
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
// the address you want to bind to, :: means all ipv4 and ipv6 addresses
|
||||
// this may not work on all operating systems
|
||||
httpAddress: '::',
|
||||
|
||||
// the port on which your httpd will listen
|
||||
|
||||
/* Cryptpad can be configured to send customized HTTP Headers
|
||||
* These settings may vary widely depending on your needs
|
||||
* Examples are provided below
|
||||
*/
|
||||
|
||||
httpHeaders: {
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
// 'X-Frame-Options': 'SAMEORIGIN',
|
||||
},
|
||||
|
||||
contentSecurity: [
|
||||
"default-src 'none'",
|
||||
"style-src 'unsafe-inline' 'self'",
|
||||
"script-src 'self'",
|
||||
"font-src 'self'",
|
||||
|
||||
/* child-src is used to restrict iframes to a set of allowed domains.
|
||||
* connect-src is used to restrict what domains can connect to the websocket.
|
||||
*
|
||||
* it is recommended that you configure these fields to match the
|
||||
* domain which will serve your cryptpad instance.
|
||||
*/
|
||||
"child-src 'self' *",
|
||||
|
||||
/* this allows connections over secure or insecure websockets
|
||||
if you are deploying to production, you'll probably want to remove
|
||||
the ws://* directive, and change '*' to your domain
|
||||
*/
|
||||
"connect-src 'self' ws: wss: blob:",
|
||||
|
||||
// data: is used by codemirror
|
||||
"img-src 'self' data: blob:",
|
||||
"media-src 'self' data: blob:"
|
||||
].join('; '),
|
||||
|
||||
// CKEditor requires significantly more lax content security policy in order to function.
|
||||
padContentSecurity: [
|
||||
"default-src 'none'",
|
||||
"style-src 'unsafe-inline' 'self'",
|
||||
// Unsafe inline, unsafe-eval are needed for ckeditor :(
|
||||
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
|
||||
"font-src 'self'",
|
||||
|
||||
/* See above under 'contentSecurity' as to how these values should be
|
||||
* configured for best effect.
|
||||
*/
|
||||
"child-src 'self' *",
|
||||
|
||||
// see the comment above in the 'contentSecurity' section
|
||||
"connect-src 'self' ws: wss: blob:",
|
||||
|
||||
// (insecure remote) images are included by users of the wysiwyg who embed photos in their pads
|
||||
"img-src *",
|
||||
"media-src *"
|
||||
].join('; '),
|
||||
|
||||
httpPort: 3000,
|
||||
|
||||
/* your server's websocket url is configurable
|
||||
* (default: '/cryptpad_websocket')
|
||||
*
|
||||
* websocketPath can be relative, of the form '/path/to/websocket'
|
||||
* or absolute, specifying a particular URL
|
||||
*
|
||||
* 'wss://cryptpad.fr:3000/cryptpad_websocket'
|
||||
*/
|
||||
websocketPath: '/cryptpad_websocket',
|
||||
|
||||
/* it is assumed that your websocket will bind to the same port as http
|
||||
* you can override this behaviour by supplying a number via websocketPort
|
||||
*/
|
||||
//websocketPort: 3000,
|
||||
|
||||
/* if you want to run a different version of cryptpad but using the same websocket
|
||||
* server, you should use the other server port as websocketPort and disable
|
||||
* the websockets on that server
|
||||
*/
|
||||
//useExternalWebsocket: false,
|
||||
|
||||
/* If Cryptpad is proxied without using https, the server needs to know.
|
||||
* Specify 'useSecureWebsockets: true' so that it can send
|
||||
* Content Security Policy Headers that prevent http and https from mixing
|
||||
*/
|
||||
useSecureWebsockets: false,
|
||||
|
||||
/* Cryptpad can log activity to stdout
|
||||
* This may be useful for debugging
|
||||
*/
|
||||
logToStdout: false,
|
||||
|
||||
/* Cryptpad supports verbose logging
|
||||
* (false by default)
|
||||
*/
|
||||
verbose: false,
|
||||
|
||||
/* Main pages
|
||||
* add exceptions to the router so that we can access /privacy.html
|
||||
* and other odd pages
|
||||
*/
|
||||
mainPages: [
|
||||
'index',
|
||||
'privacy',
|
||||
'terms',
|
||||
'about',
|
||||
'contact',
|
||||
],
|
||||
|
||||
/* Domain
|
||||
* If you want to have enable payments on your CryptPad instance, it has to be able to tell
|
||||
* our account server what is your domain
|
||||
*/
|
||||
// domain: 'https://cryptpad.fr',
|
||||
|
||||
/*
|
||||
You have the option of specifying an alternative storage adaptor.
|
||||
These status of these alternatives are specified in their READMEs,
|
||||
which are available at the following URLs:
|
||||
|
||||
mongodb: a noSQL database
|
||||
https://github.com/xwiki-labs/cryptpad-mongo-store
|
||||
amnesiadb: in memory storage
|
||||
https://github.com/xwiki-labs/cryptpad-amnesia-store
|
||||
leveldb: a simple, fast, key-value store
|
||||
https://github.com/xwiki-labs/cryptpad-level-store
|
||||
sql: an adaptor for a variety of sql databases via knexjs
|
||||
https://github.com/xwiki-labs/cryptpad-sql-store
|
||||
|
||||
For the most up to date solution, use the default storage adaptor.
|
||||
*/
|
||||
storage: './storage/file',
|
||||
|
||||
/*
|
||||
Cryptpad stores each document in an individual file on your hard drive.
|
||||
Specify a directory where files should be stored.
|
||||
It will be created automatically if it does not already exist.
|
||||
*/
|
||||
filePath: './datastore/',
|
||||
|
||||
/* CryptPad allows logged in users to request that particular documents be
|
||||
* stored by the server indefinitely. This is called 'pinning'.
|
||||
* Pin requests are stored in a pin-store. The location of this store is
|
||||
* defined here.
|
||||
*/
|
||||
pinPath: './pins',
|
||||
|
||||
/* CryptPad allows logged in users to upload encrypted files. Files/blobs
|
||||
* are stored in a 'blob-store'. Set its location here.
|
||||
*/
|
||||
blobPath: './blob',
|
||||
|
||||
/* CryptPad stores incomplete blobs in a 'staging' area until they are
|
||||
* fully uploaded. Set its location here.
|
||||
*/
|
||||
blobStagingPath: './blobstage',
|
||||
|
||||
/* Cryptpad's file storage adaptor closes unused files after a configurale
|
||||
* number of milliseconds (default 30000 (30 seconds))
|
||||
*/
|
||||
channelExpirationMs: 30000,
|
||||
|
||||
/* Cryptpad's file storage adaptor is limited by the number of open files.
|
||||
* When the adaptor reaches openFileLimit, it will clean up older files
|
||||
*/
|
||||
openFileLimit: 2048,
|
||||
|
||||
/* Cryptpad's socket server can be extended to respond to RPC calls
|
||||
* you can configure it to respond to custom RPC calls if you like.
|
||||
* provide the path to your RPC module here, or `false` if you would
|
||||
* like to disable the RPC interface completely
|
||||
*/
|
||||
rpc: './rpc.js',
|
||||
|
||||
/* RPC errors are shown by default, but if you really don't care,
|
||||
* you can suppress them
|
||||
*/
|
||||
suppressRPCErrors: false,
|
||||
|
||||
|
||||
/* WARNING: EXPERIMENTAL
|
||||
*
|
||||
* CryptPad features experimental support for encrypted file upload.
|
||||
* Our encryption format is still liable to change. As such, we do not
|
||||
* guarantee that files uploaded now will be supported in the future
|
||||
*/
|
||||
|
||||
/* Setting this value to anything other than true will cause file upload
|
||||
* attempts to be rejected outright.
|
||||
*/
|
||||
enableUploads: true,
|
||||
|
||||
/* If you have enabled file upload, you have the option of restricting it
|
||||
* to a list of users identified by their public keys. If this value is set
|
||||
* to true, your server will query a file (cryptpad/privileged.conf) when
|
||||
* users connect via RPC. Only users whose public keys can be found within
|
||||
* the file will be allowed to upload.
|
||||
*
|
||||
* privileged.conf uses '#' for line comments, and splits keys by newline.
|
||||
* This is a temporary measure until a better quota system is in place.
|
||||
* registered users' public keys can be found on the settings page.
|
||||
*/
|
||||
restrictUploads: false,
|
||||
|
||||
/* clients can use the /settings/ app to opt out of usage feedback
|
||||
* which informs the server of things like how much each app is being
|
||||
* used, and whether certain clientside features are supported by
|
||||
* the client's browser. The intent is to provide feedback to the admin
|
||||
* such that the service can be improved. Enable this with `true`
|
||||
* and ignore feedback with `false` or by commenting the attribute
|
||||
*/
|
||||
//logFeedback: true,
|
||||
|
||||
/* it is recommended that you serve cryptpad over https
|
||||
* the filepaths below are used to configure your certificates
|
||||
*/
|
||||
//privKeyAndCertFiles: [
|
||||
// '/etc/apache2/ssl/my_secret.key',
|
||||
// '/etc/apache2/ssl/my_public_cert.crt',
|
||||
// '/etc/apache2/ssl/my_certificate_authorities_cert_chain.ca'
|
||||
//],
|
||||
};
|
||||
+3
-1
@@ -7,7 +7,9 @@ define([], function () {
|
||||
// jquery declares itself as literally "jquery" so it cannot be pulled by path :(
|
||||
"jquery": "/bower_components/jquery/dist/jquery.min",
|
||||
// json.sortify same
|
||||
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify"
|
||||
"json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
|
||||
"pdfjs-dist/build/pdf": "/bower_components/pdfjs-dist/build/pdf",
|
||||
"pdfjs-dist/build/pdf.worker": "/bower_components/pdfjs-dist/build/pdf.worker"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=82)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=n(1),c=n(5),s=n(6),f=function(e){function t(e,n){r(this,t);var i=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,a.MATCHER,c.ANY));return i.targetType=n,i}return i(t,e),u(t,[{key:"getTargetType",value:function(){return this.targetType}}]),t}(s);e.exports=f},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=n(1),a=n(5),c=n(6),s=function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,u.RENDERER,a.ONCE))}return i(t,e),t}(c);e.exports=s},function(e,t,n){"use strict";function r(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),u=n(1),a=n(5),c=n(8),s=n(12),f=function(){function e(){o(this,e)}return i(e,null,[{key:"start",value:function(t){var n=e.unstack(t);n?n.type!==u.SANITIZER&&e.fill(t):e.fill(t),e.snapshot(t),e.check(t),e.run(t)}},{key:"snapshot",value:function(t){var n=t.getId();if(e.stacks[n]){var r=[];e.stacks[n].forEach(function(e){var t={};t.identifier=e.getIdentifier(),t.type=e.getType(),r.push(t)}),e.snapshots[n]?e.snapshots[n].push({stack:r}):e.snapshots[n]=[{stack:r}]}else e.snapshots[n]=[]}},{key:"fill",value:function(t){var n=[],o=t.getId(),i=c.getPlugins(u.MATCHER),s=i.filter(function(e){return e.process(t)}),f=s.map(function(e){return e.getIdentifier()}),l=c.values(),p=l.filter(function(e){return e.type!==u.MATCHER&&f.includes(e.identifier)}),y=c.filterByOccurrencies(p),b=!0,h=!1,v=void 0;try{for(var w,d=y.once[Symbol.iterator]();!(b=(w=d.next()).done);b=!0){var g=w.value;n.push(g),y.every&&n.push.apply(n,r(y.every))}}catch(e){h=!0,v=e}finally{try{!b&&d.return&&d.return()}finally{if(h)throw v}}var m=!0,O=!1,E=void 0;try{for(var k,_=y.any[Symbol.iterator]();!(m=(k=_.next()).done);m=!0){var j=k.value;n.push(j),y.every&&n.push.apply(n,r(y.every))}}catch(e){O=!0,E=e}finally{try{!m&&_.return&&_.return()}finally{if(O)throw E}}if(e.stacks[o]){var P=n.shift();if(P){if(P.occurrence!==a.ONCE)throw new Error("The last plugin is not runnable once");var T;(T=e.stacks[o]).push.apply(T,n)}else{var A;(A=e.stacks[o]).push.apply(A,n)}}else e.stacks[o]=n}},{key:"unstack",value:function(t){var n=t.getId();return e.stacks[n]?e.stacks[n].pop():null}},{key:"check",vLine truncated
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=86)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=n(1),c=n(5),s=n(6),f=function(e){function t(e,n){r(this,t);var i=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,a.MATCHER,c.ANY));return i.targetType=n,i}return i(t,e),u(t,[{key:"getTargetType",value:function(){return this.targetType}}]),t}(s);e.exports=f},function(e,t,n){"use strict";function r(e){if(e instanceof Array){var t=[];return e.forEach(function(e){e.mediaObject?t.push(e.mediaObject):t.push(r.start(e))}),t}var n=e;return r.start(n)}var o=n(26),i=n(25),u=n(44),a=n(45),c=n(24),s=n(23);r.createMediaObject=function(e){var t=new s(e),n=c.parse(t);return t.setProperties(n),t},r.start=function(e){var t=r.createMediaObject(e);return r.processingEngine.start(t)},r.pluginStore=r.pluginStore||new u,r.uriStore=r.uriStore||new a("../plugins"),r.processingEngine=r.processingEngine||new o(r.pluginStore),r.matchingEngine=r.matchingEngine||new i(r.pluginStore,r.uriStore),r.loadingEngine=null,e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=n(1),a=n(5),c=n(6),s=function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,u.RENDERER,a.ONCE))}return i(t,e),t}(c);e.exports=s},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=function(){function e(t,n,o){r(this,e),this.identifier=t,this.type=n,this.occurrence=o}return o(e,[{key:"getIdentifier",value:function(){if(this.identifier)return this.identifier;throw new Error("Plugin has not identifier")}},{key:"getType",value:function(){if(this.type)return this.type;throw new Error("Plugin has no type")}},{key:"start",value:function(e){return this.process(e)}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Line truncated
|
||||
+27
-1
@@ -6,6 +6,8 @@ define([
|
||||
'/common/cryptpad-common.js',
|
||||
//'/common/visible.js',
|
||||
//'/common/notify.js',
|
||||
'pdfjs-dist/build/pdf',
|
||||
'pdfjs-dist/build/pdf.worker',
|
||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {
|
||||
@@ -57,7 +59,7 @@ define([
|
||||
var $mt = $iframe.find('#encryptedFile');
|
||||
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
|
||||
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
|
||||
$mt.attr('data-type', type);
|
||||
// $mt.attr('data-type', type);
|
||||
|
||||
$(window.document).on('decryption', function (e) {
|
||||
var decrypted = e.originalEvent;
|
||||
@@ -98,6 +100,30 @@ define([
|
||||
|
||||
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
|
||||
|
||||
/**
|
||||
* Allowed mime types that have to be set for a rendering after a decryption.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
const allowedMediaTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'application/pdf',
|
||||
'application/dash+xml',
|
||||
'download'
|
||||
];
|
||||
|
||||
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
|
||||
|
||||
MediaTag($mt[0]);
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
|
||||
Reference in new issue
Block a user