Rewrote Javascript code.
This commit is contained in:
parent
dba5754ccc
commit
bd3e055323
21 changed files with 889 additions and 903 deletions
|
@ -1,128 +0,0 @@
|
|||
#include utils.js
|
||||
#include uiharu.js
|
||||
#include aembed.js
|
||||
#include iembed.js
|
||||
#include vembed.js
|
||||
|
||||
var MszEmbed = (function() {
|
||||
let uiharu = undefined;
|
||||
|
||||
return {
|
||||
init: function(endPoint) {
|
||||
uiharu = new Uiharu(endPoint);
|
||||
},
|
||||
handle: function(targets) {
|
||||
if(!Array.isArray(targets))
|
||||
targets = Array.from(targets);
|
||||
|
||||
const filtered = new Map;
|
||||
for(const target of targets) {
|
||||
if(!(target instanceof HTMLElement)
|
||||
|| !('dataset' in target)
|
||||
|| !('mszEmbedUrl' in target.dataset))
|
||||
continue;
|
||||
|
||||
const cleanUrl = target.dataset.mszEmbedUrl.replace(/ /, '%20');
|
||||
if(cleanUrl.indexOf('https://') !== 0
|
||||
&& cleanUrl.indexOf('http://') !== 0
|
||||
&& cleanUrl.indexOf('//') !== 0) {
|
||||
target.textContent = target.dataset.mszEmbedUrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
$rc(target);
|
||||
target.appendChild($e({
|
||||
tag: 'i',
|
||||
attrs: {
|
||||
className: 'fas fa-2x fa-spinner fa-pulse',
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
lineHeight: '32px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
if(filtered.has(cleanUrl))
|
||||
filtered.get(cleanUrl).push(target);
|
||||
else
|
||||
filtered.set(cleanUrl, [target]);
|
||||
}
|
||||
|
||||
const replaceWithUrl = function(targets, url) {
|
||||
for(const target of targets) {
|
||||
let body = $e({
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
className: 'link',
|
||||
href: url,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
},
|
||||
child: url
|
||||
});
|
||||
$ib(target, body);
|
||||
$r(target);
|
||||
}
|
||||
};
|
||||
|
||||
filtered.forEach(function(targets, url) {
|
||||
uiharu.lookupOne(url, function(metadata) {
|
||||
if(metadata.error) {
|
||||
replaceWithUrl(targets, url);
|
||||
console.error(metadata.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if(metadata.title === undefined) {
|
||||
replaceWithUrl(targets, url);
|
||||
console.warn('Media is no longer available.');
|
||||
return;
|
||||
}
|
||||
|
||||
let phc = undefined,
|
||||
options = {
|
||||
onembed: console.log,
|
||||
};
|
||||
|
||||
if(metadata.type === 'youtube:video') {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'youtube';
|
||||
options.player = MszVideoEmbedYouTube;
|
||||
} else if(metadata.type === 'niconico:video') {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'nicovideo';
|
||||
options.player = MszVideoEmbedNicoNico;
|
||||
} else if(metadata.is_video) {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'external';
|
||||
options.player = MszVideoEmbedPlayer;
|
||||
//options.frame = MszVideoEmbedFrame;
|
||||
options.nativeControls = true;
|
||||
options.autosize = false;
|
||||
options.maxWidth = 640;
|
||||
options.maxHeight = 360;
|
||||
} else if(metadata.is_audio) {
|
||||
phc = MszAudioEmbedPlaceholder;
|
||||
options.type = 'external';
|
||||
options.player = MszAudioEmbedPlayer;
|
||||
options.nativeControls = true;
|
||||
} else if(metadata.is_image) {
|
||||
phc = MszImageEmbed;
|
||||
options.type = 'external';
|
||||
}
|
||||
|
||||
if(phc === undefined)
|
||||
return;
|
||||
|
||||
for(const target of targets) {
|
||||
const placeholder = new phc(metadata, options, target);
|
||||
if(placeholder !== undefined)
|
||||
placeholder.replaceElement(target);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -1,4 +1,4 @@
|
|||
#include utils.js
|
||||
#include utility.js
|
||||
#include watcher.js
|
||||
|
||||
const MszAudioEmbedPlayerEvents = function() {
|
||||
|
@ -84,7 +84,8 @@ const MszAudioEmbedPlayer = function(metadata, options) {
|
|||
getType: function() { return 'external'; },
|
||||
};
|
||||
|
||||
watchers.proxy(pub);
|
||||
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||
|
||||
player.addEventListener('play', function() { watchers.call('play', pub); });
|
||||
|
135
assets/misuzu.js/embed/embed.js
Normal file
135
assets/misuzu.js/embed/embed.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
#include utility.js
|
||||
#include embed/audio.js
|
||||
#include embed/image.js
|
||||
#include embed/video.js
|
||||
#include ext/uiharu.js
|
||||
|
||||
const MszEmbed = (function() {
|
||||
let uiharu = undefined;
|
||||
|
||||
return {
|
||||
init: function(endPoint) {
|
||||
uiharu = new MszUiharu(endPoint);
|
||||
},
|
||||
handle: function(targets) {
|
||||
if(!Array.isArray(targets))
|
||||
targets = Array.from(targets);
|
||||
|
||||
const filtered = new Map;
|
||||
for(const target of targets) {
|
||||
if(!(target instanceof HTMLElement)
|
||||
|| !('dataset' in target)
|
||||
|| !('mszEmbedUrl' in target.dataset))
|
||||
continue;
|
||||
|
||||
const cleanUrl = target.dataset.mszEmbedUrl.replace(/ /, '%20');
|
||||
if(cleanUrl.indexOf('https://') !== 0
|
||||
&& cleanUrl.indexOf('http://') !== 0
|
||||
&& cleanUrl.indexOf('//') !== 0) {
|
||||
target.textContent = target.dataset.mszEmbedUrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
$rc(target);
|
||||
target.appendChild($e({
|
||||
tag: 'i',
|
||||
attrs: {
|
||||
className: 'fas fa-2x fa-spinner fa-pulse',
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
lineHeight: '32px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
if(filtered.has(cleanUrl))
|
||||
filtered.get(cleanUrl).push(target);
|
||||
else
|
||||
filtered.set(cleanUrl, [target]);
|
||||
}
|
||||
|
||||
const replaceWithUrl = function(targets, url) {
|
||||
for(const target of targets) {
|
||||
let body = $e({
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
className: 'link',
|
||||
href: url,
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
},
|
||||
child: url
|
||||
});
|
||||
$ib(target, body);
|
||||
$r(target);
|
||||
}
|
||||
};
|
||||
|
||||
filtered.forEach(function(targets, url) {
|
||||
uiharu.lookupOne(url)
|
||||
.catch(ex => {
|
||||
replaceWithUrl(targets, url);
|
||||
console.error(ex);
|
||||
})
|
||||
.then(result => {
|
||||
const metadata = result.body();
|
||||
|
||||
if(metadata.error) {
|
||||
replaceWithUrl(targets, url);
|
||||
console.error(metadata.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if(metadata.title === undefined) {
|
||||
replaceWithUrl(targets, url);
|
||||
console.warn('Media is no longer available.');
|
||||
return;
|
||||
}
|
||||
|
||||
let phc = undefined,
|
||||
options = {
|
||||
onembed: console.log,
|
||||
};
|
||||
|
||||
if(metadata.type === 'youtube:video') {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'youtube';
|
||||
options.player = MszVideoEmbedYouTube;
|
||||
} else if(metadata.type === 'niconico:video') {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'nicovideo';
|
||||
options.player = MszVideoEmbedNicoNico;
|
||||
} else if(metadata.is_video) {
|
||||
phc = MszVideoEmbedPlaceholder;
|
||||
options.type = 'external';
|
||||
options.player = MszVideoEmbedPlayer;
|
||||
//options.frame = MszVideoEmbedFrame;
|
||||
options.nativeControls = true;
|
||||
options.autosize = false;
|
||||
options.maxWidth = 640;
|
||||
options.maxHeight = 360;
|
||||
} else if(metadata.is_audio) {
|
||||
phc = MszAudioEmbedPlaceholder;
|
||||
options.type = 'external';
|
||||
options.player = MszAudioEmbedPlayer;
|
||||
options.nativeControls = true;
|
||||
} else if(metadata.is_image) {
|
||||
phc = MszImageEmbed;
|
||||
options.type = 'external';
|
||||
}
|
||||
|
||||
if(phc === undefined)
|
||||
return;
|
||||
|
||||
for(const target of targets) {
|
||||
const placeholder = new phc(metadata, options, target);
|
||||
if(placeholder !== undefined)
|
||||
placeholder.replaceElement(target);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -1,4 +1,4 @@
|
|||
#include utils.js
|
||||
#include utility.js
|
||||
|
||||
const MszImageEmbed = function(metadata, options, target) {
|
||||
options = options || {};
|
|
@ -1,5 +1,5 @@
|
|||
#include utils.js
|
||||
#include rng.js
|
||||
#include utility.js
|
||||
#include uniqstr.js
|
||||
#include watcher.js
|
||||
|
||||
const MszVideoEmbedPlayerEvents = function() {
|
||||
|
@ -265,12 +265,13 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
|||
getHeight: function() { return height; },
|
||||
};
|
||||
|
||||
watchers.proxy(pub);
|
||||
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||
|
||||
if(shouldObserveResize)
|
||||
player.addEventListener('resize', function() { setSize(player.videoWidth, player.videoHeight); });
|
||||
|
||||
player.addEventListener('play', function() { watchers.call('play', pub); });
|
||||
player.addEventListener('play', function() { watchers.call('play'); });
|
||||
|
||||
const pPlay = function() { player.play(); };
|
||||
pub.play = pPlay;
|
||||
|
@ -280,7 +281,7 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
|||
|
||||
let stopCalled = false;
|
||||
player.addEventListener('pause', function() {
|
||||
watchers.call(stopCalled ? 'stop' : 'pause', pub);
|
||||
watchers.call(stopCalled ? 'stop' : 'pause');
|
||||
stopCalled = false;
|
||||
});
|
||||
|
||||
|
@ -301,9 +302,9 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
|||
player.addEventListener('volumechange', function() {
|
||||
if(lastMuteState !== player.muted) {
|
||||
lastMuteState = player.muted;
|
||||
watchers.call('mute', pub, [lastMuteState]);
|
||||
watchers.call('mute', lastMuteState);
|
||||
} else
|
||||
watchers.call('volume', pub, [player.volume]);
|
||||
watchers.call('volume', player.volume);
|
||||
});
|
||||
|
||||
const pSetMuted = function(state) { player.muted = state; };
|
||||
|
@ -319,21 +320,21 @@ const MszVideoEmbedPlayer = function(metadata, options) {
|
|||
pub.getPlaybackRate = pGetPlaybackRate;
|
||||
|
||||
player.addEventListener('ratechange', function() {
|
||||
watchers.call('rate', pub, [player.playbackRate]);
|
||||
watchers.call('rate', player.playbackRate);
|
||||
});
|
||||
|
||||
const pSetPlaybackRate = function(rate) { player.playbackRate = rate; };
|
||||
pub.setPlaybackRate = pSetPlaybackRate;
|
||||
|
||||
window.addEventListener('durationchange', function() {
|
||||
watchers.call('duration', pub, [player.duration]);
|
||||
watchers.call('duration', player.duration);
|
||||
});
|
||||
|
||||
const pGetDuration = function() { return player.duration; };
|
||||
pub.getDuration = pGetDuration;
|
||||
|
||||
window.addEventListener('timeupdate', function() {
|
||||
watchers.call('time', pub, [player.currentTime]);
|
||||
watchers.call('time', player.currentTime);
|
||||
});
|
||||
|
||||
const pGetTime = function() { return player.currentTime; };
|
||||
|
@ -410,7 +411,8 @@ const MszVideoEmbedYouTube = function(metadata, options) {
|
|||
getPlayerId: function() { return playerId; },
|
||||
};
|
||||
|
||||
watchers.proxy(pub);
|
||||
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||
|
||||
const postMessage = function(data) {
|
||||
player.contentWindow.postMessage(JSON.stringify(data), ytOrigin);
|
||||
|
@ -463,33 +465,33 @@ const MszVideoEmbedYouTube = function(metadata, options) {
|
|||
lastPlayerState = state;
|
||||
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
||||
lastPlayerStateEvent = eventName;
|
||||
watchers.call(eventName, pub);
|
||||
watchers.call(eventName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMuted = function(muted) {
|
||||
isMuted = muted;
|
||||
watchers.call('mute', pub, [isMuted]);
|
||||
watchers.call('mute', isMuted);
|
||||
};
|
||||
|
||||
const handleVolume = function(value) {
|
||||
volume = value / 100;
|
||||
watchers.call('volume', pub, [volume]);
|
||||
watchers.call('volume', volume);
|
||||
};
|
||||
|
||||
const handleRate = function(rate) {
|
||||
playbackRate = rate;
|
||||
watchers.call('rate', pub, [playbackRate]);
|
||||
watchers.call('rate', playbackRate);
|
||||
};
|
||||
|
||||
const handleDuration = function(time) {
|
||||
duration = time;
|
||||
watchers.call('duration', pub, [duration]);
|
||||
watchers.call('duration', duration);
|
||||
};
|
||||
|
||||
const handleTime = function(time) {
|
||||
currentTime = time;
|
||||
watchers.call('time', pub, [currentTime]);
|
||||
watchers.call('time', currentTime);
|
||||
};
|
||||
|
||||
const handlePresetRates = function(rates) {
|
||||
|
@ -610,7 +612,8 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
|
|||
getPlayerId: function() { return playerId; },
|
||||
};
|
||||
|
||||
watchers.proxy(pub);
|
||||
pub.watch = (name, handler) => watchers.watch(name, handler);
|
||||
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
|
||||
|
||||
const postMessage = function(name, data) {
|
||||
if(name === undefined)
|
||||
|
@ -660,28 +663,28 @@ const MszVideoEmbedNicoNico = function(metadata, options) {
|
|||
|
||||
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
|
||||
lastPlayerStateEvent = eventName;
|
||||
watchers.call(eventName, pub);
|
||||
watchers.call(eventName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMuted = function(muted) {
|
||||
isMuted = muted;
|
||||
watchers.call('mute', pub, [isMuted]);
|
||||
watchers.call('mute', isMuted);
|
||||
};
|
||||
|
||||
const handleVolume = function(value) {
|
||||
volume = value;
|
||||
watchers.call('volume', pub, [volume]);
|
||||
watchers.call('volume', volume);
|
||||
};
|
||||
|
||||
const handleDuration = function(time) {
|
||||
duration = time / 1000;
|
||||
watchers.call('duration', pub, [duration]);
|
||||
watchers.call('duration', duration);
|
||||
};
|
||||
|
||||
const handleTime = function(time) {
|
||||
currentTime = time / 1000;
|
||||
watchers.call('time', pub, [currentTime]);
|
||||
watchers.call('time', currentTime);
|
||||
};
|
||||
|
||||
const metadataHanders = {
|
|
@ -1,35 +1,49 @@
|
|||
#include utils.js
|
||||
#include utility.js
|
||||
|
||||
Misuzu.Events.Christmas2019 = function() {
|
||||
this.propName = propName = 'msz-christmas-' + (new Date).getFullYear().toString();
|
||||
const MszChristmas2019EventInfo = function() {
|
||||
return {
|
||||
isActive: () => {
|
||||
const d = new Date;
|
||||
return d.getMonth() === 11 && d.getDate() > 5 && d.getDate() < 27;
|
||||
},
|
||||
dispatch: () => {
|
||||
const impl = new MszChristmas2019Event;
|
||||
impl.dispatch();
|
||||
return impl;
|
||||
},
|
||||
};
|
||||
};
|
||||
Misuzu.Events.Christmas2019.prototype.changeColour = function() {
|
||||
var count = parseInt(localStorage.getItem(this.propName));
|
||||
document.body.style.setProperty('--header-accent-colour', (count++ % 2) ? 'green' : 'red');
|
||||
localStorage.setItem(this.propName, count.toString());
|
||||
};
|
||||
Misuzu.Events.Christmas2019.prototype.isActive = function() {
|
||||
var d = new Date;
|
||||
return d.getMonth() === 11 && d.getDate() > 5 && d.getDate() < 27;
|
||||
};
|
||||
Misuzu.Events.Christmas2019.prototype.dispatch = function() {
|
||||
var headerBg = $q('.header__background'),
|
||||
menuBgs = $qa('.header__desktop__submenu__background');
|
||||
|
||||
if(!localStorage.getItem(this.propName))
|
||||
localStorage.setItem(this.propName, '0');
|
||||
|
||||
if(headerBg)
|
||||
headerBg.style.transition = 'background-color .4s';
|
||||
|
||||
setTimeout(function() {
|
||||
if(headerBg)
|
||||
headerBg.style.transition = 'background-color 1s';
|
||||
|
||||
for(var i = 0; i < menuBgs.length; i++)
|
||||
menuBgs[i].style.transition = 'background-color 1s';
|
||||
}, 1000);
|
||||
|
||||
this.changeColour();
|
||||
setInterval(this.changeColour, 10000);
|
||||
|
||||
const MszChristmas2019Event = function() {
|
||||
const propName = 'msz-christmas-' + (new Date).getFullYear().toString();
|
||||
const headerBg = $q('.header__background');
|
||||
const menuBgs = Array.from($qa('.header__desktop__submenu__background'));
|
||||
|
||||
if(!localStorage.getItem(propName))
|
||||
localStorage.setItem(propName, '0');
|
||||
|
||||
const changeColour = () => {
|
||||
let count = parseInt(localStorage.getItem(propName));
|
||||
document.body.style.setProperty('--header-accent-colour', (count++ % 2) ? 'green' : 'red');
|
||||
localStorage.setItem(propName, count.toString());
|
||||
};
|
||||
|
||||
return {
|
||||
changeColour: changeColour,
|
||||
dispatch: () => {
|
||||
if(headerBg)
|
||||
headerBg.style.transition = 'background-color .4s';
|
||||
|
||||
setTimeout(() => {
|
||||
if(headerBg)
|
||||
headerBg.style.transition = 'background-color 1s';
|
||||
|
||||
for(const menuBg of menuBgs)
|
||||
menuBg.style.transition = 'background-color 1s';
|
||||
}, 1000);
|
||||
|
||||
changeColour();
|
||||
setInterval(changeColour, 10000);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
Misuzu.Events = {};
|
||||
const MszSeasonalEvents = function() {
|
||||
const events = [];
|
||||
|
||||
#include events/christmas2019.js
|
||||
|
||||
Misuzu.Events.getList = function() {
|
||||
return [
|
||||
new Misuzu.Events.Christmas2019,
|
||||
];
|
||||
};
|
||||
Misuzu.Events.dispatch = function() {
|
||||
var list = Misuzu.Events.getList();
|
||||
for(var i = 0; i < list.length; ++i)
|
||||
if(list[i].isActive())
|
||||
list[i].dispatch();
|
||||
return {
|
||||
add: eventInfo => {
|
||||
if(!events.includes(eventInfo))
|
||||
events.push(eventInfo);
|
||||
},
|
||||
dispatch: () => {
|
||||
for(const info of events)
|
||||
if(info.isActive())
|
||||
info.dispatch();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
37
assets/misuzu.js/ext/eeprom.js
Normal file
37
assets/misuzu.js/ext/eeprom.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include utility.js
|
||||
|
||||
const MszEEPROM = (() => {
|
||||
let eepromScript;
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if(eepromScript !== undefined) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof peepPath !== 'string') {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
const scriptElem = $e({
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
src: `${peepPath}/eeprom.js`,
|
||||
charset: 'utf-8',
|
||||
type: 'text/javascript',
|
||||
onerror: () => reject(),
|
||||
onload: () => {
|
||||
eepromScript = scriptElem;
|
||||
resolve(true);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(scriptElem);
|
||||
});
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -1,4 +1,4 @@
|
|||
const Sakuya = (function() {
|
||||
const MszSakuya = (function() {
|
||||
const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' });
|
||||
const divisions = [
|
||||
{ amount: 60, name: 'seconds' },
|
15
assets/misuzu.js/ext/uiharu.js
Normal file
15
assets/misuzu.js/ext/uiharu.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include utility.js
|
||||
|
||||
const MszUiharu = function(apiUrl) {
|
||||
const maxBatchSize = 4;
|
||||
const lookupOneUrl = apiUrl + '/metadata';
|
||||
|
||||
return {
|
||||
lookupOne: async targetUrl => {
|
||||
if(typeof targetUrl !== 'string')
|
||||
throw 'targetUrl must be a string';
|
||||
|
||||
return $x.post(lookupOneUrl, { type: 'json' }, targetUrl);
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,437 +0,0 @@
|
|||
#include forum/forum.js
|
||||
|
||||
Misuzu.Forum.Editor = {};
|
||||
Misuzu.Forum.Editor.allowWindowClose = false;
|
||||
Misuzu.Forum.Editor.init = function() {
|
||||
const postingForm = $q('.js-forum-posting');
|
||||
if(!postingForm)
|
||||
return;
|
||||
|
||||
const postingButtons = postingForm.querySelector('.js-forum-posting-buttons'),
|
||||
postingText = postingForm.querySelector('.js-forum-posting-text'),
|
||||
postingParser = postingForm.querySelector('.js-forum-posting-parser'),
|
||||
postingPreview = postingForm.querySelector('.js-forum-posting-preview'),
|
||||
postingMode = postingForm.querySelector('.js-forum-posting-mode'),
|
||||
previewButton = document.createElement('button'),
|
||||
bbcodeButtons = $q('.forum__post__actions--bbcode'),
|
||||
markdownButtons = $q('.forum__post__actions--markdown'),
|
||||
markupButtons = $qa('.forum__post__action--tag');
|
||||
|
||||
// Initialise EEPROM, code sucks ass but it's getting nuked soon again anyway
|
||||
if(typeof peepPath === 'string')
|
||||
document.body.appendChild($e({
|
||||
tag: 'script',
|
||||
attrs: {
|
||||
src: peepPath + '/eeprom.js',
|
||||
charset: 'utf-8',
|
||||
type: 'text/javascript',
|
||||
onload: function() {
|
||||
const eepromClient = new EEPROM(peepApp, peepPath + '/uploads', '');
|
||||
|
||||
const eepromHistory = $e({
|
||||
attrs: {
|
||||
className: 'eeprom-widget-history-items',
|
||||
},
|
||||
});
|
||||
|
||||
const eepromHandleFileUpload = function(file) {
|
||||
const uploadElemNameValue = $e({
|
||||
attrs: {
|
||||
className: 'eeprom-widget-file-name-value',
|
||||
title: file.name,
|
||||
},
|
||||
child: file.name,
|
||||
});
|
||||
|
||||
const uploadElemName = $e({
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
className: 'eeprom-widget-file-name',
|
||||
target: '_blank',
|
||||
},
|
||||
child: uploadElemNameValue,
|
||||
});
|
||||
|
||||
const uploadElemProgressText = $e({
|
||||
attrs: {
|
||||
className: 'eeprom-widget-file-progress',
|
||||
},
|
||||
child: 'Please wait...',
|
||||
});
|
||||
|
||||
const uploadElemProgressBarValue = $e({
|
||||
attrs: {
|
||||
className: 'eeprom-widget-file-bar-fill',
|
||||
style: {
|
||||
width: '0%',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const uploadElem = $e({
|
||||
attrs: {
|
||||
className: 'eeprom-widget-file',
|
||||
},
|
||||
child: [
|
||||
{
|
||||
attrs: {
|
||||
className: 'eeprom-widget-file-info',
|
||||
},
|
||||
child: [
|
||||
uploadElemName,
|
||||
uploadElemProgressText,
|
||||
],
|
||||
},
|
||||
{
|
||||
attrs: {
|
||||
className: 'eeprom-widget-file-bar',
|
||||
},
|
||||
child: uploadElemProgressBarValue,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if(eepromHistory.children.length > 0)
|
||||
$ib(eepromHistory.firstChild, uploadElem);
|
||||
else
|
||||
eepromHistory.appendChild(uploadElem);
|
||||
|
||||
const explodeUploadElem = function() {
|
||||
$r(uploadElem);
|
||||
};
|
||||
|
||||
const uploadTask = eepromClient.createUpload(file);
|
||||
|
||||
uploadTask.onProgress = function(progressInfo) {
|
||||
const progressValue = progressInfo.progress.toString() + '%';
|
||||
uploadElemProgressBarValue.style.width = progressValue;
|
||||
uploadElemProgressText.textContent = progressValue + ' (' + (progressInfo.total - progressInfo.loaded).toString() + ' bytes remaining)';
|
||||
};
|
||||
|
||||
uploadTask.onFailure = function(errorInfo) {
|
||||
if(!errorInfo.userAborted) {
|
||||
let 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 {
|
||||
const _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;
|
||||
}
|
||||
|
||||
uploadElem.classList.add('eeprom-widget-file-fail');
|
||||
uploadElemProgressText.textContent = errorText;
|
||||
Misuzu.showMessageBox(errorText, 'Upload Error');
|
||||
}
|
||||
};
|
||||
|
||||
uploadTask.onComplete = function(fileInfo) {
|
||||
uploadElem.classList.add('eeprom-widget-file-done');
|
||||
uploadElemName.href = fileInfo.url;
|
||||
uploadElemProgressText.textContent = '';
|
||||
|
||||
const insertTheLinkIntoTheBoxEx2 = function() {
|
||||
const parserMode = parseInt(postingParser.value);
|
||||
let insertText = location.protocol + fileInfo.url;
|
||||
|
||||
if(parserMode == 1) { // bbcode
|
||||
if(fileInfo.isImage())
|
||||
insertText = '[img]' + fileInfo.url + '[/img]';
|
||||
else if(fileInfo.isAudio())
|
||||
insertText = '[audio]' + fileInfo.url + '[/audio]';
|
||||
else if(fileInfo.isVideo())
|
||||
insertText = '[video]' + fileInfo.url + '[/video]';
|
||||
} else if(parserMode == 2) { // markdown
|
||||
if(fileInfo.isMedia())
|
||||
insertText = '![](' + fileInfo.url + ')';
|
||||
}
|
||||
|
||||
$insertTags(postingText, insertText, '');
|
||||
postingText.value = postingText.value.trim();
|
||||
};
|
||||
|
||||
uploadElemProgressText.appendChild($e({
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
href: 'javascript:void(0);',
|
||||
onclick: function() { insertTheLinkIntoTheBoxEx2(); },
|
||||
},
|
||||
child: 'Insert',
|
||||
}));
|
||||
uploadElemProgressText.appendChild($t(' '));
|
||||
uploadElemProgressText.appendChild($e({
|
||||
tag: 'a',
|
||||
attrs: {
|
||||
href: 'javascript:void(0);',
|
||||
onclick: function() {
|
||||
eepromClient.deleteUpload(fileInfo).start();
|
||||
explodeUploadElem();
|
||||
},
|
||||
},
|
||||
child: 'Delete',
|
||||
}));
|
||||
|
||||
insertTheLinkIntoTheBoxEx2();
|
||||
};
|
||||
|
||||
uploadTask.start();
|
||||
};
|
||||
|
||||
const eepromFormInput = $e({
|
||||
tag: 'input',
|
||||
attrs: {
|
||||
type: 'file',
|
||||
multiple: 'multiple',
|
||||
className: 'eeprom-widget-form-input',
|
||||
onchange: function(ev) {
|
||||
const files = this.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
this.value = '';
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const eepromForm = $e({
|
||||
tag: 'label',
|
||||
attrs: {
|
||||
className: 'eeprom-widget-form',
|
||||
},
|
||||
child: [
|
||||
eepromFormInput,
|
||||
{
|
||||
attrs: {
|
||||
className: 'eeprom-widget-form-text',
|
||||
},
|
||||
child: 'Select Files...',
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
const eepromWidget = $e({
|
||||
attrs: {
|
||||
className: 'eeprom-widget',
|
||||
},
|
||||
child: [
|
||||
eepromForm,
|
||||
{
|
||||
attrs: {
|
||||
className: 'eeprom-widget-history',
|
||||
},
|
||||
child: eepromHistory,
|
||||
},
|
||||
],
|
||||
});
|
||||
postingForm.appendChild(eepromWidget);
|
||||
|
||||
postingText.addEventListener('paste', function(ev) {
|
||||
if(ev.clipboardData && ev.clipboardData.files.length > 0) {
|
||||
ev.preventDefault();
|
||||
|
||||
const files = ev.clipboardData.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('dragenter', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('dragover', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('dragleave', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('drop', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if(ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
||||
const files = ev.dataTransfer.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
}
|
||||
});
|
||||
},
|
||||
onerror: function(ev) {
|
||||
console.error('Failed to initialise EEPROM: ', ev);
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// hack: don't prompt user when hitting submit, really need to make this not stupid.
|
||||
postingButtons.firstElementChild.addEventListener('click', function() {
|
||||
Misuzu.Forum.Editor.allowWindowClose = true;
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', function(ev) {
|
||||
if(!Misuzu.Forum.Editor.allowWindowClose && postingText.value.length > 0) {
|
||||
ev.preventDefault();
|
||||
ev.returnValue = '';
|
||||
}
|
||||
});
|
||||
|
||||
for(var i = 0; i < markupButtons.length; ++i)
|
||||
(function(currentBtn) {
|
||||
currentBtn.addEventListener('click', function(ev) {
|
||||
$insertTags(postingText, currentBtn.dataset.tagOpen, currentBtn.dataset.tagClose);
|
||||
});
|
||||
})(markupButtons[i]);
|
||||
|
||||
Misuzu.Forum.Editor.switchButtons(parseInt(postingParser.value));
|
||||
|
||||
var lastPostText = '',
|
||||
lastPostParser = null;
|
||||
|
||||
postingParser.addEventListener('change', function() {
|
||||
var postParser = parseInt(postingParser.value);
|
||||
Misuzu.Forum.Editor.switchButtons(postParser);
|
||||
|
||||
if(postingPreview.hasAttribute('hidden'))
|
||||
return;
|
||||
|
||||
// dunno if this would even be possible, but ech
|
||||
if(postParser === lastPostParser)
|
||||
return;
|
||||
|
||||
postingParser.setAttribute('disabled', 'disabled');
|
||||
previewButton.setAttribute('disabled', 'disabled');
|
||||
previewButton.classList.add('input__button--busy');
|
||||
|
||||
Misuzu.Forum.Editor.renderPreview(postParser, lastPostText, function(success, text) {
|
||||
if(!success) {
|
||||
Misuzu.showMessageBox(text);
|
||||
return;
|
||||
}
|
||||
|
||||
postingPreview.classList[postParser == 2 ? 'add' : 'remove']('markdown');
|
||||
|
||||
lastPostParser = postParser;
|
||||
postingPreview.innerHTML = text;
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
|
||||
previewButton.removeAttribute('disabled');
|
||||
postingParser.removeAttribute('disabled');
|
||||
previewButton.classList.remove('input__button--busy');
|
||||
});
|
||||
});
|
||||
|
||||
previewButton.className = 'input__button';
|
||||
previewButton.textContent = 'Preview';
|
||||
previewButton.type = 'button';
|
||||
previewButton.value = 'preview';
|
||||
previewButton.addEventListener('click', function() {
|
||||
if(previewButton.value === 'back') {
|
||||
postingPreview.setAttribute('hidden', 'hidden');
|
||||
postingText.removeAttribute('hidden');
|
||||
previewButton.value = 'preview';
|
||||
previewButton.textContent = 'Preview';
|
||||
postingMode.textContent = postingMode.dataset.original;
|
||||
postingMode.dataset.original = null;
|
||||
} else {
|
||||
var postText = postingText.value,
|
||||
postParser = parseInt(postingParser.value);
|
||||
|
||||
if(lastPostText === postText && lastPostParser === postParser) {
|
||||
postingPreview.removeAttribute('hidden');
|
||||
postingText.setAttribute('hidden', 'hidden');
|
||||
previewButton.value = 'back';
|
||||
previewButton.textContent = 'Edit';
|
||||
postingMode.dataset.original = postingMode.textContent;
|
||||
postingMode.textContent = 'Previewing';
|
||||
return;
|
||||
}
|
||||
|
||||
postingParser.setAttribute('disabled', 'disabled');
|
||||
previewButton.setAttribute('disabled', 'disabled');
|
||||
previewButton.classList.add('input__button--busy');
|
||||
|
||||
Misuzu.Forum.Editor.renderPreview(postParser, postText, function(success, text) {
|
||||
if(!success) {
|
||||
Misuzu.showMessageBox(text);
|
||||
return;
|
||||
}
|
||||
|
||||
postingPreview.classList[postParser == 2 ? 'add' : 'remove']('markdown');
|
||||
|
||||
lastPostText = postText;
|
||||
lastPostParser = postParser;
|
||||
postingPreview.innerHTML = text;
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
|
||||
postingPreview.removeAttribute('hidden');
|
||||
postingText.setAttribute('hidden', 'hidden');
|
||||
previewButton.value = 'back';
|
||||
previewButton.textContent = 'Back';
|
||||
previewButton.removeAttribute('disabled');
|
||||
postingParser.removeAttribute('disabled');
|
||||
previewButton.classList.remove('input__button--busy');
|
||||
postingMode.dataset.original = postingMode.textContent;
|
||||
postingMode.textContent = 'Previewing';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
postingButtons.insertBefore(previewButton, postingButtons.firstChild);
|
||||
};
|
||||
Misuzu.Forum.Editor.switchButtons = function(parser) {
|
||||
var bbcodeButtons = $q('.forum__post__actions--bbcode'),
|
||||
markdownButtons = $q('.forum__post__actions--markdown');
|
||||
|
||||
bbcodeButtons.hidden = parser != 1;
|
||||
markdownButtons.hidden = parser != 2;
|
||||
};
|
||||
Misuzu.Forum.Editor.renderPreview = function(parser, text, callback) {
|
||||
if(!callback)
|
||||
return;
|
||||
parser = parseInt(parser);
|
||||
text = text || '';
|
||||
|
||||
var xhr = new XMLHttpRequest,
|
||||
formData = new FormData;
|
||||
|
||||
formData.append('post[mode]', 'preview');
|
||||
formData.append('post[text]', text);
|
||||
formData.append('post[parser]', parser.toString());
|
||||
|
||||
xhr.addEventListener('readystatechange', function() {
|
||||
if(xhr.readyState !== XMLHttpRequest.DONE)
|
||||
return;
|
||||
if(xhr.status === 200)
|
||||
callback(true, xhr.response);
|
||||
else
|
||||
callback(false, 'Failed to render preview.');
|
||||
});
|
||||
// need to figure out a url registry system again, current one is too much overhead so lets just do this for now
|
||||
xhr.open('POST', '/forum/posting.php');
|
||||
xhr.withCredentials = true;
|
||||
xhr.send(formData);
|
||||
};
|
319
assets/misuzu.js/forum/editor.jsx
Normal file
319
assets/misuzu.js/forum/editor.jsx
Normal file
|
@ -0,0 +1,319 @@
|
|||
#include msgbox.jsx
|
||||
#include utility.js
|
||||
#include ext/eeprom.js
|
||||
|
||||
let MszForumEditorAllowClose = false;
|
||||
|
||||
const MszForumEditor = function(form) {
|
||||
if(!(form instanceof Element))
|
||||
throw 'form must be an instance of element';
|
||||
|
||||
const buttonsElem = form.querySelector('.js-forum-posting-buttons'),
|
||||
textElem = form.querySelector('.js-forum-posting-text'),
|
||||
parserElem = form.querySelector('.js-forum-posting-parser'),
|
||||
previewElem = form.querySelector('.js-forum-posting-preview'),
|
||||
modeElem = form.querySelector('.js-forum-posting-mode'),
|
||||
markupBtns = form.querySelectorAll('.js-forum-posting-markup');
|
||||
|
||||
const bbBtns = $q('.forum__post__actions--bbcode'),
|
||||
mdBtns = $q('.forum__post__actions--markdown');
|
||||
|
||||
let lastPostText = '',
|
||||
lastPostParser;
|
||||
|
||||
MszEEPROM.init()
|
||||
.catch(() => console.error('Failed to initialise EEPROM'))
|
||||
.then(() => {
|
||||
const eepromClient = new EEPROM(peepApp, `${peepPath}/uploads`, '');
|
||||
const eepromHistory = <div class="eeprom-widget-history-items"/>;
|
||||
|
||||
const eepromHandleFileUpload = file => {
|
||||
const uploadElemNameValue = <div class="eeprom-widget-file-name-value" title={file.name}>{file.name}</div>;
|
||||
const uploadElemName = <a class="eeprom-widget-file-name" target="_blank">{uploadElemNameValue}</a>;
|
||||
const uploadElemProgressText = <div class="eeprom-widget-file-progress">Please wait...</div>;
|
||||
const uploadElemProgressBarValue = <div class="eeprom-widget-file-bar-fill" style={{ width: '0%' }}/>;
|
||||
const uploadElem = <div class="eeprom-widget-file">
|
||||
<div class="eeprom-widget-file-info">
|
||||
{uploadElemName}
|
||||
{uploadElemProgressText}
|
||||
</div>
|
||||
<div class="eeprom-widget-file-bar">
|
||||
{uploadElemProgressBarValue}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
if(eepromHistory.children.length > 0)
|
||||
$ib(eepromHistory.firstChild, uploadElem);
|
||||
else
|
||||
eepromHistory.appendChild(uploadElem);
|
||||
|
||||
const explodeUploadElem = () => $r(uploadElem);
|
||||
const uploadTask = eepromClient.createUpload(file);
|
||||
|
||||
uploadTask.onProgress = function(progressInfo) {
|
||||
const progressValue = `${progressInfo.progress}%`;
|
||||
uploadElemProgressBarValue.style.width = progressValue;
|
||||
uploadElemProgressText.textContent = `${progressValue} (${progressInfo.total - progressInfo.loaded} bytes remaining)`;
|
||||
};
|
||||
|
||||
uploadTask.onFailure = function(errorInfo) {
|
||||
if(!errorInfo.userAborted) {
|
||||
let 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 {
|
||||
const types = ['bytes', 'KB', 'MB', 'GB', 'TB'],
|
||||
typeIndex = parseInt(Math.floor(Math.log(errorInfo.maxSize) / Math.log(1024))),
|
||||
number = Math.round(errorInfo.maxSize / Math.pow(1024, _i), 2);
|
||||
|
||||
errorText = `Upload may not be larger than ${number} ${types[typeIndex]}.`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
uploadElem.classList.add('eeprom-widget-file-fail');
|
||||
uploadElemProgressText.textContent = errorText;
|
||||
MszShowMessageBox(errorText, 'Upload Error');
|
||||
}
|
||||
};
|
||||
|
||||
uploadTask.onComplete = function(fileInfo) {
|
||||
uploadElem.classList.add('eeprom-widget-file-done');
|
||||
uploadElemName.href = fileInfo.url;
|
||||
uploadElemProgressText.textContent = '';
|
||||
|
||||
const insertTheLinkIntoTheBoxEx2 = function() {
|
||||
const parserMode = parseInt(parserElem.value);
|
||||
let insertText = location.protocol + fileInfo.url;
|
||||
|
||||
if(parserMode == 1) { // bbcode
|
||||
if(fileInfo.isImage())
|
||||
insertText = `[img]${fileInfo.url}[/img]`;
|
||||
else if(fileInfo.isAudio())
|
||||
insertText = `[audio]${fileInfo.url}[/audio]`;
|
||||
else if(fileInfo.isVideo())
|
||||
insertText = `[video]${fileInfo.url}[/video]`;
|
||||
} else if(parserMode == 2) { // markdown
|
||||
if(fileInfo.isMedia())
|
||||
insertText = `![](${fileInfo.url})`;
|
||||
}
|
||||
|
||||
$insertTags(textElem, insertText, '');
|
||||
textElem.value = textElem.value.trim();
|
||||
};
|
||||
|
||||
uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => insertTheLinkIntoTheBoxEx2()}>Insert</a>);
|
||||
uploadElemProgressText.appendChild($t(' '));
|
||||
uploadElemProgressText.appendChild(<a href="javascript:void(0)" onclick={() => {
|
||||
eepromClient.deleteUpload(fileInfo).start();
|
||||
explodeUploadElem();
|
||||
}}>Delete</a>);
|
||||
|
||||
insertTheLinkIntoTheBoxEx2();
|
||||
};
|
||||
|
||||
uploadTask.start();
|
||||
};
|
||||
|
||||
const eepromFormInput = <input type="file" multiple={true} class="eeprom-widget-form-input"
|
||||
onchange={() => {
|
||||
const files = eepromFormInput.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
eepromFormInput.value = '';
|
||||
}}/>;
|
||||
|
||||
const eepromForm = <label class="eeprom-widget-form">
|
||||
{eepromFormInput}
|
||||
<div class="eeprom-widget-form-text">
|
||||
Select Files...
|
||||
</div>
|
||||
</label>;
|
||||
|
||||
const eepromWidget = <div class="eeprom-widget">
|
||||
{eepromForm}
|
||||
<div class="eeprom-widget-history">
|
||||
{eepromHistory}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
form.appendChild(eepromWidget);
|
||||
|
||||
textElem.addEventListener('paste', ev => {
|
||||
if(ev.clipboardData && ev.clipboardData.files.length > 0) {
|
||||
ev.preventDefault();
|
||||
|
||||
const files = ev.clipboardData.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('dragenter', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('dragover', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('dragleave', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
document.body.addEventListener('drop', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if(ev.dataTransfer && ev.dataTransfer.files.length > 0) {
|
||||
const files = ev.dataTransfer.files;
|
||||
for(const file of files)
|
||||
eepromHandleFileUpload(file);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// hack: don't prompt user when hitting submit, really need to make this not stupid.
|
||||
buttonsElem.firstChild.addEventListener('click', () => MszForumEditorAllowClose = true);
|
||||
|
||||
window.addEventListener('beforeunload', function(ev) {
|
||||
if(!MszForumEditorAllowClose && textElem.value.length > 0) {
|
||||
ev.preventDefault();
|
||||
ev.returnValue = '';
|
||||
}
|
||||
});
|
||||
|
||||
for(const button of markupBtns)
|
||||
button.addEventListener('click', () => $insertTags(textElem, button.dataset.tagOpen, button.dataset.tagClose));
|
||||
|
||||
const switchButtons = parser => {
|
||||
parser = parseInt(parser);
|
||||
bbBtns.hidden = parser !== 1;
|
||||
mdBtns.hidden = parser !== 2;
|
||||
};
|
||||
|
||||
const renderPreview = async (parser, text) => {
|
||||
if(typeof text !== 'string')
|
||||
return '';
|
||||
|
||||
const formData = new FormData;
|
||||
formData.append('post[mode]', 'preview');
|
||||
formData.append('post[text]', text);
|
||||
formData.append('post[parser]', parseInt(parser));
|
||||
|
||||
const result = await $x.post('/forum/posting.php', { authed: true }, formData);
|
||||
|
||||
return result.body();
|
||||
};
|
||||
|
||||
const previewBtn = <button class="input__button" type="button" value="preview">Preview</button>;
|
||||
previewBtn.addEventListener('click', function() {
|
||||
if(previewBtn.value === 'back') {
|
||||
previewElem.setAttribute('hidden', 'hidden');
|
||||
textElem.removeAttribute('hidden');
|
||||
previewBtn.value = 'preview';
|
||||
previewBtn.textContent = 'Preview';
|
||||
modeElem.textContent = modeElem.dataset.original;
|
||||
modeElem.dataset.original = null;
|
||||
} else {
|
||||
const postText = textElem.value,
|
||||
postParser = parseInt(parserElem.value);
|
||||
|
||||
if(lastPostText === postText && lastPostParser === postParser) {
|
||||
previewElem.removeAttribute('hidden');
|
||||
textElem.setAttribute('hidden', 'hidden');
|
||||
previewBtn.value = 'back';
|
||||
previewBtn.textContent = 'Edit';
|
||||
modeElem.dataset.original = modeElem.textContent;
|
||||
modeElem.textContent = 'Previewing';
|
||||
return;
|
||||
}
|
||||
|
||||
parserElem.setAttribute('disabled', 'disabled');
|
||||
previewBtn.setAttribute('disabled', 'disabled');
|
||||
previewBtn.classList.add('input__button--busy');
|
||||
|
||||
renderPreview(postParser, postText)
|
||||
.catch(() => {
|
||||
previewElem.innerHTML = '';
|
||||
MszShowMessageBox('Failed to render preview.');
|
||||
})
|
||||
.then(body => {
|
||||
previewElem.classList.toggle('markdown', postParser === 2);
|
||||
|
||||
lastPostText = postText;
|
||||
lastPostParser = postParser;
|
||||
previewElem.innerHTML = body;
|
||||
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
|
||||
previewElem.removeAttribute('hidden');
|
||||
textElem.setAttribute('hidden', 'hidden');
|
||||
previewBtn.value = 'back';
|
||||
previewBtn.textContent = 'Back';
|
||||
previewBtn.removeAttribute('disabled');
|
||||
parserElem.removeAttribute('disabled');
|
||||
previewBtn.classList.remove('input__button--busy');
|
||||
modeElem.dataset.original = modeElem.textContent;
|
||||
modeElem.textContent = 'Previewing';
|
||||
});
|
||||
}
|
||||
});
|
||||
buttonsElem.insertBefore(previewBtn, buttonsElem.firstChild);
|
||||
|
||||
switchButtons(parserElem.value);
|
||||
|
||||
parserElem.addEventListener('change', () => {
|
||||
const postParser = parseInt(parserElem.value);
|
||||
switchButtons(postParser);
|
||||
|
||||
if(previewElem.hasAttribute('hidden'))
|
||||
return;
|
||||
|
||||
// dunno if this would even be possible, but ech
|
||||
if(postParser === lastPostParser)
|
||||
return;
|
||||
|
||||
parserElem.setAttribute('disabled', 'disabled');
|
||||
previewBtn.setAttribute('disabled', 'disabled');
|
||||
previewBtn.classList.add('input__button--busy');
|
||||
|
||||
renderPreview(postParser, lastPostText)
|
||||
.catch(() => {
|
||||
previewElem.innerHTML = '';
|
||||
MszShowMessageBox('Failed to render preview.');
|
||||
})
|
||||
.then(body => {
|
||||
previewElem.classList.add('markdown', postParser === 2);
|
||||
lastPostParser = postParser;
|
||||
previewElem.innerHTML = body;
|
||||
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
|
||||
previewBtn.removeAttribute('disabled');
|
||||
parserElem.removeAttribute('disabled');
|
||||
previewBtn.classList.remove('input__button--busy');
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
Misuzu.Forum = {};
|
|
@ -1,135 +1,81 @@
|
|||
#include sakuya.js
|
||||
|
||||
var Misuzu = function() {
|
||||
Sakuya.trackElements($qa('time'));
|
||||
hljs.initHighlighting();
|
||||
|
||||
MszEmbed.init(location.protocol + '//uiharu.' + location.host);
|
||||
|
||||
Misuzu.initQuickSubmit(); // only used by the forum posting form
|
||||
Misuzu.Forum.Editor.init();
|
||||
Misuzu.Events.dispatch();
|
||||
Misuzu.initLoginPage();
|
||||
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
};
|
||||
|
||||
#include utils.js
|
||||
#include embed.js
|
||||
#include forum/editor.js
|
||||
#include utility.js
|
||||
#include embed/embed.js
|
||||
#include events/christmas2019.js
|
||||
#include events/events.js
|
||||
#include ext/sakuya.js
|
||||
#include forum/editor.jsx
|
||||
|
||||
Misuzu.showMessageBox = function(text, title, buttons) {
|
||||
if($q('.messagebox'))
|
||||
return false;
|
||||
(async () => {
|
||||
const initLoginPage = async () => {
|
||||
const forms = Array.from($qa('.js-login-form'));
|
||||
if(forms.length < 1)
|
||||
return;
|
||||
|
||||
text = text || '';
|
||||
title = title || '';
|
||||
buttons = buttons || [];
|
||||
|
||||
var element = document.createElement('div');
|
||||
element.className = 'messagebox';
|
||||
|
||||
var container = element.appendChild(document.createElement('div'));
|
||||
container.className = 'container messagebox__container';
|
||||
|
||||
var titleElement = container.appendChild(document.createElement('div')),
|
||||
titleBackground = titleElement.appendChild(document.createElement('div')),
|
||||
titleText = titleElement.appendChild(document.createElement('div'));
|
||||
|
||||
titleElement.className = 'container__title';
|
||||
titleBackground.className = 'container__title__background';
|
||||
titleText.className = 'container__title__text';
|
||||
titleText.textContent = title || 'Information';
|
||||
|
||||
var textElement = container.appendChild(document.createElement('div'));
|
||||
textElement.className = 'container__content';
|
||||
textElement.textContent = text;
|
||||
|
||||
var buttonsContainer = container.appendChild(document.createElement('div'));
|
||||
buttonsContainer.className = 'messagebox__buttons';
|
||||
|
||||
var firstButton = null;
|
||||
|
||||
if(buttons.length < 1) {
|
||||
firstButton = buttonsContainer.appendChild(document.createElement('button'));
|
||||
firstButton.className = 'input__button';
|
||||
firstButton.textContent = 'OK';
|
||||
firstButton.addEventListener('click', function() { element.remove(); });
|
||||
} else {
|
||||
for(var i = 0; i < buttons.length; i++) {
|
||||
var button = buttonsContainer.appendChild(document.createElement('button'));
|
||||
button.className = 'input__button';
|
||||
button.textContent = buttons[i].text;
|
||||
button.addEventListener('click', function() {
|
||||
element.remove();
|
||||
buttons[i].callback();
|
||||
});
|
||||
|
||||
if(firstButton === null)
|
||||
firstButton = button;
|
||||
}
|
||||
}
|
||||
|
||||
document.body.appendChild(element);
|
||||
firstButton.focus();
|
||||
return true;
|
||||
};
|
||||
Misuzu.initLoginPage = function() {
|
||||
var updateForm = function(avatarElem, usernameElem) {
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.addEventListener('readystatechange', function() {
|
||||
if(xhr.readyState !== 4)
|
||||
const updateForm = async (avatar, userName) => {
|
||||
if(!(avatar instanceof Element) || !(userName instanceof Element))
|
||||
return;
|
||||
|
||||
var json = JSON.parse(xhr.responseText);
|
||||
if(!json)
|
||||
return;
|
||||
const result = (await $x.get(`/auth/login.php?resolve=1&name=${encodeURIComponent(userName.value)}`, { type: 'json' })).body();
|
||||
|
||||
if(json.name)
|
||||
usernameElem.value = json.name;
|
||||
avatarElem.src = json.avatar;
|
||||
});
|
||||
// need to figure out a url registry system again, current one is too much overhead so lets just do this for now
|
||||
xhr.open('GET', '/auth/login.php?resolve=1&name=' + encodeURIComponent(usernameElem.value));
|
||||
xhr.send();
|
||||
};
|
||||
avatar.src = result.avatar;
|
||||
if(result.name.length > 0)
|
||||
userName.value = result.name;
|
||||
};
|
||||
|
||||
var loginForms = $c('js-login-form');
|
||||
for(const form of forms) {
|
||||
const avatar = form.querySelector('.js-login-avatar');
|
||||
const userName = form.querySelector('.js-login-username');
|
||||
let timeOut;
|
||||
|
||||
for(var i = 0; i < loginForms.length; ++i)
|
||||
(function(form) {
|
||||
var loginTimeOut = 0,
|
||||
loginAvatar = form.querySelector('.js-login-avatar'),
|
||||
loginUsername = form.querySelector('.js-login-username');
|
||||
await updateForm(avatar, userName);
|
||||
|
||||
updateForm(loginAvatar, loginUsername);
|
||||
loginUsername.addEventListener('keyup', function() {
|
||||
if(loginTimeOut)
|
||||
userName.addEventListener('input', function() {
|
||||
if(timeOut !== undefined)
|
||||
return;
|
||||
loginTimeOut = setTimeout(function() {
|
||||
updateForm(loginAvatar, loginUsername);
|
||||
clearTimeout(loginTimeOut);
|
||||
loginTimeOut = 0;
|
||||
|
||||
timeOut = setTimeout(() => {
|
||||
updateForm(avatar, userName)
|
||||
.finally(() => {
|
||||
clearTimeout(timeOut);
|
||||
timeOut = undefined;
|
||||
});
|
||||
}, 750);
|
||||
});
|
||||
})(loginForms[i]);
|
||||
};
|
||||
Misuzu.initQuickSubmit = function() {
|
||||
var ctrlSubmit = Array.from($qa('.js-quick-submit, .js-ctrl-enter-submit'));
|
||||
if(!ctrlSubmit)
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for(var i = 0; i < ctrlSubmit.length; ++i)
|
||||
ctrlSubmit[i].addEventListener('keydown', function(ev) {
|
||||
if((ev.code === 'Enter' || ev.code === 'NumpadEnter') // i hate this fucking language so much
|
||||
&& ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) {
|
||||
// hack: prevent forum editor from screaming when using this keycombo
|
||||
// can probably be done in a less stupid manner
|
||||
Misuzu.Forum.Editor.allowWindowClose = true;
|
||||
const initQuickSubmit = () => {
|
||||
const elems = Array.from($qa('.js-quick-submit, .js-ctrl-enter-submit'));
|
||||
if(elems.length < 1)
|
||||
return;
|
||||
|
||||
this.form.submit();
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
for(const elem of elems)
|
||||
elem.addEventListener('keydown', ev => {
|
||||
if((ev.code === 'Enter' || ev.code === 'NumpadEnter') && ev.ctrlKey && !ev.altKey && !ev.shiftKey && !ev.metaKey) {
|
||||
// hack: prevent forum editor from screaming when using this keycombo
|
||||
// can probably be done in a less stupid manner
|
||||
MszForumEditorAllowClose = true;
|
||||
|
||||
elem.submit();
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MszSakuya.trackElements($qa('time'));
|
||||
hljs.highlightAll();
|
||||
|
||||
MszEmbed.init(`${location.protocol}//uiharu.${location.host}`);
|
||||
|
||||
// only used by the forum posting form
|
||||
initQuickSubmit();
|
||||
MszForumEditor($q('.js-forum-posting'));
|
||||
|
||||
const events = new MszSeasonalEvents;
|
||||
events.add(new MszChristmas2019EventInfo);
|
||||
events.dispatch();
|
||||
|
||||
await initLoginPage();
|
||||
|
||||
MszEmbed.handle($qa('.js-msz-embed-media'));
|
||||
})();
|
||||
|
|
49
assets/misuzu.js/msgbox.jsx
Normal file
49
assets/misuzu.js/msgbox.jsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include utility.js
|
||||
|
||||
const MszShowMessageBox = async (text, title, buttons, target) => {
|
||||
if(typeof text !== 'string')
|
||||
throw 'text must be a string';
|
||||
if(!(target instanceof Element))
|
||||
target = document.body;
|
||||
|
||||
if(target.querySelector('.messagebox'))
|
||||
return false;
|
||||
|
||||
if(typeof title !== 'string')
|
||||
title = 'Information';
|
||||
if(!Array.isArray(buttons))
|
||||
buttons = [];
|
||||
|
||||
let buttonsElem;
|
||||
const html = <div class="messagebox">
|
||||
<div class="container messagebox__container">
|
||||
<div class="container__title">
|
||||
<div class="container__title__background"/>
|
||||
<div class="container__title__text">{title}</div>
|
||||
</div>
|
||||
<div class="container__content">{text}</div>
|
||||
{buttonsElem = <div class="messagebox__buttons"/>}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
let firstButton;
|
||||
if(buttons.length < 1) {
|
||||
firstButton = <button class="input__button" onclick={() => html.remove()}>OK</button>;
|
||||
buttonsElem.appendChild(firstButton);
|
||||
} else {
|
||||
for(const button of buttons) {
|
||||
const buttonElem = <button class="input__button" onclick={() => { html.remove(); if(typeof button === 'function') button.callback(); }}>
|
||||
{button.text}
|
||||
</button>;
|
||||
buttonsElem.appendChild(buttonElem);
|
||||
|
||||
if(firstButton === undefined)
|
||||
firstButton = buttonElem;
|
||||
}
|
||||
}
|
||||
|
||||
target.appendChild(html);
|
||||
firstButton.focus();
|
||||
|
||||
return true;
|
||||
};
|
|
@ -1,58 +0,0 @@
|
|||
const Uiharu = function(apiUrl) {
|
||||
const maxBatchSize = 4;
|
||||
const lookupOneUrl = apiUrl + '/metadata',
|
||||
lookupManyUrl = apiUrl + '/metadata/batch';
|
||||
|
||||
const lookupManyInternal = function(targetUrls, callback) {
|
||||
const formData = new FormData;
|
||||
|
||||
for(const url of targetUrls)
|
||||
formData.append('url[]', url);
|
||||
|
||||
const xhr = new XMLHttpRequest;
|
||||
xhr.addEventListener('load', function() {
|
||||
callback(JSON.parse(xhr.responseText));
|
||||
});
|
||||
xhr.addEventListener('error', function(ev) {
|
||||
callback({ status: xhr.status, error: 'xhr', details: ev });
|
||||
});
|
||||
xhr.open('POST', lookupManyUrl);
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
return {
|
||||
lookupOne: function(targetUrl, callback) {
|
||||
if(typeof callback !== 'function')
|
||||
throw 'callback is missing';
|
||||
targetUrl = (targetUrl || '').toString();
|
||||
if(targetUrl.length < 1)
|
||||
return;
|
||||
|
||||
const xhr = new XMLHttpRequest;
|
||||
xhr.addEventListener('load', function() {
|
||||
callback(JSON.parse(xhr.responseText));
|
||||
});
|
||||
xhr.addEventListener('error', function() {
|
||||
callback({ status: xhr.status, error: 'xhr', details: ex });
|
||||
});
|
||||
xhr.open('POST', lookupOneUrl);
|
||||
xhr.send(targetUrl);
|
||||
},
|
||||
lookupMany: function(targetUrls, callback) {
|
||||
if(!Array.isArray(targetUrls))
|
||||
throw 'targetUrls must be an array of urls';
|
||||
if(typeof callback !== 'function')
|
||||
throw 'callback is missing';
|
||||
if(targetUrls < 1)
|
||||
return;
|
||||
|
||||
if(targetUrls.length <= maxBatchSize) {
|
||||
lookupManyInternal(targetUrls, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
for(let i = 0; i < targetUrls.length; i += maxBatchSize)
|
||||
lookupManyInternal(targetUrls.slice(i, i + maxBatchSize), callback);
|
||||
},
|
||||
};
|
||||
};
|
|
@ -13,6 +13,10 @@ const $ri = function(name) {
|
|||
$r($i(name));
|
||||
};
|
||||
|
||||
const $rq = function(query) {
|
||||
$r($q(query));
|
||||
};
|
||||
|
||||
const $ib = function(ref, elem) {
|
||||
ref.parentNode.insertBefore(elem, ref);
|
||||
};
|
||||
|
@ -158,17 +162,125 @@ const $as = function(array) {
|
|||
}
|
||||
};
|
||||
|
||||
var $insertTags = function(target, tagOpen, tagClose) {
|
||||
const $x = (function() {
|
||||
const send = function(method, url, options, body) {
|
||||
if(options === undefined)
|
||||
options = {};
|
||||
else if(typeof options !== 'object')
|
||||
throw 'options must be undefined or an object';
|
||||
|
||||
const xhr = new XMLHttpRequest;
|
||||
const requestHeaders = new Map;
|
||||
|
||||
if('headers' in options && typeof options.headers === 'object')
|
||||
for(const name in options.headers)
|
||||
if(options.headers.hasOwnProperty(name))
|
||||
requestHeaders.set(name.toLowerCase(), options.headers[name]);
|
||||
|
||||
if(typeof options.download === 'function') {
|
||||
xhr.onloadstart = ev => options.download(ev);
|
||||
xhr.onprogress = ev => options.download(ev);
|
||||
xhr.onloadend = ev => options.download(ev);
|
||||
}
|
||||
|
||||
if(typeof options.upload === 'function') {
|
||||
xhr.upload.onloadstart = ev => options.upload(ev);
|
||||
xhr.upload.onprogress = ev => options.upload(ev);
|
||||
xhr.upload.onloadend = ev => options.upload(ev);
|
||||
}
|
||||
|
||||
if(options.authed)
|
||||
xhr.withCredentials = true;
|
||||
|
||||
if(typeof options.timeout === 'number')
|
||||
xhr.timeout = options.timeout;
|
||||
|
||||
if(typeof options.type === 'string')
|
||||
xhr.responseType = options.type;
|
||||
|
||||
if(typeof options.abort === 'function')
|
||||
options.abort(() => xhr.abort());
|
||||
|
||||
if(typeof options.xhr === 'function')
|
||||
options.xhr(() => xhr);
|
||||
|
||||
if(typeof body === 'object') {
|
||||
if(body instanceof URLSearchParams) {
|
||||
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
|
||||
} else if(body instanceof FormData) {
|
||||
// content-type is implicitly set
|
||||
} else if(body instanceof Blob || body instanceof ArrayBuffer || body instanceof DataView) {
|
||||
if(!requestHeaders.has('content-type'))
|
||||
requestHeaders.set('content-type', 'application/octet-stream');
|
||||
} else if(!requestHeaders.has('content-type')) {
|
||||
const bodyParts = [];
|
||||
for(const name in body)
|
||||
if(body.hasOwnProperty(name))
|
||||
bodyParts.push(encodeURIComponent(name) + '=' + encodeURIComponent(body[name]));
|
||||
body = bodyParts.join('&');
|
||||
requestHeaders.set('content-type', 'application/x-www-form-urlencoded');
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let responseHeaders = undefined;
|
||||
|
||||
xhr.onload = ev => resolve({
|
||||
status: xhr.status,
|
||||
body: () => xhr.response,
|
||||
text: () => xhr.responseText,
|
||||
headers: () => {
|
||||
if(responseHeaders !== undefined)
|
||||
return responseHeaders;
|
||||
|
||||
responseHeaders = new Map;
|
||||
|
||||
const raw = xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);
|
||||
for(const name in raw)
|
||||
if(raw.hasOwnProperty(name)) {
|
||||
const parts = raw[name].split(': ');
|
||||
responseHeaders.set(parts.shift(), parts.join(': '));
|
||||
}
|
||||
|
||||
return responseHeaders;
|
||||
},
|
||||
xhr: xhr,
|
||||
ev: ev,
|
||||
});
|
||||
|
||||
xhr.onerror = ev => reject({
|
||||
xhr: xhr,
|
||||
ev: ev,
|
||||
});
|
||||
|
||||
xhr.open(method, url);
|
||||
for(const [name, value] of requestHeaders)
|
||||
xhr.setRequestHeader(name, value);
|
||||
xhr.send(body);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
send: send,
|
||||
get: (url, options, body) => send('GET', url, options, body),
|
||||
post: (url, options, body) => send('POST', url, options, body),
|
||||
delete: (url, options, body) => send('DELETE', url, options, body),
|
||||
patch: (url, options, body) => send('PATCH', url, options, body),
|
||||
put: (url, options, body) => send('PUT', url, options, body),
|
||||
};
|
||||
})();
|
||||
|
||||
const $insertTags = function(target, tagOpen, tagClose) {
|
||||
tagOpen = tagOpen || '';
|
||||
tagClose = tagClose || '';
|
||||
|
||||
if(document.selection) {
|
||||
target.focus();
|
||||
var selected = document.selection.createRange();
|
||||
const selected = document.selection.createRange();
|
||||
selected.text = tagOpen + selected.text + tagClose;
|
||||
target.focus();
|
||||
} else if(target.selectionStart || target.selectionStart === 0) {
|
||||
var startPos = target.selectionStart,
|
||||
const startPos = target.selectionStart,
|
||||
endPos = target.selectionEnd,
|
||||
scrollTop = target.scrollTop;
|
||||
|
||||
|
@ -181,7 +293,7 @@ var $insertTags = function(target, tagOpen, tagClose) {
|
|||
target.focus();
|
||||
target.selectionStart = startPos + tagOpen.length;
|
||||
target.selectionEnd = endPos + tagOpen.length;
|
||||
target.scrollTop + scrollTop;
|
||||
target.scrollTop = scrollTop;
|
||||
} else {
|
||||
target.value += tagOpen + tagClose;
|
||||
target.focus();
|
|
@ -1,83 +1,65 @@
|
|||
const MszWatcher = function() {
|
||||
let watchers = [];
|
||||
const handlers = [];
|
||||
|
||||
const watch = (handler, ...args) => {
|
||||
if(typeof handler !== 'function')
|
||||
throw 'handler must be a function';
|
||||
if(handlers.includes(handler))
|
||||
throw 'handler already registered';
|
||||
|
||||
handlers.push(handler);
|
||||
args.push(true);
|
||||
handler(...args);
|
||||
};
|
||||
|
||||
const unwatch = handler => {
|
||||
$ari(handlers, handler);
|
||||
};
|
||||
|
||||
return {
|
||||
watch: function(watcher, thisArg, args) {
|
||||
if(typeof watcher !== 'function')
|
||||
throw 'watcher must be a function';
|
||||
if(watchers.indexOf(watcher) >= 0)
|
||||
return;
|
||||
|
||||
watchers.push(watcher);
|
||||
|
||||
if(thisArg !== undefined) {
|
||||
if(!Array.isArray(args)) {
|
||||
if(args !== undefined)
|
||||
args = [args];
|
||||
else args = [];
|
||||
}
|
||||
|
||||
// initial call
|
||||
args.push(true);
|
||||
|
||||
watcher.apply(thisArg, args);
|
||||
}
|
||||
},
|
||||
unwatch: function(watcher) {
|
||||
$ari(watchers, watcher);
|
||||
},
|
||||
call: function(thisArg, args) {
|
||||
if(!Array.isArray(args)) {
|
||||
if(args !== undefined)
|
||||
args = [args];
|
||||
else args = [];
|
||||
}
|
||||
|
||||
watch: watch,
|
||||
unwatch: unwatch,
|
||||
call: (...args) => {
|
||||
args.push(false);
|
||||
|
||||
for(const watcher of watchers)
|
||||
watcher.apply(thisArg, args);
|
||||
for(const handler of handlers)
|
||||
handler(...args);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const MszWatcherCollection = function() {
|
||||
const collection = new Map;
|
||||
const watchers = new Map;
|
||||
|
||||
const watch = function(name, watcher, thisArg, args) {
|
||||
const watchers = collection.get(name);
|
||||
if(watchers === undefined)
|
||||
const getWatcher = name => {
|
||||
const watcher = watchers.get(name);
|
||||
if(watcher === undefined)
|
||||
throw 'undefined watcher name';
|
||||
watchers.watch(watcher, thisArg, args);
|
||||
return watcher;
|
||||
};
|
||||
|
||||
const unwatch = function(name, watcher) {
|
||||
const watchers = collection.get(name);
|
||||
if(watchers === undefined)
|
||||
throw 'undefined watcher name';
|
||||
watchers.unwatch(watcher);
|
||||
const watch = (name, handler, ...args) => {
|
||||
getWatcher(name).watch(handler, ...args);
|
||||
};
|
||||
|
||||
const unwatch = (name, handler) => {
|
||||
getWatcher(name).unwatch(handler);
|
||||
};
|
||||
|
||||
return {
|
||||
define: function(names) {
|
||||
if(!Array.isArray(names))
|
||||
names = [names];
|
||||
for(const name of names)
|
||||
collection.set(name, new MszWatcher);
|
||||
},
|
||||
call: function(name, thisArg, args) {
|
||||
const watchers = collection.get(name);
|
||||
if(watchers === undefined)
|
||||
throw 'undefined watcher name';
|
||||
watchers.call(thisArg, args);
|
||||
},
|
||||
watch: watch,
|
||||
unwatch: unwatch,
|
||||
proxy: function(obj) {
|
||||
obj.watch = function(name, watcher) {
|
||||
watch(name, watcher);
|
||||
};
|
||||
obj.unwatch = unwatch;
|
||||
define: names => {
|
||||
if(typeof names === 'string')
|
||||
watchers.set(names, new MszWatcher);
|
||||
else if(Array.isArray(names))
|
||||
for(const name of names)
|
||||
watchers.set(name, new MszWatcher);
|
||||
else
|
||||
throw 'names must be an array of names or a single name';
|
||||
},
|
||||
call: (name, ...args) => {
|
||||
getWatcher(name).call(...args);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -79,31 +79,31 @@
|
|||
<div class="forum__post__text js-forum-posting-preview" hidden></div>
|
||||
|
||||
<div class="forum__post__actions forum__post__actions--bbcode" hidden>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-bold" title="Bold [b]<text>[/b]" data-tag-open="[b]" data-tag-close="[/b]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-bold js-forum-posting-markup" title="Bold [b]<text>[/b]" data-tag-open="[b]" data-tag-close="[/b]">
|
||||
<i class="fas fa-bold fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-italic" title="Italic [i]<text>[/i]" data-tag-open="[i]" data-tag-close="[/i]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-italic js-forum-posting-markup" title="Italic [i]<text>[/i]" data-tag-open="[i]" data-tag-close="[/i]">
|
||||
<i class="fas fa-italic fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-underline" title="Underline [u]<text>[/u]" data-tag-open="[u]" data-tag-close="[/u]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-underline js-forum-posting-markup" title="Underline [u]<text>[/u]" data-tag-open="[u]" data-tag-close="[/u]">
|
||||
<i class="fas fa-underline fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-strike" title="Strikethrough [s]<text>[/s]" data-tag-open="[s]" data-tag-close="[/s]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-strike js-forum-posting-markup" title="Strikethrough [s]<text>[/s]" data-tag-open="[s]" data-tag-close="[/s]">
|
||||
<i class="fas fa-strikethrough fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-link" title="Link [url]<url>[/url] or [url=<url>]<text>[/url]" data-tag-open="[url=]" data-tag-close="[/url]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-link js-forum-posting-markup" title="Link [url]<url>[/url] or [url=<url>]<text>[/url]" data-tag-open="[url=]" data-tag-close="[/url]">
|
||||
<i class="fas fa-link fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-image" title="Image [img]<url>[/img]" data-tag-open="[img]" data-tag-close="[/img]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-image js-forum-posting-markup" title="Image [img]<url>[/img]" data-tag-open="[img]" data-tag-close="[/img]">
|
||||
<i class="fas fa-image fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-audio" title="Audio [audio]<url>[/url]" data-tag-open="[audio]" data-tag-close="[/audio]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-audio js-forum-posting-markup" title="Audio [audio]<url>[/url]" data-tag-open="[audio]" data-tag-close="[/audio]">
|
||||
<i class="fas fa-music fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-video" title="Video [video]<url>[/video]" data-tag-open="[video]" data-tag-close="[/video]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-video js-forum-posting-markup" title="Video [video]<url>[/video]" data-tag-open="[video]" data-tag-close="[/video]">
|
||||
<i class="fas fa-video fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-code" title="Code [code]<code>[/code]" data-tag-open="[code]" data-tag-close="[/code]">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-code js-forum-posting-markup" title="Code [code]<code>[/code]" data-tag-open="[code]" data-tag-close="[/code]">
|
||||
<i class="fas fa-code fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--bb-zalgo" title="Zalgo [zalgo]<text>[/zalgo]" data-tag-open="[zalgo]" data-tag-close="[/zalgo]">
|
||||
|
@ -112,31 +112,31 @@
|
|||
</div>
|
||||
|
||||
<div class="forum__post__actions forum__post__actions--markdown" hidden>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-bold" title="Bold **<text>**" data-tag-open="**" data-tag-close="**">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-bold js-forum-posting-markup" title="Bold **<text>**" data-tag-open="**" data-tag-close="**">
|
||||
<i class="fas fa-bold fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-italic" title="Italic *<text>* or _<text>_" data-tag-open="*" data-tag-close="*">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-italic js-forum-posting-markup" title="Italic *<text>* or _<text>_" data-tag-open="*" data-tag-close="*">
|
||||
<i class="fas fa-italic fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-underline" title="Underline __<text>__" data-tag-open="__" data-tag-close="__">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-underline js-forum-posting-markup" title="Underline __<text>__" data-tag-open="__" data-tag-close="__">
|
||||
<i class="fas fa-underline fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-strike" title="Strikethrough ~~<text>~~" data-tag-open="~~" data-tag-close="~~">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-strike js-forum-posting-markup" title="Strikethrough ~~<text>~~" data-tag-open="~~" data-tag-close="~~">
|
||||
<i class="fas fa-strikethrough fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-link" title="Link [<text>](<url>)" data-tag-open="[](" data-tag-close=")">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-link js-forum-posting-markup" title="Link [<text>](<url>)" data-tag-open="[](" data-tag-close=")">
|
||||
<i class="fas fa-link fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-image" title="Image ![<alt text>](<url>)" data-tag-open="![](" data-tag-close=")">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-image js-forum-posting-markup" title="Image ![<alt text>](<url>)" data-tag-open="![](" data-tag-close=")">
|
||||
<i class="fas fa-image fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-audio" title="Audio ![<alt text>](<url>)" data-tag-open="![](" data-tag-close=")">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-audio js-forum-posting-markup" title="Audio ![<alt text>](<url>)" data-tag-open="![](" data-tag-close=")">
|
||||
<i class="fas fa-music fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-video" title="Video ![<alt text>](<url>)" data-tag-open="![](" data-tag-close=")">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-video js-forum-posting-markup" title="Video ![<alt text>](<url>)" data-tag-open="![](" data-tag-close=")">
|
||||
<i class="fas fa-video fa-fw"></i>
|
||||
</div>
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-code" title="Code `<code>` or ```<code>```" data-tag-open="```" data-tag-close="```">
|
||||
<div class="forum__post__action forum__post__action--tag forum__post__action--md-code js-forum-posting-markup" title="Code `<code>` or ```<code>```" data-tag-open="```" data-tag-close="```">
|
||||
<i class="fas fa-code fa-fw"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -53,9 +53,6 @@
|
|||
{% include '_layout/footer.twig' %}
|
||||
{% endblock %}
|
||||
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function() { Misuzu(); });
|
||||
</script>
|
||||
<script src="/vendor/highlightjs/highlight.min.js" type="text/javascript"></script>
|
||||
<script src="{{ asset('misuzu.js') }}" type="text/javascript"></script>
|
||||
</body>
|
||||
|
|
Loading…
Reference in a new issue