misuzu/assets/misuzu.js/embed/video.js

811 lines
24 KiB
JavaScript
Raw Normal View History

#include watcher.js
2023-01-29 01:51:54 +00:00
const MszVideoEmbedPlayerEvents = function() {
return [
'play', 'pause', 'stop',
'mute', 'volume',
'rate', 'duration', 'time',
];
};
const MszVideoEstimateAspectRatio = function(width, height) {
const gcd = function(a, b) { return b ? gcd(b, a % b) : Math.abs(a); };
const ratio = gcd(width, height);
width /= ratio;
height /= ratio;
return [width, height];
};
const MszVideoConstrainSize = function(w, h, mw, mh) {
mw = mw || 0;
mh = mh || 0;
w = w || 0;
h = h || 0;
const ar = MszVideoEstimateAspectRatio(w, h);
if(w > h) {
if(mw > 0) {
w = Math.min(mw, w);
h = Math.ceil((w / ar[0]) * ar[1]);
if(mh > 0) h = Math.min(mh, h);
}
} else {
if(mh > 0) {
h = Math.min(mh, h);
w = Math.ceil((h / ar[1]) * ar[0]);
if(mw > 0) w = Math.min(mw, w);
}
}
return [w, h];
};
const MszVideoEmbed = function(playerOrFrame) {
const frame = playerOrFrame;
const player = frame?.player ?? frame;
2023-01-29 01:51:54 +00:00
const element = $element(
'div',
{ classList: ['embed', 'embed-' + player.getType()] },
frame,
);
2023-01-29 01:51:54 +00:00
return {
get element() { return element; },
get player() { return player; },
2023-01-29 01:51:54 +00:00
appendTo: function(target) {
target.appendChild(element);
2023-01-29 01:51:54 +00:00
},
insertBefore: function(ref) {
$insertBefore(ref, element);
2023-01-29 01:51:54 +00:00
},
nuke: function() {
element.remove();
2023-01-29 01:51:54 +00:00
},
replaceElement(target) {
$insertBefore(target, element);
target.remove();
2023-01-29 01:51:54 +00:00
},
getFrame: function() {
return frame;
},
};
};
const MszVideoEmbedFrame = function(player, options) {
options = options || {};
2023-01-29 20:29:20 +00:00
const icoStatePlay = 'fa-play',
icoStatePause = 'fa-pause',
icoStateStop = 'fa-stop';
const icoVolMute = 'fa-volume-mute',
icoVolOff = 'fa-volume-off',
icoVolQuiet = 'fa-volume-down',
icoVolLoud = 'fa-volume-up';
const btnPlayPause = $element('div', null, $element(
'i', { classList: ['fas', 'fa-fw', icoStatePlay] }
));
const btnStop = $element('div', null, $element(
'i', { classList: ['fas', 'fa-fw', icoStateStop] }
));
const numCurrentTime = $element('div');
const sldProgress = $element('div');
const numDurationRemaining = $element('div');
const btnVolMute = $element('div', null, $element(
'i', {
// isMuted === icoVolMute
// vol < 0.01 === icoVolOff
// vol < 0.5 === icoVolQuiet
// vol < 1.0 === icoVolLoud
classList: ['fas', 'fa-fw', icoVolLoud],
2023-01-29 20:29:20 +00:00
}
));
2023-01-29 20:29:20 +00:00
const element = $element(
'div',
{
2023-01-29 01:51:54 +00:00
className: 'embedvf',
style: {
width: player.getWidth().toString() + 'px',
height: player.getHeight().toString() + 'px',
},
},
$element('div', { className: 'embedvf-player' }, player),
$element(
'div',
{ className: 'embedvf-overlay' },
$element(
'div',
{ className: 'embedvf-controls' },
btnPlayPause,
btnStop,
numCurrentTime,
sldProgress,
numDurationRemaining,
),
),
);
2023-01-29 01:51:54 +00:00
return {
get element() { return element; },
get player() { return player; },
2023-01-29 01:51:54 +00:00
appendTo: function(target) {
target.appendChild(element);
2023-01-29 01:51:54 +00:00
},
insertBefore: function(ref) {
$insertBefore(ref, element);
2023-01-29 01:51:54 +00:00
},
nuke: function() {
element.remove();
2023-01-29 01:51:54 +00:00
},
replaceElement(target) {
$insertBefore(target, element);
target.remove();
2023-01-29 01:51:54 +00:00
},
};
};
const MszVideoEmbedPlayer = function(metadata, options) {
options = options || {};
const shouldAutoplay = options.autoplay === undefined || options.autoplay;
const haveNativeControls = options.nativeControls !== undefined && options.nativeControls;
const shouldObserveResize = options.observeResize === undefined || options.observeResize;
2023-01-29 01:51:54 +00:00
const videoAttrs = {
src: metadata.url,
style: {},
};
if(shouldAutoplay)
videoAttrs.autoplay = 'autoplay';
if(haveNativeControls)
videoAttrs.controls = 'controls';
const constrainSize = function(w, h, mw, mh) {
return MszVideoConstrainSize(w, h, mw || options.maxWidth, mh || options.maxHeight);
};
const initialSize = constrainSize(
options.width || metadata.width || 200,
options.height || metadata.height || 200
);
videoAttrs.style.width = initialSize[0].toString() + 'px';
videoAttrs.style.height = initialSize[1].toString() + 'px';
2024-01-30 23:47:02 +00:00
const watchers = new MszWatchers;
2023-01-29 01:51:54 +00:00
watchers.define(MszVideoEmbedPlayerEvents());
const element = $element('video', videoAttrs);
2023-01-29 01:51:54 +00:00
const setSize = function(w, h) {
const size = constrainSize(w, h, initialSize[0], initialSize[1]);
element.style.width = size[0].toString() + 'px';
element.style.height = size[1].toString() + 'px';
2023-01-29 01:51:54 +00:00
};
const pub = {
get element() { return element; },
2023-01-29 01:51:54 +00:00
appendTo: function(target) {
target.appendChild(element);
2023-01-29 01:51:54 +00:00
},
insertBefore: function(ref) {
$insertBefore(ref, element);
2023-01-29 01:51:54 +00:00
},
nuke: function() {
element.remove();
2023-01-29 01:51:54 +00:00
},
replaceElement(target) {
$insertBefore(target, element);
target.remove();
2023-01-29 01:51:54 +00:00
},
getType: function() { return 'external'; },
getWidth: function() { return width; },
getHeight: function() { return height; },
};
2024-01-24 21:53:26 +00:00
pub.watch = (name, handler) => watchers.watch(name, handler);
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
2023-01-29 01:51:54 +00:00
if(shouldObserveResize)
element.addEventListener('resize', function() { setSize(element.videoWidth, element.videoHeight); });
2023-01-29 01:51:54 +00:00
element.addEventListener('play', function() { watchers.call('play'); });
2023-01-29 01:51:54 +00:00
const pPlay = function() { element.play(); };
2023-01-29 01:51:54 +00:00
pub.play = pPlay;
const pPause = function() { element.pause(); };
2023-01-29 01:51:54 +00:00
pub.pause = pPause;
let stopCalled = false;
element.addEventListener('pause', function() {
2024-01-24 21:53:26 +00:00
watchers.call(stopCalled ? 'stop' : 'pause');
2023-01-29 01:51:54 +00:00
stopCalled = false;
});
const pStop = function() {
stopCalled = true;
element.pause();
element.currentTime = 0;
2023-01-29 01:51:54 +00:00
};
pub.stop = pStop;
const pIsPlaying = function() { return !element.paused; };
2023-01-29 01:51:54 +00:00
pub.isPlaying = pIsPlaying;
const pIsMuted = function() { return element.muted; };
2023-01-29 01:51:54 +00:00
pub.isMuted = pIsMuted;
let lastMuteState = element.muted;
element.addEventListener('volumechange', function() {
if(lastMuteState !== element.muted) {
lastMuteState = element.muted;
2024-01-24 21:53:26 +00:00
watchers.call('mute', lastMuteState);
2023-01-29 01:51:54 +00:00
} else
watchers.call('volume', element.volume);
2023-01-29 01:51:54 +00:00
});
const pSetMuted = function(state) { element.muted = state; };
2023-01-29 01:51:54 +00:00
pub.setMuted = pSetMuted;
const pGetVolume = function() { return element.volume; };
2023-01-29 01:51:54 +00:00
pub.getVolume = pGetVolume;
const pSetVolume = function(volume) { element.volume = volume; };
2023-01-29 01:51:54 +00:00
pub.setVolume = pSetVolume;
const pGetPlaybackRate = function() { return element.playbackRate; };
2023-01-29 01:51:54 +00:00
pub.getPlaybackRate = pGetPlaybackRate;
element.addEventListener('ratechange', function() {
watchers.call('rate', element.playbackRate);
2023-01-29 01:51:54 +00:00
});
const pSetPlaybackRate = function(rate) { element.playbackRate = rate; };
2023-01-29 01:51:54 +00:00
pub.setPlaybackRate = pSetPlaybackRate;
window.addEventListener('durationchange', function() {
watchers.call('duration', element.duration);
2023-01-29 01:51:54 +00:00
});
const pGetDuration = function() { return element.duration; };
2023-01-29 01:51:54 +00:00
pub.getDuration = pGetDuration;
window.addEventListener('timeupdate', function() {
watchers.call('time', element.currentTime);
2023-01-29 01:51:54 +00:00
});
const pGetTime = function() { return element.currentTime; };
2023-01-29 01:51:54 +00:00
pub.getTime = pGetTime;
const pSeek = function(time) { element.currentTime = time; };
2023-01-29 01:51:54 +00:00
pub.seek = pSeek;
return pub;
};
const MszVideoEmbedYouTube = function(metadata, options) {
options = options || {};
const ytOrigin = 'https://www.youtube.com';
const playerId = 'yt-' + $rngs(8);
const shouldAutoplay = options.autoplay === undefined || options.autoplay;
2023-01-29 01:51:54 +00:00
let embedUrl = 'https://www.youtube.com/embed/' + metadata.youtube_video_id + '?enablejsapi=1';
embedUrl += '&origin=' + encodeURIComponent(location.origin);
if(metadata.youtube_start_time)
embedUrl += '&t=' + encodeURIComponent(metadata.youtube_start_time);
if(metadata.youtube_playlist) {
embedUrl += '&list=' + encodeURIComponent(metadata.youtube_playlist);
if(metadata.youtube_playlist_index)
embedUrl += '&index=' + encodeURIComponent(metadata.youtube_playlist_index);
}
let presetPlaybackRates = undefined,
isMuted = undefined,
volume = undefined,
playbackRate = undefined,
duration = undefined,
currentTime = undefined,
isPlaying = undefined;
2024-01-30 23:47:02 +00:00
const watchers = new MszWatchers;
2023-01-29 01:51:54 +00:00
watchers.define(MszVideoEmbedPlayerEvents());
const element = $element('iframe', {
frameborder: 0,
allow: 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',
allowfullscreen: 'allowfullscreen',
src: embedUrl,
2023-01-29 01:51:54 +00:00
});
const pub = {
get element() { return element; },
2023-01-29 01:51:54 +00:00
appendTo: function(target) {
target.appendChild(element);
2023-01-29 01:51:54 +00:00
},
insertBefore: function(ref) {
$insertBefore(ref, element);
2023-01-29 01:51:54 +00:00
},
nuke: function() {
element.remove();
2023-01-29 01:51:54 +00:00
},
replaceElement(target) {
$insertBefore(target, element);
target.remove();
2023-01-29 01:51:54 +00:00
},
getType: function() { return 'youtube'; },
getWidth: function() { return 560; },
getHeight: function() { return 315; },
getPlayerId: function() { return playerId; },
};
2024-01-24 21:53:26 +00:00
pub.watch = (name, handler) => watchers.watch(name, handler);
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
2023-01-29 01:51:54 +00:00
const postMessage = function(data) {
element.contentWindow.postMessage(JSON.stringify(data), ytOrigin);
2023-01-29 01:51:54 +00:00
};
const postCommand = function(name, args) {
postMessage({
id: playerId,
event: 'command',
func: name,
args: args || [],
});
};
const cmdPlay = function() { postCommand('playVideo'); };
const cmdPause = function() { postCommand('pauseVideo'); };
const cmdStop = function() { postCommand('stopVideo'); };
const cmdMute = function() { postCommand('mute'); };
const cmdUnMute = function() { postCommand('unMute'); };
const cmdSetVolume = function(volume) { postCommand('setVolume', [volume * 100]); };
const cmdSetPlaybackRate = function(rate) { postCommand('setPlaybackRate', [rate]); };
const cmdSeekTo = function(timecode) { postCommand('seekTo', [timecode, true]); };
const cmdMuteState = function(state) {
if(state) cmdMute();
else cmdUnMute();
};
const varIsPlaying = function() { return isPlaying; };
const varIsMuted = function() { return isMuted; };
const varVolume = function() { return volume; };
const varRate = function() { return playbackRate; };
const varDuration = function() { return duration; };
const varTime = function() { return currentTime; };
let lastPlayerState = undefined,
lastPlayerStateEvent = undefined;
const handlePlayerState = function(state) {
let eventName = undefined;
if(state === 1) {
isPlaying = true;
eventName = 'play';
} else {
isPlaying = false;
if(state === 2)
eventName = 'pause';
else if(lastPlayerState === -1 && state === 5)
eventName = 'stop';
}
lastPlayerState = state;
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
lastPlayerStateEvent = eventName;
2024-01-24 21:53:26 +00:00
watchers.call(eventName);
2023-01-29 01:51:54 +00:00
}
};
const handleMuted = function(muted) {
isMuted = muted;
2024-01-24 21:53:26 +00:00
watchers.call('mute', isMuted);
2023-01-29 01:51:54 +00:00
};
const handleVolume = function(value) {
volume = value / 100;
2024-01-24 21:53:26 +00:00
watchers.call('volume', volume);
2023-01-29 01:51:54 +00:00
};
const handleRate = function(rate) {
playbackRate = rate;
2024-01-24 21:53:26 +00:00
watchers.call('rate', playbackRate);
2023-01-29 01:51:54 +00:00
};
const handleDuration = function(time) {
duration = time;
2024-01-24 21:53:26 +00:00
watchers.call('duration', duration);
2023-01-29 01:51:54 +00:00
};
const handleTime = function(time) {
currentTime = time;
2024-01-24 21:53:26 +00:00
watchers.call('time', currentTime);
2023-01-29 01:51:54 +00:00
};
const handlePresetRates = function(rates) {
presetPlaybackRates = rates;
};
const infoHandlers = {
'playerState': handlePlayerState,
'muted': handleMuted,
'volume': handleVolume,
'playbackRate': handleRate,
'duration': handleDuration,
'currentTime': handleTime,
'availablePlaybackRates': handlePresetRates,
};
const processInfo = function(info) {
for(const name in infoHandlers)
if(name in info && info[name] !== undefined)
infoHandlers[name](info[name]);
};
window.addEventListener('message', function(ev) {
if(ev.origin !== ytOrigin || typeof ev.data !== 'string')
return;
const data = JSON.parse(ev.data);
if(!data || data.id !== playerId)
return;
if(data.event === 'initialDelivery') {
if(data.info !== undefined)
processInfo(data.info);
} else if(data.event === 'onReady') {
if(shouldAutoplay)
cmdPlay();
} else if(data.event === 'infoDelivery') {
if(data.info !== undefined)
processInfo(data.info);
}
});
element.addEventListener('load', function(ev) {
2023-01-29 01:51:54 +00:00
postMessage({
id: playerId,
event: 'listening',
});
});
pub.play = cmdPlay;
pub.pause = cmdPause;
pub.stop = cmdStop;
pub.isPlaying = varIsPlaying;
pub.isMuted = varIsMuted;
pub.setMuted = cmdMuteState;
pub.getVolume = varVolume;
pub.setVolume = cmdSetVolume;
pub.getRate = varRate;
pub.setRate = cmdSetPlaybackRate;
pub.getDuration = varDuration;
pub.getTime = varTime;
pub.seek = cmdSeekTo;
return pub;
};
const MszVideoEmbedNicoNico = function(metadata, options) {
options = options || {};
const nndOrigin = 'https://embed.nicovideo.jp';
const playerId = 'nnd-' + $rngs(8);
const shouldAutoplay = options.autoplay === undefined || options.autoplay;
2023-01-29 01:51:54 +00:00
let embedUrl = 'https://embed.nicovideo.jp/watch/' + metadata.nicovideo_video_id + '?jsapi=1&playerId=' + playerId;
if(metadata.nicovideo_start_time)
embedUrl += '&from=' + encodeURIComponent(metadata.nicovideo_start_time);
let isMuted = undefined;
let volume = undefined;
let duration = undefined;
let currentTime = undefined;
let isPlaying = false;
2023-01-29 01:51:54 +00:00
2024-01-30 23:47:02 +00:00
const watchers = new MszWatchers;
2023-01-29 01:51:54 +00:00
watchers.define(MszVideoEmbedPlayerEvents());
const element = $element('iframe', {
frameborder: 0,
allow: 'autoplay',
allowfullscreen: 'allowfullscreen',
src: embedUrl,
2023-01-29 01:51:54 +00:00
});
const pub = {
get element() { return element; },
2023-01-29 01:51:54 +00:00
appendTo: function(target) {
target.appendChild(element);
2023-01-29 01:51:54 +00:00
},
insertBefore: function(ref) {
$insertBefore(ref, element);
2023-01-29 01:51:54 +00:00
},
nuke: function() {
element.remove();
2023-01-29 01:51:54 +00:00
},
replaceElement(target) {
$insertBefore(target, element);
target.remove();
2023-01-29 01:51:54 +00:00
},
getType: function() { return 'nicovideo'; },
getWidth: function() { return 640; },
getHeight: function() { return 360; },
getPlayerId: function() { return playerId; },
};
2024-01-24 21:53:26 +00:00
pub.watch = (name, handler) => watchers.watch(name, handler);
pub.unwatch = (name, handler) => watchers.unwatch(name, handler);
2023-01-29 01:51:54 +00:00
const postMessage = function(name, data) {
if(name === undefined)
throw 'name must be specified';
element.contentWindow.postMessage({
2023-01-29 01:51:54 +00:00
playerId: playerId,
sourceConnectorType: 1,
eventName: name,
data: data,
}, nndOrigin);
};
const cmdPlay = function() { postMessage('play'); };
const cmdPause = function() { postMessage('pause'); };
const cmdMute = function(state) { postMessage('mute', { mute: state }); };
const cmdSeek = function(time) { postMessage('seek', { time: time }); };
const cmdVolumeChange = function(volume) { postMessage('volumeChange', { volume: volume }); };
let stopCalled = false;
const cmdStop = function() {
stopCalled = true;
cmdPause();
cmdSeek(0);
};
const varIsPlaying = function() { return isPlaying; };
const varIsMuted = function() { return isMuted; };
const varVolume = function() { return volume; };
const varDuration = function() { return duration; };
const varTime = function() { return currentTime; };
let lastPlayerStateEvent = undefined;
const handlePlayerStatus = function(status) {
let eventName = undefined;
if(status === 2) {
isPlaying = true;
eventName = 'play';
} else {
isPlaying = false;
if(status === 4 || stopCalled) {
eventName = 'stop';
stopCalled = false;
} else if(status === 3)
eventName = 'pause';
}
if(eventName !== undefined && eventName !== lastPlayerStateEvent) {
lastPlayerStateEvent = eventName;
2024-01-24 21:53:26 +00:00
watchers.call(eventName);
2023-01-29 01:51:54 +00:00
}
};
const handleMuted = function(muted) {
isMuted = muted;
2024-01-24 21:53:26 +00:00
watchers.call('mute', isMuted);
2023-01-29 01:51:54 +00:00
};
const handleVolume = function(value) {
volume = value;
2024-01-24 21:53:26 +00:00
watchers.call('volume', volume);
2023-01-29 01:51:54 +00:00
};
const handleDuration = function(time) {
duration = time / 1000;
2024-01-24 21:53:26 +00:00
watchers.call('duration', duration);
2023-01-29 01:51:54 +00:00
};
const handleTime = function(time) {
currentTime = time / 1000;
2024-01-24 21:53:26 +00:00
watchers.call('time', currentTime);
2023-01-29 01:51:54 +00:00
};
const metadataHanders = {
'muted': handleMuted,
'volume': handleVolume,
'duration': handleDuration,
'currentTime': handleTime,
};
const statusHandlers = {
'playerStatus': handlePlayerStatus,
};
const processData = function(handlers, info) {
for(const name in handlers)
if(name in info && info[name] !== undefined)
handlers[name](info[name]);
};
window.addEventListener('message', function(ev) {
if(ev.origin !== nndOrigin || ev.data.playerId !== playerId)
return;
if(ev.data.eventName === 'loadComplete') {
if(shouldAutoplay)
cmdPlay();
} else if(ev.data.eventName === 'playerMetadataChange') {
if(ev.data.data !== undefined)
processData(metadataHanders, ev.data.data);
} else if(ev.data.eventName === 'statusChange') {
if(ev.data.data !== undefined)
processData(statusHandlers, ev.data.data);
}
});
pub.play = cmdPlay;
pub.pause = cmdPause;
pub.stop = cmdStop;
pub.isPlaying = varIsPlaying;
pub.isMuted = varIsMuted;
pub.setMuted = cmdMute;
pub.getVolume = varVolume;
pub.setVolume = cmdVolumeChange;
pub.getDuration = varDuration;
pub.getTime = varTime;
pub.seek = cmdSeek;
return pub;
};
const MszVideoEmbedPlaceholder = function(metadata, options) {
options = options || {};
if(typeof options.player !== 'function' && typeof options.onclick !== 'function')
throw 'Neither a player nor an onclick handler were provided.';
const shouldAutoSize = options.autosize === undefined || options.autosize;
const infoChildren = [];
infoChildren.push($element(
'h1',
{ className: 'embedph-info-title' },
metadata.title,
));
2023-01-29 01:51:54 +00:00
if(metadata.description) {
2023-01-29 03:35:45 +00:00
let firstLine = metadata.description.split("\n")[0].trim();
2023-01-29 01:51:54 +00:00
if(firstLine.length > 300)
firstLine = firstLine.substring(0, 300).trim() + '...';
infoChildren.push($element(
'div',
{ className: 'embedph-info-desc' },
firstLine,
));
2023-01-29 01:51:54 +00:00
}
infoChildren.push($element(
'div',
{ className: 'embedph-info-site' },
metadata.site_name,
));
2023-01-29 01:51:54 +00:00
const style = [];
if(typeof metadata.color !== 'undefined')
style.push('--embedph-colour: ' + metadata.color);
if(!shouldAutoSize) {
const size = MszVideoConstrainSize(
options.width || metadata.width || 200,
options.height || metadata.height || 200,
options.maxWidth,
options.maxHeight
);
style.push('width: ' + size[0].toString() + 'px');
style.push('height: ' + size[1].toString() + 'px');
}
let element;
const pub = {
get element() { return element; },
};
2023-01-29 01:51:54 +00:00
element = $element(
'div',
{
2023-01-29 01:51:54 +00:00
className: ('embedph embedph-' + (options.type || 'external')),
style: style.join(';'),
},
$element(
'div',
{ className: 'embedph-bg' },
$element('img', { src: metadata.image }),
),
$element(
'div',
{ className: 'embedph-fg' },
$element(
'div',
{ className: 'embedph-info' },
$element(
'div',
{ className: 'embedph-info-wrap' },
$element('div', { className: 'embedph-info-bar' }),
$element('div', { className: 'embedph-info-body' }, ...infoChildren),
),
),
$element(
'div',
{
className: 'embedph-play',
onclick: function(ev) {
if(ev.target.tagName.toLowerCase() === 'a')
return;
if(typeof options.onclick === 'function') {
options.onclick(ev);
return;
}
const player = new options.player(metadata, options);
let frameOrPlayer = player;
if(typeof options.frame === 'function')
frameOrPlayer = new options.frame(player, options);
const embed = new MszVideoEmbed(frameOrPlayer);
if(options.autoembed === undefined || options.autoembed)
embed.replaceElement(element);
if(typeof options.onembed === 'function')
options.onembed(embed);
2023-01-29 01:51:54 +00:00
},
},
$element(
'div',
{ className: 'embedph-play-internal' },
$element('i', { className: 'fas fa-play fa-4x fa-fw' }),
),
$element(
'a',
2023-01-29 01:51:54 +00:00
{
className: 'embedph-play-external',
href: metadata.url,
target: '_blank',
rel: 'noopener',
2023-01-29 20:29:20 +00:00
},
`or watch on ${metadata.site_name}?`
),
),
),
);
2023-01-29 01:51:54 +00:00
pub.appendTo = function(target) { target.appendChild(element); };
pub.insertBefore = function(ref) { $insertBefore(ref, element); };
2023-01-29 01:51:54 +00:00
pub.nuke = function() {
element.remove();
2023-01-29 01:51:54 +00:00
};
pub.replaceElement = function(target) {
$insertBefore(target, element);
target.remove();
2023-01-29 01:51:54 +00:00
};
return pub;
};