Restructured EEPROM client js.
This commit is contained in:
parent
6ee8738686
commit
d5d4096dc3
5 changed files with 338 additions and 193 deletions
2
cron.php
2
cron.php
|
@ -14,7 +14,7 @@ $semaphore = sem_get($ftok, 1);
|
|||
if(!sem_acquire($semaphore))
|
||||
die('Failed to acquire semaphore.' . PHP_EOL);
|
||||
|
||||
require_once __DIR__ . '/startup.php';
|
||||
require_once __DIR__ . '/eeprom.php';
|
||||
|
||||
// Mark expired as deleted
|
||||
$expired = Upload::expired();
|
||||
|
|
185
eeprom.js
185
eeprom.js
|
@ -1,185 +0,0 @@
|
|||
var EEPROM = function(srcId, endpoint, authorization) {
|
||||
this.srcId = parseInt(srcId);
|
||||
this.setEndpoint(endpoint);
|
||||
this.setAuthorization(authorization);
|
||||
};
|
||||
EEPROM.prototype.setEndpoint = function(endpoint) {
|
||||
this.endpoint = endpoint.toString();
|
||||
};
|
||||
EEPROM.prototype.setAuthorization = function(authorization) {
|
||||
this.authorization = authorization.toString();
|
||||
};
|
||||
EEPROM.prototype.deleteUpload = function(fileInfo) {
|
||||
return new EEPROM.EEPROMDeleteTask(
|
||||
this.authorization,
|
||||
fileInfo
|
||||
);
|
||||
};
|
||||
EEPROM.prototype.createUpload = function(file) {
|
||||
return new EEPROM.EEPROMUploadTask(
|
||||
this.srcId,
|
||||
this.endpoint,
|
||||
this.authorization,
|
||||
file
|
||||
);
|
||||
};
|
||||
EEPROM.ERR_GENERIC = 'generic'; // Generic error, no further details
|
||||
EEPROM.ERR_INVALID = 'invalid'; // Request is invalid
|
||||
EEPROM.ERR_AUTH = 'auth'; // Auth token expired
|
||||
EEPROM.ERR_ACCESS = 'access'; // Barred from uploading
|
||||
EEPROM.ERR_DMCA = 'dmca'; // DMCA takedown
|
||||
EEPROM.ERR_GONE = 'gone'; // Invalid endpoint
|
||||
EEPROM.ERR_SERVER = 'server'; // Server error
|
||||
EEPROM.ERR_SIZE = 'size'; // Too large
|
||||
|
||||
EEPROM.EEPROMFile = function(fileInfo) {
|
||||
// A merge operation woulda been neat.
|
||||
this.id = (fileInfo.id || '').toString();
|
||||
this.url = (fileInfo.url || '').toString();
|
||||
this.urlf = (fileInfo.urlf || '').toString();
|
||||
this.thumb = (fileInfo.thumb || '').toString();
|
||||
this.name = (fileInfo.name || '').toString();
|
||||
this.type = (fileInfo.type || '').toString();
|
||||
this.size = parseInt(fileInfo.size || 0);
|
||||
this.user = parseInt(fileInfo.user || 0);
|
||||
this.hash = (fileInfo.hash || '').toString();
|
||||
this.created = (fileInfo.created || null);
|
||||
this.accessed = (fileInfo.accessed || null);
|
||||
this.expires = (fileInfo.expires || null);
|
||||
this.deleted = (fileInfo.deleted || null);
|
||||
this.dmca = (fileInfo.dmca || null);
|
||||
};
|
||||
EEPROM.EEPROMFile.prototype.isImage = function() { return this.type.indexOf('image/') === 0; };
|
||||
EEPROM.EEPROMFile.prototype.isAudio = function() { return this.type.indexOf('audio/') === 0; };
|
||||
EEPROM.EEPROMFile.prototype.isVideo = function() { return this.type.indexOf('video/') === 0; };
|
||||
|
||||
EEPROM.EEPROMDeleteTask = function(authorization, fileInfo) {
|
||||
var _self = this;
|
||||
this.authorization = authorization;
|
||||
this.fileInfo = fileInfo;
|
||||
this.xhr = new XMLHttpRequest;
|
||||
this.xhr.addEventListener('readystatechange', function() {
|
||||
if(this.readyState !== 4)
|
||||
return;
|
||||
|
||||
if(this.status !== 204) {
|
||||
_self.errorCode = EEPROM.ERR_GENERIC;
|
||||
|
||||
switch(this.status) {
|
||||
case 401:
|
||||
_self.errorCode = EEPROM.ERR_AUTH;
|
||||
break;
|
||||
case 403:
|
||||
_self.errorCode = EEPROM.ERR_ACCESS;
|
||||
break;
|
||||
case 404:
|
||||
case 410:
|
||||
_self.errorCode = EEPROM.ERR_GONE;
|
||||
break;
|
||||
case 500:
|
||||
case 503:
|
||||
_self.errorCode = EEPROM.ERR_SERVER;
|
||||
break;
|
||||
}
|
||||
|
||||
if(_self.onFailure)
|
||||
_self.onFailure(_self.errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if(_self.onSuccess)
|
||||
_self.onSuccess();
|
||||
});
|
||||
};
|
||||
EEPROM.EEPROMDeleteTask.prototype.onSuccess = undefined;
|
||||
EEPROM.EEPROMDeleteTask.prototype.onFailure = undefined;
|
||||
EEPROM.EEPROMDeleteTask.prototype.start = function() {
|
||||
this.xhr.open('DELETE', this.fileInfo.urlf);
|
||||
this.xhr.setRequestHeader('Authorization', this.authorization);
|
||||
this.xhr.send();
|
||||
};
|
||||
|
||||
EEPROM.EEPROMUploadTask = function(srcId, endpoint, authorization, file) {
|
||||
var _self = this;
|
||||
|
||||
this.aborted = false;
|
||||
this.endpoint = endpoint;
|
||||
this.authorization = authorization;
|
||||
this.xhr = new XMLHttpRequest;
|
||||
this.formData = new FormData;
|
||||
this.formData.append('src', srcId);
|
||||
this.formData.append('file', file);
|
||||
|
||||
var reportUploadProgress = function(ev) {
|
||||
if(_self.onProgress)
|
||||
_self.onProgress(new EEPROM.EEPROMUploadTask.EEPROMUploadTaskProgress(ev.loaded, ev.total));
|
||||
};
|
||||
|
||||
this.xhr.upload.addEventListener('loadstart', reportUploadProgress);
|
||||
this.xhr.upload.addEventListener('progress', reportUploadProgress);
|
||||
this.xhr.upload.addEventListener('load', reportUploadProgress);
|
||||
|
||||
this.xhr.addEventListener('readystatechange', function() {
|
||||
if(this.readyState !== 4)
|
||||
return;
|
||||
|
||||
if(this.status !== 201) {
|
||||
_self.failureResponse = {
|
||||
userAborted: _self.aborted,
|
||||
error: EEPROM.ERR_GENERIC,
|
||||
};
|
||||
|
||||
switch(this.status) {
|
||||
case 400:
|
||||
case 405:
|
||||
_self.failureResponse.error = EEPROM.ERR_INVALID;
|
||||
break;
|
||||
case 401:
|
||||
_self.failureResponse.error = EEPROM.ERR_AUTH;
|
||||
break;
|
||||
case 403:
|
||||
_self.failureResponse.error = EEPROM.ERR_ACCESS;
|
||||
break;
|
||||
case 404:
|
||||
case 410:
|
||||
_self.failureResponse.error = EEPROM.ERR_GONE;
|
||||
break;
|
||||
case 451:
|
||||
_self.failureResponse.error = EEPROM.ERR_DMCA;
|
||||
break;
|
||||
case 413:
|
||||
_self.failureResponse.error = EEPROM.ERR_SIZE;
|
||||
_self.failureResponse.maxSize = parseInt(this.getResponseHeader('X-EEPROM-Max-Size'));
|
||||
break;
|
||||
case 500:
|
||||
case 503:
|
||||
_self.failureResponse.error = EEPROM.ERR_SERVER;
|
||||
break;
|
||||
}
|
||||
|
||||
if(_self.onFailure)
|
||||
_self.onFailure(_self.failureResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
_self.fileInfo = new EEPROM.EEPROMFile(JSON.parse(this.responseText));
|
||||
|
||||
if(_self.onComplete)
|
||||
_self.onComplete(_self.fileInfo);
|
||||
});
|
||||
};
|
||||
EEPROM.EEPROMUploadTask.EEPROMUploadTaskProgress = function(loaded, total) {
|
||||
this.progress = Math.ceil(((this.loaded = loaded) / (this.total = total)) * 100);
|
||||
};
|
||||
EEPROM.EEPROMUploadTask.prototype.onComplete = undefined;
|
||||
EEPROM.EEPROMUploadTask.prototype.onFailure = undefined;
|
||||
EEPROM.EEPROMUploadTask.prototype.onProgress = undefined;
|
||||
EEPROM.EEPROMUploadTask.prototype.abort = function() {
|
||||
this.aborted = true;
|
||||
this.xhr.abort();
|
||||
};
|
||||
EEPROM.EEPROMUploadTask.prototype.start = function() {
|
||||
this.xhr.open('POST', this.endpoint);
|
||||
this.xhr.setRequestHeader('Authorization', this.authorization);
|
||||
this.xhr.send(this.formData);
|
||||
};
|
|
@ -5,15 +5,11 @@ define('PRM_STARTUP', microtime(true));
|
|||
define('PRM_ROOT', __DIR__);
|
||||
define('PRM_CLI', PHP_SAPI === 'cli');
|
||||
define('PRM_DEBUG', is_file(PRM_ROOT . '/.debug'));
|
||||
define('PRM_PHP_MIN_VER', '7.4.0');
|
||||
define('PRM_PUBLIC', PRM_ROOT . '/public');
|
||||
define('PRM_SOURCE', PRM_ROOT . '/src');
|
||||
define('PRM_UPLOADS', PRM_PUBLIC . '/data');
|
||||
define('PRM_THUMBS', PRM_PUBLIC . '/thumb');
|
||||
|
||||
if(version_compare(PHP_VERSION, PRM_PHP_MIN_VER, '<'))
|
||||
die('EEPROM required at least PHP ' . PRM_PHP_MIN_VER . '.');
|
||||
|
||||
error_reporting(PRM_DEBUG ? -1 : 0);
|
||||
ini_set('display_errors', PRM_DEBUG ? 'On' : 'Off');
|
||||
|
212
js/eeprom-v1.0.js
Normal file
212
js/eeprom-v1.0.js
Normal file
|
@ -0,0 +1,212 @@
|
|||
var EEPROM = function(srcId, endpoint, authorization) {
|
||||
var obj = {
|
||||
srcId: parseInt(srcId),
|
||||
endpoint: endpoint,
|
||||
authorization: authorization,
|
||||
};
|
||||
|
||||
console.log(obj);
|
||||
|
||||
obj.setEndpoint = function(endpoint) {
|
||||
obj.endpoint = (endpoint || '').toString();
|
||||
};
|
||||
|
||||
obj.setAuthorization = function(authorization) {
|
||||
obj.authorization = (authorization || '').toString();
|
||||
};
|
||||
|
||||
obj.deleteUpload = function(fileInfo) {
|
||||
return new EEPROM.EEPROMDeleteTask(
|
||||
obj.authorization,
|
||||
fileInfo
|
||||
);
|
||||
};
|
||||
|
||||
obj.createUpload = function(file) {
|
||||
return new EEPROM.EEPROMUploadTask(
|
||||
obj.srcId,
|
||||
obj.endpoint,
|
||||
obj.authorization,
|
||||
file
|
||||
);
|
||||
};
|
||||
|
||||
return obj;
|
||||
};
|
||||
EEPROM.ERR_GENERIC = 'generic';
|
||||
EEPROM.ERR_INVALID = 'invalid';
|
||||
EEPROM.ERR_AUTH = 'auth';
|
||||
EEPROM.ERR_ACCESS = 'access';
|
||||
EEPROM.ERR_DMCA = 'dmca';
|
||||
EEPROM.ERR_GONE = 'gone';
|
||||
EEPROM.ERR_SERVER = 'server';
|
||||
EEPROM.ERR_SIZE = 'size';
|
||||
|
||||
EEPROM.EEPROMFile = function(fileInfo) {
|
||||
var obj = {
|
||||
id: (fileInfo.id || '').toString(),
|
||||
url: (fileInfo.url || '').toString(),
|
||||
urlf: (fileInfo.urlf || '').toString(),
|
||||
thumb: (fileInfo.thumb || '').toString(),
|
||||
name: (fileInfo.name || '').toString(),
|
||||
type: (fileInfo.type || '').toString(),
|
||||
size: parseInt(fileInfo.size || 0),
|
||||
user: parseInt(fileInfo.user || 0),
|
||||
hash: (fileInfo.hash || '').toString(),
|
||||
created: (fileInfo.created || null),
|
||||
accessed: (fileInfo.accessed || null),
|
||||
expires: (fileInfo.expires || null),
|
||||
deleted: (fileInfo.deleted || null),
|
||||
dmca: (fileInfo.dmca || null),
|
||||
};
|
||||
|
||||
obj.isImage = function() { return obj.type.indexOf('image/') === 0; };
|
||||
obj.isAudio = function() { return obj.type.indexOf('audio/') === 0; };
|
||||
obj.isVideo = function() { return obj.type.indexOf('video/') === 0; };
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
EEPROM.EEPROMDeleteTask = function(authorization, fileInfo) {
|
||||
var obj = {
|
||||
authorization: authorization,
|
||||
fileInfo: fileInfo,
|
||||
onSuccess: undefined,
|
||||
onFailure: undefined,
|
||||
};
|
||||
|
||||
var xhr = obj.xhr = new XMLHttpRequest;
|
||||
obj.xhr.addEventListener('readystatechange', function() {
|
||||
if(xhr.readyState !== 4)
|
||||
return;
|
||||
|
||||
if(xhr.status !== 204) {
|
||||
obj.errorCode = EEPROM.ERR_GENERIC;
|
||||
|
||||
switch(xhr.status) {
|
||||
case 401:
|
||||
obj.errorCode = EEPROM.ERR_AUTH;
|
||||
break;
|
||||
case 403:
|
||||
obj.errorCode = EEPROM.ERR_ACCESS;
|
||||
break;
|
||||
case 404:
|
||||
case 410:
|
||||
obj.errorCode = EEPROM.ERR_GONE;
|
||||
break;
|
||||
case 500:
|
||||
case 503:
|
||||
obj.errorCode = EEPROM.ERR_SERVER;
|
||||
break;
|
||||
}
|
||||
|
||||
if(obj.onFailure)
|
||||
obj.onFailure(obj.errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
if(obj.onSuccess)
|
||||
obj.onSuccess();
|
||||
});
|
||||
|
||||
obj.start = function() {
|
||||
xhr.open('DELETE', obj.fileInfo.urlf);
|
||||
xhr.setRequestHeader('Authorization', obj.authorization);
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
EEPROM.EEPROMUploadTask = function(srcId, endpoint, authorization, file) {
|
||||
var obj = {
|
||||
aborted: false,
|
||||
endpoint: endpoint,
|
||||
authorization: authorization,
|
||||
onComplete: undefined,
|
||||
onFailure: undefined,
|
||||
onProgress: undefined,
|
||||
failureResponse: undefined,
|
||||
};
|
||||
|
||||
var xhr = obj.xhr = new XMLHttpRequest,
|
||||
fd = obj.formData = new FormData;
|
||||
|
||||
fd.append('src', srcId);
|
||||
fd.append('file', file);
|
||||
|
||||
var reportUploadProgress = function(ev) {
|
||||
if(obj.onProgress)
|
||||
obj.onProgress({
|
||||
loaded: ev.loaded,
|
||||
total: ev.total,
|
||||
progress: Math.ceil((ev.loaded / ev.total) * 100),
|
||||
});
|
||||
};
|
||||
|
||||
xhr.upload.addEventListener('loadstart', reportUploadProgress);
|
||||
xhr.upload.addEventListener('progress', reportUploadProgress);
|
||||
xhr.upload.addEventListener('load', reportUploadProgress);
|
||||
|
||||
xhr.addEventListener('readystatechange', function() {
|
||||
if(this.readyState !== 4)
|
||||
return;
|
||||
|
||||
if(this.status !== 201) {
|
||||
obj.failureResponse = {
|
||||
userAborted: obj.aborted,
|
||||
error: EEPROM.ERR_GENERIC,
|
||||
};
|
||||
|
||||
switch(this.status) {
|
||||
case 400:
|
||||
case 405:
|
||||
obj.failureResponse.error = EEPROM.ERR_INVALID;
|
||||
break;
|
||||
case 401:
|
||||
obj.failureResponse.error = EEPROM.ERR_AUTH;
|
||||
break;
|
||||
case 403:
|
||||
obj.failureResponse.error = EEPROM.ERR_ACCESS;
|
||||
break;
|
||||
case 404:
|
||||
case 410:
|
||||
obj.failureResponse.error = EEPROM.ERR_GONE;
|
||||
break;
|
||||
case 451:
|
||||
obj.failureResponse.error = EEPROM.ERR_DMCA;
|
||||
break;
|
||||
case 413:
|
||||
obj.failureResponse.error = EEPROM.ERR_SIZE;
|
||||
obj.failureResponse.maxSize = parseInt(this.getResponseHeader('X-EEPROM-Max-Size'));
|
||||
break;
|
||||
case 500:
|
||||
case 503:
|
||||
obj.failureResponse.error = EEPROM.ERR_SERVER;
|
||||
break;
|
||||
}
|
||||
|
||||
if(obj.onFailure)
|
||||
obj.onFailure(obj.failureResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
obj.fileInfo = new EEPROM.EEPROMFile(JSON.parse(this.responseText));
|
||||
|
||||
if(obj.onComplete)
|
||||
obj.onComplete(obj.fileInfo);
|
||||
});
|
||||
|
||||
obj.abort = function() {
|
||||
obj.aborted = true;
|
||||
xhr.abort();
|
||||
};
|
||||
|
||||
obj.start = function() {
|
||||
xhr.open('POST', obj.endpoint);
|
||||
xhr.setRequestHeader('Authorization', obj.authorization);
|
||||
xhr.send(fd);
|
||||
};
|
||||
|
||||
return obj;
|
||||
};
|
128
public/index.php
128
public/index.php
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
namespace EEPROM;
|
||||
|
||||
require_once __DIR__ . '/../startup.php';
|
||||
require_once __DIR__ . '/../eeprom.php';
|
||||
|
||||
$reqMethod = $_SERVER['REQUEST_METHOD'];
|
||||
$reqPath = '/' . trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
|
||||
|
@ -159,9 +159,131 @@ if(preg_match('#^/uploads/([a-zA-Z0-9-_]{32})\.json/?$#', $reqPath, $matches)) {
|
|||
return;
|
||||
}
|
||||
|
||||
if($reqPath === '/eeprom.js' && is_file(PRM_ROOT . '/eeprom.js')) {
|
||||
if($reqPath === '/eeprom.js') {
|
||||
header('Content-Type: application/javascript; charset=utf-8');
|
||||
echo file_get_contents(PRM_ROOT . '/eeprom.js');
|
||||
echo file_get_contents(PRM_ROOT . '/js/eeprom-v1.0.js');
|
||||
return;
|
||||
}
|
||||
|
||||
if($reqPath === '/test-v1.0.html' && PRM_DEBUG) {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
$cookie = htmlspecialchars((string)filter_input(INPUT_COOKIE, 'msz_auth'));
|
||||
|
||||
echo <<<TEST
|
||||
<!doctype html>
|
||||
<input type="file" id="-eeprom-file"/> <button id="-eeprom-abort">Abort</button> <progress id="-eeprom-progress" min="0" max="100" value="0"></progress> <span id="-eeprom-progress-done"></span> <span id="-eeprom-progress-total"></span><br/>
|
||||
<div id="-eeprom-history"></div>
|
||||
<script src="/eeprom.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var eFile = document.getElementById('-eeprom-file'),
|
||||
eAbort = document.getElementById('-eeprom-abort'),
|
||||
eProgress = document.getElementById('-eeprom-progress'),
|
||||
eProgressD = document.getElementById('-eeprom-progress-done'),
|
||||
eProgressT = document.getElementById('-eeprom-progress-total'),
|
||||
eHistory = document.getElementById('-eeprom-history'),
|
||||
eTask = undefined;
|
||||
|
||||
eAbort.onclick = function() {
|
||||
if(eTask)
|
||||
eTask.abort();
|
||||
};
|
||||
|
||||
var eClient = new EEPROM(1, '/uploads', 'Misuzu {$cookie}');
|
||||
|
||||
eFile.onchange = function() {
|
||||
var task = eTask = eClient.createUpload(this.files[0]);
|
||||
|
||||
task.onProgress = function(progressInfo) {
|
||||
eProgress.value = progressInfo.progress;
|
||||
eProgressD.textContent = progressInfo.loaded;
|
||||
eProgressT.textContent = progressInfo.total;
|
||||
};
|
||||
|
||||
task.onFailure = function(errorInfo) {
|
||||
if(!errorInfo.userAborted) {
|
||||
var errorText = 'Was unable to upload file.';
|
||||
|
||||
switch(errorInfo.error) {
|
||||
case EEPROM.ERR_INVALID:
|
||||
errorText = 'Upload request was invalid.';
|
||||
break;
|
||||
case EEPROM.ERR_AUTH:
|
||||
errorText = 'Upload authentication failed, refresh and try again.';
|
||||
break;
|
||||
case EEPROM.ERR_ACCESS:
|
||||
errorText = 'You\'re not allowed to upload files.';
|
||||
break;
|
||||
case EEPROM.ERR_GONE:
|
||||
errorText = 'Upload client has a configuration error or the server is gone.';
|
||||
break;
|
||||
case EEPROM.ERR_DMCA:
|
||||
errorText = 'This file has been uploaded before and was removed for copyright reasons, you cannot upload this file.';
|
||||
break;
|
||||
case EEPROM.ERR_SERVER:
|
||||
errorText = 'Upload server returned a critical error, try again later.';
|
||||
break;
|
||||
case EEPROM.ERR_SIZE:
|
||||
if(errorInfo.maxSize < 1)
|
||||
errorText = 'Selected file is too large.';
|
||||
else {
|
||||
var _t = ['bytes', 'KB', 'MB', 'GB', 'TB'],
|
||||
_i = parseInt(Math.floor(Math.log(errorInfo.maxSize) / Math.log(1024))),
|
||||
_s = Math.round(errorInfo.maxSize / Math.pow(1024, _i), 2);
|
||||
|
||||
errorText = 'Upload may not be larger than %1 %2.'.replace('%1', _s).replace('%2', _t[_i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
alert(errorText);
|
||||
}
|
||||
};
|
||||
|
||||
task.onComplete = function(fileInfo) {
|
||||
eTask = undefined;
|
||||
|
||||
var elem = document.createElement('div');
|
||||
elem.id = fileInfo.id;
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.textContent = fileInfo.id;
|
||||
link.href = fileInfo.url;
|
||||
link.target = '_blank';
|
||||
elem.appendChild(link);
|
||||
|
||||
elem.appendChild(document.createTextNode(' '));
|
||||
|
||||
var del = document.createElement('button');
|
||||
del.textContent = 'Delete';
|
||||
del.onclick = function() {
|
||||
var dTask = eClient.deleteUpload(fileInfo);
|
||||
|
||||
dTask.onFailure = function(reason) {
|
||||
alert('Delete failed: ' + reason);
|
||||
};
|
||||
|
||||
dTask.onSuccess = function() {
|
||||
eHistory.removeChild(elem);
|
||||
};
|
||||
|
||||
dTask.start();
|
||||
};
|
||||
elem.appendChild(del);
|
||||
|
||||
elem.appendChild(document.createTextNode(' '));
|
||||
|
||||
var json = document.createElement('code');
|
||||
json.textContent = JSON.stringify(fileInfo);
|
||||
elem.appendChild(json);
|
||||
|
||||
eHistory.appendChild(elem);
|
||||
};
|
||||
|
||||
task.start();
|
||||
};
|
||||
</script>
|
||||
TEST;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue