Update site structure, update global js to add domain warning, update SpiderADB

This commit is contained in:
2024-04-19 00:34:22 +02:00
parent eecdf1f2fd
commit 87757fbcee
80 changed files with 248 additions and 75 deletions

View File

@@ -0,0 +1,676 @@
// TODO:
// * support opening posts from not only id but also permalink
// * custom colors
// * reduce lag on mobile somehow
// * open author profiles/bios as a channel and show only their messages instead of the full site
// * show site/profile info on click of navbar for mobile
// * in-app search of site content
// * homepage with history and sponsored sources
// * don't show redundant day markers
// * fetch and compile to show Markdown WordPress export pages from Git?
// * app info in main page without JS?
// * fix some messages being skipped when connection errors happen (already done?)
// * optionally show post titles?
// * fix some unfinished tasks still executing when clicking back
// * I think we might need to handle acronicized names for users when needed?
// * show, and/or sort by, posts tags/categories
// * scroll to post id when loading from dataInject or RSS
let MbState = {};
let MbApiTransformer;
function ArgsRewrite (props={}, navigate=true) {
for (const key in props) {
const value = props[key];
value ? (MbState.args[key] = value) : (delete MbState.args[key]);
}
let hash = '/';
for (const arg in MbState.args) {
hash += `${arg}=${MbState.args[arg]}|`;
}
if (navigate) {
location.hash = hash;
}
return hash
}
const SureArray = (obj) => (Array.isArray(obj) ? obj : [obj]);
// <https://stackoverflow.com/questions/29956338/how-to-accurately-determine-if-an-element-is-scrollable/71170105#71170105>
function CanScrollEl (el, scrollAxis) {
if (0 === el[scrollAxis]) {
el[scrollAxis] = 1;
if (1 === el[scrollAxis]) {
el[scrollAxis] = 0;
return true;
}
} else {
return true;
}
return false;
}
function IsScrollableY (el) {
return (el.scrollHeight > el.clientHeight) && CanScrollEl(el, 'scrollTop') && ('hidden' !== getComputedStyle(el).overflowY);
}
// <https://www.javascripttutorial.net/dom/css/check-if-an-element-is-visible-in-the-viewport/>
function IsElemInViewport (elem) {
const rect = elem.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
function GetDomainFromUrl (url) {
return url.split('//')[1].split('/')[0];
}
function MakeSiteRestUrl (path='') {
const siteUrl = MbState.siteUrl;
if (GetDomainFromUrl(siteUrl).toLowerCase() === 'octospacc.altervista.org') {
return `${siteUrl}/wp-content/uploads/${siteUrl.split('.').slice(0, 1)[0].split('//')[1]}/scripts/stuff.php?&Thing=SiteWpJsonCors&AccessToken=9ab6e20c&$Query=${encodeURIComponent(path)}`;
} else {
if (["atom", "rss", "wordpress.org"].includes(MbState.platform)) {
const proxies = ["corsproxy.io", "corsproxy.org"];
return `https://${proxies[~~(Math.random() * proxies.length)]}/?${siteUrl}/${MbState.platform === 'wordpress.org' ? `wp-json/${path}` : ''}`;
} else if (MbState.platform === 'wordpress.com') {
return `https://public-api.wordpress.com/rest/v1.1/sites/${GetDomainFromUrl(siteUrl)}/${path}`;
} else if (MbState.platform === 'mastodon') {
return `${MbState.siteUrl.split('/').slice(0, 3).join('/')}/api/${path || 'v2/instance'}`;
}
}
}
function MakeApiEndpoint (type, options={}) {
const translations = {
"mastodon": {},
"wordpress.org": {
count: "per_page",
orderBy: "orderby",
},
"wordpress.com": {
count: "number",
orderBy: "order_by",
},
}
let query = '';
for (const option in options) {
if (option !== 'id') {
query += `&${translations[MbState.platform][option] || option}=${options[option]}`;
}
}
query = `${options.id || ''}?${query.slice(1)}`;
switch (MbState.platform) {
case 'mastodon':
switch (type) {
case 'acct' : query = `v1/accounts/lookup?acct=${options.username}`; break;
case 'default':
case 'posts': query = `v1/accounts/${MbState.userId}/statuses?exclude_replies=true`; break;
}
break;
case 'wordpress.org': query = `wp/v2/${type}/${query}`; break;
case 'wordpress.com': query = `${type}/${query}`; break;
}
return query;
}
function MakeAcroName(name) {
let acro = '';
for (const word of name.split(' ').slice(0,3)) {
acro += word[0].toUpperCase();
}
return acro;
}
async function MbViewerInit () {
if (!location.hash) {
location.hash = '/';
}
if (!MbApiTransformer) {
MbApiTransformer = Trasformapi(MbViewerTrasformapiSchema).TransformForInput;
}
MbState = {
args: {},
siteData: {
name: "👁️‍🗨️️ MBViewer",
acroName: '👁️‍🗨️️ MBV',
description: `
The messages of this channel are baked inside the app,
and serve as the centralized place for all kinds of information about it,
while at the same time acting as a demo.
Please enjoy your time here, or use the search bar to input a supported URL.
<br/>
For other projects, visit the Octo Hub at <a href="https://hub.octt.eu.org">hub.octt.eu.org</a>!
`,
},
authors: {},
lastPostOffsetAfter: 0,
lastPostOffsetBefore: 0,
lastMustScroll: true,
internalIdCount: 0,
};
$('form.tgme_header_search_form')[0].action = '';
$('form.tgme_header_search_form')[0].onsubmit = function(event){
let url = event.target.querySelector('input').value;
const urlLow = url.toLowerCase();
if (!urlLow.startsWith('http://') && !urlLow.startsWith('https://')) {
url = `https://${url}`;
}
if (["t.me", "telegram.me"].includes(url.toLowerCase().split('://')[1].split('/')[0])) {
location = url;
} else {
ArgsRewrite({ siteurl: url });
}
event.preventDefault();
};
$('a.tgme_header_link')[0].onclick = function(){
if (window.innerWidth <= 720 ) { // .tgme_header_right_column @media max-width
if (!$('.tgme_header_right_column')[0].style.display) {
$('body')[0].style.overflow = 'hidden';
$('main.tgme_main')[0].style.visibility = 'hidden';
$('.tgme_header_search')[0].style.display = 'none';
$('.tgme_header_right_column')[0].style.display = 'revert';
$('.tgme_header_right_column')[0].style.width = 'calc(100% - 24px)';
$('.tgme_header_right_column .tgme_channel_info')[0].style.height = 'calc(100% - 32px)';
$('.tgme_header_right_column a[name="closeColumn"]')[0].hidden = false;
} else {
HideMobileRightColumn();
}
}
};
$('.tgme_header_right_column a[name="closeColumn"]')[0].onclick = HideMobileRightColumn;
$('.tgme_channel_info_header_username').html('');
$('.tgme_page_photo_image').html('');
$('.tgme_page_photo_image').removeClass('bgcolor0 bgcolor1 bgcolor2 bgcolor3 bgcolor4 bgcolor5 bgcolor6');
$('.tgme_page_photo_image').attr('data-content', '');
$('.tgme_header_title, .tgme_channel_info_header_title').html('');
$('.tgme_channel_info_description').html('');
$('section.tgme_channel_history.js-message_history').html('');
for (const arg of location.hash.split('/').slice(1).join('/').split('|')) {
if (arg) {
const argTokens = arg.split('=');
const valueItems = argTokens.slice(1).join('=').split(',');
MbState.args[argTokens[0].toLowerCase()] = (valueItems.length > 1 ? valueItems : valueItems[0]);
}
}
MbState.siteUrl = MbState.args.siteurl;
MbState.platform = /*SureArray(*/MbState.args.platform/*)*/;
MbState.postId = MbState.args.postid;
//MbState.postSlug = MbState.args.postslug;
RefreshIncludeStyle();
RefreshIncludeScript();
if (MbState.args.dataurl) {
// TODO initially remove built-in site data?
MbState.dataInject = {};
try {
const fileUrlPrefix = (MbState.args.dataurl.split('/').slice(0, -1).join('/') || '.');
const dataRequest = await fetch(MbState.args.dataurl);
MbState.dataInject = await dataRequest.json();
let messagesNew = [];
if (MbState.platform === 'telegram.export') {//(["telegram.export", "telegram.json"].includes(MbState.platform)) {
MbState.siteData = {
name: MbState.dataInject.name,
description: `${MbState.dataInject.type} ${MbState.dataInject.id}`,
acroName: (!MbState.siteData.iconUrl ? MbState.dataInject.name && MakeAcroName(MbState.dataInject.name) : ''),
bgColor: ~~(Math.random() * 7),
};
const textEncoder = document.createElement('textarea');
for (const message of MbState.dataInject.messages) {
if (message.type !== 'message') {
continue;
}
messagesNew.push({
content: '',
quoting: (message.forwarded_from && {
author: {
name: `Forwarded from ${message.forwarded_from}`,
},
}),
time: message.date,
url: `#${ArgsRewrite({ postid: null }, false)}PostId=${message.id}`,
});
//for (const piece of (Array.isArray(message.text) ? message.text : [message.text])) {
// messagesNew[messagesNew.length - 1].content += (piece.text || piece);
//}
//const encoder = document.createElement('textarea');
//encoder.innerHTML = messagesNew[messagesNew.length - 1].content;
//messagesNew[messagesNew.length - 1].content = encoder.innerHTML.replaceAll('\n', '<br/>');
for (const entity of message.text_entities) {
const entityTag = { bold: "b", italic: "i", link: "a", text_link: "a", pre: "pre", blockquote: "blockquote" }[entity.type];
const entityHref = (entityTag === 'a' && (entity.href || entity.text));
textEncoder.innerHTML = entity.text;
entity.text = textEncoder.innerHTML.replaceAll('\n', '<br/>');
messagesNew[messagesNew.length - 1].content += (entityTag
? `<${entityTag} ${entityHref ? `href="${entityHref}"` : ''}>${entity.text}</${entityTag}>`
: entity.text);
}
if (messagesNew[messagesNew.length - 1].content) {
messagesNew[messagesNew.length - 1].content = `<p>${messagesNew[messagesNew.length - 1].content}</p>`;
}
if (message.photo) {
messagesNew[messagesNew.length - 1].content = `<img src="${fileUrlPrefix}/${message.photo}"/>${messagesNew[messagesNew.length - 1].content}`;
} else if (message.file && message.mime_type?.split('/')[0] === 'video') {
messagesNew[messagesNew.length - 1].content = `<video controls="true" src="${fileUrlPrefix}/${message.file}"></video>${messagesNew[messagesNew.length - 1].content}`;
}
}
} else {
messagesNew = MbApiTransformer('message[]', MbState.platform, MbState.dataInject);
}
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml());
TWeb.loadMore($('.js-messages_more_wrap > a'), messagesNew);
} catch(err) {
console.log(err);
setTimeout(MbViewerInit, 1000);
return;
}
}
else if (MbState.siteUrl) {
if (!MbState.platform) {
if (GetDomainFromUrl(MbState.siteUrl).toLowerCase().endsWith('wordpress.com')) {
MbState.platform = 'wordpress.com';
} else {
MbState.platform = 'wordpress.org';
}
}
try {
const siteRequest = await fetch(MakeSiteRestUrl());
MbState.siteData = (["atom", "rss"].includes(MbState.platform)
? new DOMParser().parseFromString(await siteRequest.text(), 'text/xml')
: await siteRequest.json());
if (MbState.platform === 'mastodon') {
MbState.siteData = MbApiTransformer('profile', MbState.platform, MbState.siteData);
let username = MbState.siteUrl;
if (username.endsWith('/')) username = username.slice(0, -1);
username = username.split('/').slice(-1)[0];
if (username.startsWith('@')) username = username.slice(1);
const userRequest = await fetch(MakeSiteRestUrl(MakeApiEndpoint('acct', { username })));
const userData = await userRequest.json();
MbState.authors[MbState.userId = userData.id] = MbApiTransformer('profile', MbState.platform, userData);
}
} catch(err) {
console.log(err);
setTimeout(MbViewerInit, 1000);
return;
}
const siteLink = (MbState.siteData.url || MbState.siteData.URL || MbState.siteUrl);
MbState.startingPost = MbState.postId;//!!(MbState.postId || MbState.postSlug);
if (MbState.startingPost) {
try {
const postRequest = await fetch(MakeSiteRestUrl(MakeApiEndpoint('posts', { id: MbState.postId })));
MbState.startingPost = await postRequest.json();
$('section.tgme_channel_history.js-message_history').append(MakeMoreWrapperHtml('after'));
MbState.lastPostOffsetAfter = 0; // for some reason we need to clear this after making the wrapper or else we lose a post
TWeb.loadMore($('.js-messages_more_wrap > a[data-after]'), MbState.startingPost);
$('section.tgme_channel_history.js-message_history').prepend(MakeMoreWrapperHtml('before'));
} catch(err) {
console.log(err);
setTimeout(MbViewerInit, 1000);
return;
}
} else if (!["atom", "rss"].includes(MbState.platform)) {
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml('before'));
TWeb.loadMore($('.js-messages_more_wrap > a'));
}
$('form.tgme_header_search_form')[0].action = `${siteLink}/?s`;
$('form.tgme_header_search_form')[0].onsubmit = null;
$('.tgme_channel_info_header_username').html(`<a href="${siteLink}">${GetDomainFromUrl(siteLink).toLowerCase()}</a>`);
}
if (MbState.siteUrl || MbState.dataInject) {
$('a[name="goBack"]')[0].hidden = false;
}
if (["atom", "rss"].includes(MbState.platform)) {
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml());
TWeb.loadMore($('.js-messages_more_wrap > a'), MbState.siteData);
MbState.siteData = MbApiTransformer('profile', MbState.platform, MbState.siteData.querySelector(':scope > channel'));
}
MbState.siteData.iconUrl = (MbState.siteData.icon?.url || MbState.siteData.site_icon_url || MbState.siteData.icon?.img || MbState.siteData.icon?.ico);
MbState.siteData.acroName ||= (!MbState.siteData.iconUrl ? MbState.siteData.name && MakeAcroName(MbState.siteData.name) : '');
MbState.siteData.bgColor = ~~(Math.random() * 7);
if (MbState.siteData.iconUrl && !["http", "https"].includes(MbState.siteData.iconUrl.split('://')[0])) {
MbState.siteData.iconUrl = `${MbState.siteUrl}${MbState.siteData.iconUrl}`;
}
if (!MbState.siteUrl && !MbState.dataInject) {
$('a[name="goBack"]')[0].hidden = true;
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml());
TWeb.loadMore($('.js-messages_more_wrap > a'), [{ content: `<p>
Here I am, doing another strange thing of mine.
This is my personal experiment to make an MB-style frontend for sources that are by default not really friendly to that concept.
Since this first day, we will start with just WordPress, and we'll see what comes from that.
See <a href="https://octospacc.altervista.org/2024/01/13/wordpress-che-non-e/">https://octospacc.altervista.org/2024/01/13/wordpress-che-non-e/</a>.
</p>`, time: '2024-01-13T21:00' }, { content: `<p>
After fixing a few post-release issues driving me insane (scrolling cough cough), here are some new improvements:
<br/> * Handling of posts without date is just a bit nicer.
<br/> * Added a back button to return to this page here from a real site stream.
<br/> * Added CORS proxies to handle sites normally inaccessible.
<br/> * Hey there, this text and everything is new...
<br/>
I also just now realized that wordpress.com uses a different REST API with different endpoints and parameters,
so I will need to handle that...
</p>`, time: '2024-01-14T02:00' }, { content: `<p>
New changes:
<br/> * Correctly handle wordpress.com blogs
<br/> * Show specific users as post authors whenever possible
<br/> * Made the navigation bar smarter: now handles URLs without schema, and t.me links (redirects to official site)
<br/> * Made the info box (right column on desktop) visible on small screens (by clicking the screen header)
<br/> * Added an Altervista workaround for videos not loading (bypass anti-hotlinking)
<br/> * Made URL hash parameter names case-insensitive
<br/> * Now sites without an icon will display a random color and their acronicized name
<br/> * Hopefully fixed all the scrolling-loading issues for real this time...
</p>`, time: '2024-01-15T01:00' }, { content: `<p>
New changes:
<br/> * Adapt newly-added icons for dark mode
<br/> * Improved visualization of info column for small screens
<br/> * Improved video anti-hotlinking bypass, added fullscreen button for browsers which wouldn't otherwise show the native one
<br/> * Allow opening the stream at the point in time of a specific post ID for a website
</p>`, time: '2024-01-16T00:00' }, { content: `<p>
I was thinking this tool would now just start to die,
since I should try to get some time to develop my actual well-made and non-kanged frontend,
but I will need a few libraries and things first, that I can actually already start developing and introduce here.
<br/>
So, here are some new changes:
<br/> * Fixed video embed fullscreen, and added a reload button in case load fails
<br/> * Initial support for handling data via Trasformapi lib
<br/> * Initial, experimental support for RSS feeds specifically, via Trasformapi (very broken)
</p>`, time: '2024-01-23T01:00' }, { content: `<p>
New changes:
<br/> * Updated Trasformapi.js with misc fixes, query constants, and streamlined/powerful data querying
(XPath support for both XML sources, and JSON sources via defiant.js)
<br/> * Only slightly better RSS support
<br/> * Initial, experimental support for Mastodon profiles (broken)
<br/> * Hotfixed a defiant parsing bug on Firefox
</p>`, time: '2024-01-24T01:00' }, { content: `<p>
New changes:
<br/> * Read Telegram's JSON chat exports (experimental, very slow and resource-heavy)
<br/>
Regarding Trasformapi, I transformed some of my development tears into words, read here if you're curious:
<a href="https://octospacc.altervista.org/2024/01/25/mbviewer-per-distrarci/">https://octospacc.altervista.org/2024/01/25/mbviewer-per-distrarci/</a>.
</p>`, time: '2024-01-25T01:00' }, { content: `<p>
Some small things:
<br/> * Fixed RSS feeds parsing on Firefox (mentioned in the post linked above), by fixing a bug in Trasformapi
<br/> * HTML is now sanitized for removal of dangerous tags and attributes before displaying
<br/> * Support including user-defined CSS rules from URL (<code>data:</code> supported) via the <code>includeStyle</code> argument
</p>`, time: '2024-01-27T20:00' }, { content: `<p>
New changes:
<br/> * Support including user-defined JS scripts from URL (<code>data:</code> supported) via the <code>includeScript</code> argument. A script must expose a <code>MbViewerFunction(data)</code> function to be invoked by the main application to do useful operations, and then return data by calling the <code>MbViewerReturn(data)</code> API function.
<br/>
...I will probably need to write actual documentation about this, but for sure I will post about this on <a href="https://octospacc.altervista.org/?p=1416">https://octospacc.altervista.org/?p=1416</a>.
</p>`, time: '2024-02-01T00:00' }, { content: `<p>
Updates:
<br/> * Include special CSS for optimized PDF/paper printing
</p>`, time: '2024-02-05T11:00' }, { content: `<p>
Copyright notice: MBViewer uses code borrowed from <a href="https://t.me">t.me</a>,
specially modified to handle customized data visualizations in an MB-style.
<br/>
This webapp is not affiliated with Telegram (Telegram FZ LLC, Telegram Messenger Inc.), and
all rights upon the original materials (which are: everything not strictly related to the "MBViewer" mod) belong to the original owners.
</p>` }]);
}
document.title = `${MbState.siteData.name} — 👁️‍🗨️️ MBViewer`;
$('.tgme_page_photo_image').attr('data-content', MbState.siteData.acroName);
$('.tgme_header_title, .tgme_channel_info_header_title').html(MbState.siteData.name);
$('.tgme_channel_info_description').html(MbState.siteData.description);
if (MbState.siteData.iconUrl) {
$('.tgme_page_photo_image').html(`<img src="${MbState.siteData.iconUrl}"/>`);
} else {
$('.tgme_page_photo_image').addClass(`bgcolor${MbState.siteData.bgColor}`);
}
}
function RefreshIncludeStyle () {
document.querySelector('link[href][rel="stylesheet"]#MbViewerIncludeStyle')?.remove();
if (MbState.args.includestyle) {
const linkElem = document.createElement('link');
linkElem.id = 'MbViewerIncludeStyle';
linkElem.rel = 'stylesheet';
linkElem.href = MbState.args.includestyle;
document.body.appendChild(linkElem);
}
}
function RefreshIncludeScript () {
document.querySelector('iframe#MbViewerIncludeScript')?.remove();
if (MbState.args.includescript) {
const frameElement = document.createElement('iframe');
frameElement.id = 'MbViewerIncludeScript';
frameElement.sandbox = 'allow-scripts';
frameElement.src = `data:text/html;utf8,
<script>
function MbViewerReturn (data) {
/* data.type = 'IncludeScriptResult'; */
window.top.postMessage({ MbViewer: data }, '*');
}
</script>
<script src="${MbState.args.includescript}"></script>
<script>
window.addEventListener('message', function(event){
MbViewerFunction(event.data.MbViewer);
});
</script>
`;
frameElement.hidden = true;
document.body.appendChild(frameElement);
}
}
function MakeMoreWrapperHtml (wrapType) {
let offset, order, relativeOpts;
switch (wrapType) {
case 'after':
offset = MbState.lastPostOffsetAfter;
MbState.lastPostOffsetAfter--;
order = 'asc';
break;
case 'before':
offset = MbState.lastPostOffsetBefore;
MbState.lastPostOffsetBefore++;
order = 'desc';
break;
}
if (MbState.startingPost) {
relativeOpts = { order: order, exclude: (MbState.startingPost.id || MbState.startingPost.ID) };
relativeOpts[wrapType] = MbState.startingPost.date;
}
return `<div class="tgme_widget_message_centered js-messages_more_wrap">
<a href="${MbState.siteUrl && !["atom", "rss"].includes(MbState.platform) && MakeSiteRestUrl(MakeApiEndpoint('posts', { count: 1, offset: offset, orderBy: "date", ...(MbState.startingPost && relativeOpts) }))}" data-${wrapType}="" class="tme_messages_more js-messages_more"></a>
</div>`;
}
async function MakeMbHtml (postData, makeMoreWrap) {
postData = (typeof(postData) === 'string' ? JSON.parse(postData) : postData);
if (["atom", "rss"].includes(MbState.platform)) {
postData = Array.from(postData.querySelectorAll(':scope > channel > item'));
}
if (["atom", "rss", "mastodon"].includes(MbState.platform)) {
postData.reverse();
}
let html = '';
const siteLink = (MbState.siteData.url || MbState.siteData.URL || MbState.siteLink);
const siteHref = (siteLink ? `href="${siteLink}"` : '');
for (postData of (postData.posts ? postData.posts : SureArray(postData))) {
if (MbState.platform && MbState.platform !== 'telegram.export') {
postData = MbApiTransformer('message', MbState.platform, postData);
}
const authorId = (postData.author?.id || postData._links?.author[0]?.href?.split('/')?.slice(-1)[0]);
if (authorId && !MbState.authors[authorId]) {
MbState.authors[authorId] = (typeof(postData.author) === 'object' && Object.keys(postData.author).join(' ') !== 'id'
? postData.author
: await (await fetch(MakeSiteRestUrl(MakeApiEndpoint('users', { id: authorId })))).json());
}
const authorData = (MbState.authors[authorId] || postData.author || (postData.quoting?.author && !postData.quoting?.content));
const authorLink = (authorData?.link || (siteLink && `${siteLink}/author/${authorData?.name}`));
const authorHref = (authorLink ? `href="${authorLink}"` : '');
const iconUrl = (Object.values(authorData?.avatar_urls || {}).slice(-1)[0] || authorData?.icon?.url || MbState.siteData.iconUrl);
let attachmentsHtml = '';
for (const attachment of (postData.attachments || postData.quoting?.attachments || [])) {
if (attachment) {
const mediaKind = attachment.type?.split('/')[0];
const elemTag = (mediaKind === 'image' ? 'img' : mediaKind);
const elemClosing = (mediaKind === 'image' ? '/>' : `></${elemTag}>`);
attachmentsHtml += `<${elemTag} controls="true" src="${attachment.url}" alt="${attachment.description?.replaceAll('&', '&amp;')?.replaceAll('"', '&quot;') || ''}"/>`;
}
}
const postInternalId = MbState.internalIdCount++;
const postInnerHtml = `
${attachmentsHtml}
${ReformatPostHtml(postData.content)}
${postData.quoting?.content ? `[♻️ Reblog]: ${ReformatPostHtml(postData.quoting.content)}` : ''}
`;
html += `
<div class="tgme_widget_message_wrap js-widget_message_wrap date_visible">
<div class="tgme_widget_message text_not_supported_wrap js-widget_message" data-post="${postData.id || postData.ID}">
<div class="tgme_widget_message_user">
<a ${authorHref || siteHref}>
<i class="tgme_widget_message_user_photo ${iconUrl ? '' : `bgcolor${MbState.siteData.bgColor}`}" style="background-color: unset;" data-content="${MbState.siteData.acroName}">
${iconUrl ? `<img src="${iconUrl}"/>` : ''}
</i>
</a>
</div>
<div class="tgme_widget_message_bubble">
<i class="tgme_widget_message_bubble_tail">
<svg class="bubble_icon" width="9px" height="20px" viewBox="0 0 9 20">
<g fill="none">
<path class="background" fill="#ffffff" d="M8,1 L9,1 L9,20 L8,20 L8,18 C7.807,15.161 7.124,12.233 5.950,9.218 C5.046,6.893 3.504,4.733 1.325,2.738 L1.325,2.738 C0.917,2.365 0.89,1.732 1.263,1.325 C1.452,1.118 1.72,1 2,1 L8,1 Z"></path>
<path class="border_1x" fill="#d7e3ec" d="M9,1 L2,1 C1.72,1 1.452,1.118 1.263,1.325 C0.89,1.732 0.917,2.365 1.325,2.738 C3.504,4.733 5.046,6.893 5.95,9.218 C7.124,12.233 7.807,15.161 8,18 L8,20 L9,20 L9,1 Z M2,0 L9,0 L9,20 L7,20 L7,20 L7.002,18.068 C6.816,15.333 6.156,12.504 5.018,9.58 C4.172,7.406 2.72,5.371 0.649,3.475 C-0.165,2.729 -0.221,1.464 0.525,0.649 C0.904,0.236 1.439,0 2,0 Z"></path>
<path class="border_2x" d="M9,1 L2,1 C1.72,1 1.452,1.118 1.263,1.325 C0.89,1.732 0.917,2.365 1.325,2.738 C3.504,4.733 5.046,6.893 5.95,9.218 C7.124,12.233 7.807,15.161 8,18 L8,20 L9,20 L9,1 Z M2,0.5 L9,0.5 L9,20 L7.5,20 L7.5,20 L7.501,18.034 C7.312,15.247 6.64,12.369 5.484,9.399 C4.609,7.15 3.112,5.052 0.987,3.106 C0.376,2.547 0.334,1.598 0.894,0.987 C1.178,0.677 1.579,0.5 2,0.5 Z"></path>
<path class="border_3x" d="M9,1 L2,1 C1.72,1 1.452,1.118 1.263,1.325 C0.89,1.732 0.917,2.365 1.325,2.738 C3.504,4.733 5.046,6.893 5.95,9.218 C7.124,12.233 7.807,15.161 8,18 L8,20 L9,20 L9,1 Z M2,0.667 L9,0.667 L9,20 L7.667,20 L7.667,20 L7.668,18.023 C7.477,15.218 6.802,12.324 5.64,9.338 C4.755,7.064 3.243,4.946 1.1,2.983 C0.557,2.486 0.52,1.643 1.017,1.1 C1.269,0.824 1.626,0.667 2,0.667 Z"></path>
</g>
</svg>
</i>
<div class="tgme_widget_message_author accent_color">
<a class="tgme_widget_message_owner_name" ${authorHref || siteHref}>
<span dir="auto">
${authorData?.name
? `${authorData.name} [${MbState.siteData.name}]`
: MbState.siteData.name
}
</span>
</a>
</div>
<div class="tgme_widget_message_text js-message_text before_footer" dir="auto">
<div class="MbPost" data-internal-id="${postInternalId}">
${postInnerHtml}
</div>
</div>
<div class="tgme_widget_message_footer compact js-message_footer">
<div class="tgme_widget_message_info short js-message_info">
<span class="tgme_widget_message_meta">
<a class="tgme_widget_message_date" ${postData.url ? `href="${postData.url}"` : ''}>
<time datetime="${postData.time}" class="time"></time>
<!-- TODO: show edited status -->
</a>
</span>
</div>
</div>
</div>
</div>
</div>
`;
if (document.querySelector('iframe#MbViewerIncludeScript')) {
function pollPostElement () {
const postElement = document.querySelector(`div.tgme_widget_message_text > div.MbPost[data-internal-id="${postInternalId}"]`);
if (postElement) {
document.querySelector('iframe#MbViewerIncludeScript').contentWindow.postMessage({ MbViewer: { id: postInternalId, html: postInnerHtml } }, '*');
} else {
setTimeout(pollPostElement, 75);
}
}
pollPostElement();
}
}
if (!html) {
// no more messages?
return;
}
if (makeMoreWrap && MbState.siteUrl) {
const wrapHtml = MakeMoreWrapperHtml(makeMoreWrap);
switch (makeMoreWrap) {
case 'after': html = `${html}${wrapHtml}`; break;
case 'before': html = `${wrapHtml}${html}`; break;
}
}
MbState.lastMoreWrap = makeMoreWrap;
return html;
}
function ReformatPostHtml (html) {
const content = $(`<div>${cleanHTML(html, false)}</div>`);
// bypass Altervista's anti-hotlinking protection by hiding our HTTP Referer header
// TODO: only do this for altervista sites maybe
if (MbState.platform === 'wordpress.org') {
for (const videoElem of content.find('video').toArray()) {
videoElem.preload = 'none';
const frameElem = document.createElement('iframe');
frameElem.style = 'border: none; width: 100%;';
frameElem.allowFullscreen = true;
frameElem.src = `data:text/html;utf8,<!DOCTYPE html><body>
<style>
html, body { margin: 0; overflow: hidden; }
video { max-width: 100%; }
</style>
${encodeURIComponent(videoElem.outerHTML)}
<button style="position: absolute; top: 0; right: 0; z-index: 1;">
Reload Media
</button>
<script>
var videoElem = document.querySelector('video');
var buttonElem = document.querySelector('button');
buttonElem.onclick = function(){
videoElem.load();
};
videoElem.onloadedmetadata = function(){
window.top.postMessage((videoElem.src + ' ' + getComputedStyle(videoElem).height), '*');
};
videoElem.load();
</script>
</body>`;
videoElem.replaceWith(frameElem);
}
}
return content.html();
}
function HideMobileRightColumn () {
$('body')[0].style.overflow = '';
$('main.tgme_main')[0].style.visibility = '';
$('.tgme_header_search')[0].style.display = '';
$('.tgme_header_right_column')[0].style.display = '';
$('.tgme_header_right_column')[0].style.width = '';
$('.tgme_header_right_column .tgme_channel_info')[0].style.height = '';
$('.tgme_header_right_column a[name="closeColumn"]')[0].hidden = true;
}
function ResizeLayouts () {
if (window.innerWidth <= 720 ) { // .tgme_header_right_column @media max-width
$('a.tgme_header_link')[0].href = 'javascript:;';
} else {
HideMobileRightColumn();
$('a.tgme_header_link')[0].removeAttribute('href');
}
}
$('a[name="goBack"]')[0].onclick = function(){
ArgsRewrite({ dataurl: null, siteurl: null, postid: null, platform: null, includestyle: null, includescript: null /*postslug: null*/ });
};
// TODO: we should check origin
window.addEventListener('message', function(event){
// TODO edit the video embed function to send objects for consistency
if (typeof(event.data) === 'string') {
const tokens = event.data.split(' ');
$(`iframe[src*="${encodeURIComponent(tokens[0])}"]`).height(tokens[1]);
}
else if (event.data.MbViewer) {
const data = event.data.MbViewer;
//switch (data.type) {
// case 'IncludeScriptResult':
document.querySelector(`div.tgme_widget_message_text > div.MbPost[data-internal-id="${parseInt(data.id)}"]`).innerHTML = cleanHTML(data.html);
// break;
//}
}
});
window.addEventListener('resize', ResizeLayouts);
window.addEventListener('hashchange', MbViewerInit);

View File

@@ -0,0 +1,130 @@
const MbViewerTrasformapiSchema = `<schema>
<set
rss-media-query="*[name()='media:content' or name()='enclosure']"
/>
<!-- WIP, find out how to structure this -->
<endpoint name="messages">
<method name="GET" args="" returns="message[]"/>
<!-- ... -->
</endpoint>
<entity name="message">
<prop name="id" type="int">
<content upstream="rss" query="./guid"/>
<content upstream="wordpress.com" query="//ID"/>
<content upstream="wordpress.org" query="//id"/>
<content upstream="mastodon" query="//id"/>
</prop>
<prop name="url" type="string">
<content upstream="rss" query="./link"/>
<content upstream="wordpress.com" query="//URL"/>
<content upstream="wordpress.org" query="//link"/>
<content upstream="mastodon" query="//url"/>
</prop>
<prop name="title" type="string">
<content upstream="rss" query="title"/>
<content upstream="wordpress.com" query="//title"/>
<content upstream="wordpress.org" query="//title/rendered"/>
</prop>
<prop name="content" type="string">
<!-- TODO optional multiple 'query' attrs -->
<!--<content upstream="rss" query="content:encoded"/>-->
<content upstream="rss" query="./description"/>
<content upstream="wordpress.com" query="//content"/>
<content upstream="wordpress.org" query="//content/rendered"/>
<content upstream="mastodon" query="//content"/>
</prop>
<prop name="attachments" type="file[]">
<content upstream="rss">
<prop name="url" query="{rss-media-query}/@url"/>
<prop name="type" query="{rss-media-query}/@type"/>
<prop name="description" query="{rss-media-query}/*[name()='media:description']"/>
</content>
<!--
<content upstream="mastodon">
<prop name="url" query="media_attachments.url"/>
<prop name="type" query="media_attachments.type"/>
</content>
-->
<content upstream="mastodon" query="//media_attachments"/>
</prop>
<prop name="author" type="profile">
<content upstream="rss"/>
<content upstream="wordpress.com" query="//author"/>
<content upstream="wordpress.org"/>
<content upstream="mastodon" query="//account"/>
</prop>
<prop name="time" type="string">
<content upstream="rss" query="pubDate"/>
<content upstream="wordpress.com" query="//date"/>
<content upstream="wordpress.org" query="//date"/>
<content upstream="mastodon" query="//created_at"/>
</prop>
<prop name="revisions" type="revision[]">
<content upstream="wordpress.com"/>
<content upstream="wordpress.org"/>
<content upstream="mastodon"/>
</prop>
<prop name="quoting" type="message">
<content upstream="mastodon" query="//reblog"/>
</prop>
<!--<prop name="replying" type="message">
<content upstream="mastodon" query=""/>
</prop>-->
</entity>
<entity name="revision">
<prop name="time" type="string">
<content upstream="wordpress.com" query="//modified"/>
<content upstream="wordpress.org" query="//modified"/>
<content upstream="mastodon" query="//edited_at"/>
</prop>
</entity>
<!-- TODO (for wordpress) how to handle both authors and sites as a profile type? maybe add a 'variant' attr for 'content' tags? -->
<entity name="profile">
<prop name="id" type="int"> <!-- TODO fix type -->
<content upstream="rss" query="link"/>
<content upstream="wordpress.com" query="//ID"/>
<content upstream="wordpress.org" query="//author"/>
</prop>
<prop name="url" type="string">
<content upstream="rss" query="link"/>
<content upstream="wordpress.com" query="//profile_URL"/>
</prop>
<prop name="name" type="string">
<content upstream="rss" query="*[name()='title' or name()='dc:creator']"/>
<content upstream="wordpress.com" query="//name"/>
<content upstream="mastodon" query="//title"/>
</prop>
<prop name="description" type="string">
<content upstream="rss" query="description"/>
<content upstream="mastodon" query="//description"/>
</prop>
<prop name="icon" type="file">
<content upstream="rss">
<prop name="url" query="image/url"/>
</content>
<content upstream="wordpress.com">
<prop name="url" query="//avatar_URL"/>
</content>
<content upstream="mastodon"> <!-- TODO read user avatars -->
<!--<prop name="url" query="//thumbnail/url"/>-->
<prop name="url" query="//contact/account/avatar"/>
</content>
</prop>
</entity>
<entity name="file">
<prop name="url" type="string">
<content upstream="mastodon" query="//url"/>
</prop>
<prop name="type" type="string">
<content upstream="mastodon" query="//type"/>
</prop>
<prop name="description" type="string"/>
</entity>
</schema>`;

9
static/MBViewer/js/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
static/MBViewer/js/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,379 @@
(function($) {
$.fn.redraw = function() {
return this.map(function(){ this.offsetTop; return this; });
};
$.fn.scrollIntoView = function(options) {
options = options || {}
return this.first().each(function() {
var position = options.position || 'auto',
padding = options.padding || 0,
duration = options.duration || 0;
var $item = $(this),
$cont = $item.scrollParent(),
scrollTop = $cont.scrollTop(),
positionTop = 0,
paddingTop = 0,
itemHeight = $item.outerHeight(),
isBody = false;
if ($cont.get(0) === document) {
isBody = true;
$cont = $(window);
positionTop = $item.offset().top;
paddingTop = $('header').height() + 1;
} else {
positionTop = $item.offset().top - $cont.offset().top + scrollTop;
}
if (options.slidedEl) {
if (options.slidedEl === 'this') {
options.slidedEl = this;
}
$(options.slidedEl, this).each(function() {
itemHeight += (this.scrollHeight - this.clientHeight);
});
}
var itemTop = positionTop,
itemBottom = itemTop + itemHeight,
contHeight = $cont.height(),
contTop = scrollTop + padding + paddingTop,
contBottom = scrollTop + contHeight - padding,
scrollTo = null;
if (position == 'auto') {
if (itemTop < contTop) {
scrollTo = itemTop - padding - paddingTop;
} else if (itemBottom > contBottom) {
if (itemHeight > contHeight - padding - padding) {
scrollTo = itemTop - padding - paddingTop;
} else {
scrollTo = itemBottom - contHeight + padding;
}
}
} else if (position == 'top' || position == 'center') {
if (contHeight > itemHeight) {
padding = (contHeight - paddingTop - itemHeight) / 2;
}
scrollTo = itemTop - padding - paddingTop;
} else if (position == 'bottom') {
if (itemHeight > contHeight - padding - padding) {
scrollTo = itemTop - padding - paddingTop;
} else {
scrollTo = itemBottom - contHeight + padding;
}
}
if (scrollTo) {
if (duration) {
if (isBody) {
$cont = $('html');
}
$cont.stop().animate({scrollTop: scrollTo}, duration);
} else {
$cont.scrollTop(scrollTo);
}
}
});
};
})(jQuery);
function doesSupportThinBoxShadow() {
if (!window.getComputedStyle) return;
var div = document.createElement('div');
div.style.boxShadow = '0 0 0 0.5px black';
div.style.display = 'none';
document.body.appendChild(div);
var box_shadow = window.getComputedStyle(div).boxShadow;
document.body.removeChild(div);
return box_shadow.indexOf('0.5') >= 0;
}
function formatDate(datetime) {
var date = new Date(datetime);
var cur_date = new Date();
var j = date.getDate();
var M = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][date.getMonth()];
var Y = date.getFullYear();
if (cur_date.getFullYear() == date.getFullYear()) {
return M + ' ' + j;
}
if (!j && !M && !Y) {
return 'Undefined';
}
return M + ' ' + j + ', ' + Y;
}
function getCssProperty(el, prop) {
if (window.getComputedStyle) {
return window.getComputedStyle(el, '').getPropertyValue(prop) || null;
} else if (el.currentStyle) {
return el.currentStyle[prop] || null;
}
return null;
}
function isVisible(el, padding) {
var node = el, val;
var visibility = getCssProperty(node, 'visibility');
if (visibility == 'hidden') return false;
while (node) {
if (node === document.documentElement) break;
var display = getCssProperty(node, 'display');
if (display == 'none') return false;
var opacity = getCssProperty(node, 'opacity');
if (opacity !== null && opacity < 0.1) return false;
node = node.parentNode;
}
if (el.getBoundingClientRect) {
padding = +padding || 0;
var rect = el.getBoundingClientRect();
var html = document.documentElement;
if (rect.bottom < padding ||
rect.right < padding ||
rect.top > (window.innerHeight || html.clientHeight) - padding ||
rect.left > (window.innerWidth || html.clientWidth) - padding) {
return false;
}
}
return true;
}
var TWeb = {
init: function(options) {
options = options || {};
if (!doesSupportThinBoxShadow()) {
$('body').addClass('thin_box_shadow');
}
$('.js-widget_message').each(function() {
TPost.init(this);
});
TWeb.updateServiceDate($('.js-widget_message_wrap'));
if (options.scrollToPost) {
TWeb.highlightPost(options.scrollToPost, true);
} else {
$('.js-widget_message_wrap').last().scrollIntoView({position: 'top'});
}
$('body').removeClass('no_transitions');
$('.js-header_search').on('focus', function() {
$('header.tgme_header').removeClass('search_collapsed');
$(this).select();
});
$('.js-header_search').on('blur', function() {
$('header.tgme_header').addClass('search_collapsed');
});
TWeb.initScroll();
TWeb.initViews();
if (window.matchMedia) {
var darkMedia = window.matchMedia('(prefers-color-scheme: dark)');
TWeb.toggleTheme(darkMedia.matches);
darkMedia.addListener(function(e) {
TWeb.toggleTheme(e.matches);
});
}
if (true) { // wallpaper supported
$('body').addClass('twallpaper');
TWallpaper.init($('#tgme_background').get(0));
$(window).on('focus', function() { // chrome fix
TWallpaper.update();
});
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
reduceMotion.addEventListener('change', function() {
TWallpaper.scrollAnimate(!reduceMotion.matches);
});
TWallpaper.scrollAnimate(!reduceMotion.matches);
}
},
toggleTheme: function(dark) {
$('html').toggleClass('theme_dark', dark);
},
initScroll: function() {
var $document = $(document);
$document.on('scroll', function() {
$before = $('.js-messages_more[data-before]');
$after = $('.js-messages_more[data-after]');
var wheight = $(window).height();
var scrollTop = $(window).scrollTop();
if ($before.length) {
var bottom = $before.offset().top + $before.height() - scrollTop;
if (bottom > -wheight * 3) {
TWeb.loadMore($before);
}
}
if ($after.length) {
var top = $after.offset().top - scrollTop;
if (top < wheight * 3) {
TWeb.loadMore($after);
}
}
});
$document.on('click', '.js-messages_more', function() {
var $el = $(this);
TWeb.loadMore($el);
});
},
initViews: function() {
TWeb.viewsMap = {};
TWeb.viewsQueue = [];
TWeb.viewsLastLoad = 0;
var $document = $(document), $window = $(window);
$document.ready(function() {
$window.on('scroll resize', TWeb.checkVisiblePosts);
TWeb.checkVisiblePosts();
});
},
checkVisiblePosts: function() {
$('.js-widget_message[data-view]').each(function() {
if (isVisible(this, 50)) {
var view = this.getAttribute('data-view');
if (view) {
TWeb.addViewToQueue(view);
}
this.removeAttribute('data-view');
}
});
},
addViewToQueue: function(view) {
if (!TWeb.viewsMap[view]) {
TWeb.viewsMap[view] = true;
TWeb.viewsQueue.push(view);
TWeb.sendViewsMaybe();
}
},
sendViewsMaybe: function() {
var now = +(new Date);
if (now - TWeb.viewsLastLoad < 10000 && TWeb.viewsQueue.length < 50) {
return setTimeout(TWeb.sendViewsMaybe, 10000);
}
if (TWeb.viewsQueue.length > 0) {
var views = TWeb.viewsQueue.join(';');
TWeb.viewsQueue = [];
$.ajax('/v/', {type: 'POST', data: {views: views}});
TWeb.viewsLastLoad = now;
}
},
highlightPost: function(post_id, scroll) {
var $postWrap = $('.js-widget_message[data-post="' + post_id + '"]').parents('.js-widget_message_wrap');
if (scroll) {
$postWrap.scrollIntoView({position: 'top'});
}
$postWrap.addClass('prepare_highlight').redraw().addClass('highlight');
setTimeout(function() {
$postWrap.removeClass('highlight');
setTimeout(function() {
$postWrap.removeClass('prepare_highlight');
}, 300);
}, 1500);
},
updateServiceDate: function($wrapEls, skip_first) {
$wrapEls.each(function() {
if (!$(this).data('msg_date')) {
var datetime = $('time[datetime]', this).attr('datetime');
if (datetime) {
var date_formatted = formatDate(datetime);
$('<div class="tgme_widget_message_service_date_wrap"><div class="tgme_widget_message_service_date">' + date_formatted + '</div></div>').appendTo(this);
$(this).data('msg_date', date_formatted);
}
}
});
var len = $wrapEls.size();
for (var i = len - 1; i >= 0; i--) {
var $wrapEl = $wrapEls.eq(i);
var $prevWrapEl = i > 0 ? $wrapEls.eq(i - 1) : null;
if (!$prevWrapEl && skip_first) continue;
var date_visible = !$prevWrapEl || $prevWrapEl.data('msg_date') != $wrapEl.data('msg_date');
$wrapEl.toggleClass('date_visible', date_visible);
}
},
loadMore: function($moreEl, dataOverride) {
var loading = $moreEl.data('loading');
if (loading) {
return false;
}
var wrapType = '';
var after = $moreEl.attr('data-after');
var before = $moreEl.attr('data-before');
if (after !== undefined) {
wrapType = 'after';
} else if (before !== undefined) {
wrapType = 'before';
}
var url = $moreEl.attr('href');
$moreEl.data('loading', true);
$moreEl.addClass('dots-animated');
var _load = function(url, before, after) {
if (dataOverride) {
_loadContinue(dataOverride);
} else if (url) {
$.ajax(url, {
success: function(data) { _loadContinue(data) },
error: function(data) {
var timeout = $moreEl.data('timeout') || 1000;
$moreEl.data('timeout', timeout > 60000 ? timeout : timeout * 2);
setTimeout(function(){ _load(url, before, after); }, timeout);
}
});
}
};
var _loadContinue = async function(data) {
var messageHistoryCountBefore = $('section.tgme_channel_history.js-message_history .tgme_widget_message_wrap.js-widget_message_wrap').length;
var [initialHtmlScroll, initialHtmlHeight] = [$('html').scrollTop(), $('html').height()];
var $data = $(await MakeMbHtml(data, wrapType));
var $helper = $('<div class="tgme_widget_messages_helper"></div>');
$helper.append($data);
$('.js-message_history').append($helper);
$helper.find('.js-widget_message').each(function() {
TPost.init(this);
});
$helper.remove();
var wrapEls = $data.filter('.js-widget_message_wrap').get();
var $moreElWrap = $moreEl.parents('.js-messages_more_wrap');
if (before) {
var firstWrapEl = $moreElWrap.next('.js-widget_message_wrap').get();
var $wrapEls = $(wrapEls.concat(firstWrapEl));
TWeb.updateServiceDate($wrapEls);
var y = $moreElWrap.offset().top + $moreElWrap.outerHeight(true) - $(document).scrollTop();
$data.insertBefore($moreElWrap);
var st = $moreElWrap.offset().top - y;
$moreElWrap.remove();
$(window).scrollTop(st);
} else {
var lastWrapEl = $moreElWrap.prev('.js-widget_message_wrap').get();
var $wrapEls = $(lastWrapEl.concat(wrapEls));
TWeb.updateServiceDate($wrapEls, lastWrapEl.length > 0);
$data.insertBefore($moreElWrap);
$moreElWrap.remove();
}
// load more messages if the current viewport is not tall enough to be scrolled
if (!IsScrollableY($('html')[0])) {
MbState.wasEverNonScrollable = true;
MbState.lastMustScroll = true;
TWeb.loadMore($('.js-messages_more_wrap > a'));
return;
}
if (MbState.lastMustScroll) {
return _scrollToLastMessage();
}
if (MbState.wasEverNonScrollable) {
return _scrollToLastMessage();
}
if (wrapType === 'before') {
$('html').scrollTop(initialHtmlScroll + $('html').height() - initialHtmlHeight);
}
if (MbState.startingPost && messageHistoryCountBefore === 1) {
TWeb.highlightPost(MbState.startingPost?.id || MbState.startingPost?.ID);
}
};
var _scrollToLastMessage = function() {
//$('#BottomAnchor')[0].scrollIntoView();
var lastMessageElem = $('.tgme_widget_message_wrap').last()[0];
lastMessageElem.scrollIntoView();
// scroll a bit more to show the message nicely if it's taller than viewport
if (lastMessageElem.clientHeight > ($('html')[0].clientHeight - 48)) {
$('html')[0].scrollTop -= 48;
}
MbState.lastMustScroll = false;
};
// avoid automatic infinite upscrolling
if ($('html')[0].scrollTop === 0) {
$('html')[0].scrollTop = 16;
}
_load(url, before, after);
},
}
window.TWeb = TWeb;

View File

@@ -0,0 +1,808 @@
var RLottie = (function () {
var rlottie = {}, apiInitStarted = false, apiInited = false, initCallbacks = [];
var deviceRatio = window.devicePixelRatio || 1;
var startTime = +(new Date());
function dT() {
return '[' + ((+(new Date()) - startTime)/ 1000.0) + '] ';
}
rlottie.Api = {};
rlottie.players = Object.create(null);;
rlottie.WORKERS_LIMIT = 4;
var reqId = 0;
var mainLoopAf = false;
var mainLoopTo = false;
var mainLoopInited = false;
var checkViewportDate = false;
var lastRenderDate = false;
var userAgent = window.navigator.userAgent;
var isSafari = !!window.safari ||
!!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
var isRAF = isSafari;
rlottie.isSafari = isSafari;
function wasmIsSupported() {
try {
if (typeof WebAssembly === 'object' &&
typeof WebAssembly.instantiate === 'function') {
const module = new WebAssembly.Module(Uint8Array.of(
0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
));
if (module instanceof WebAssembly.Module) {
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
}
} catch (e) {}
return false;
}
function isSupported() {
return (
wasmIsSupported() &&
typeof Uint8ClampedArray !== 'undefined' &&
typeof Worker !== 'undefined' &&
typeof ImageData !== 'undefined'
);
}
rlottie.isSupported = isSupported();
function mainLoop() {
var key, rlPlayer, delta, rendered;
var isEmpty = true;
var now = +Date.now();
var checkViewport = !checkViewportDate || (now - checkViewportDate) > 1000;
for (key in rlottie.players) {
rlPlayer = rlottie.players[key];
if (rlPlayer &&
rlPlayer.frameCount) {
delta = now - rlPlayer.frameThen;
if (delta > rlPlayer.frameInterval) {
rendered = render(rlPlayer, checkViewport);
if (rendered) {
lastRenderDate = now;
}
}
}
}
// var delay = !lastRenderDate || now - lastRenderDate < 100 ? 16 : 500;
var delay = 16;
if (delay < 20 && isRAF) {
mainLoopAf = requestAnimationFrame(mainLoop)
} else {
mainLoopTo = setTimeout(mainLoop, delay);
}
mainLoopInited = true;
if (checkViewport) {
checkViewportDate = now;
}
}
function setupMainLoop() {
var isEmpty = true, forceRender = false, rlPlayer;
for (key in rlottie.players) {
rlPlayer = rlottie.players[key];
if (rlPlayer &&
rlPlayer.frameCount) {
if (rlPlayer.forceRender) {
forceRender = true;
}
isEmpty = false;
break;
}
}
if (mainLoopInited === isEmpty || forceRender) {
mainLoopAf && cancelAnimationFrame(mainLoopAf);
mainLoopTo && clearTimeout(mainLoopTo);
mainLoopInited = false;
if (!isEmpty) {
if (isRAF) {
mainLoopAf = requestAnimationFrame(mainLoop);
} else {
mainLoopTo = setTimeout(mainLoop, 0);
}
mainLoopInited = true;
}
}
}
function initApi(callback) {
if (apiInited) {
callback && callback();
} else {
callback && initCallbacks.push(callback);
if (!apiInitStarted) {
console.log(dT(), 'tgsticker init');
apiInitStarted = true;
QueryableWorkerProxy.init('/js/tgsticker-worker.js?14', rlottie.WORKERS_LIMIT, function() {
apiInited = true;
for (var i = 0; i < initCallbacks.length; i++) {
initCallbacks[i]();
}
initCallbacks = [];
});
}
}
}
function destroyWorkers() {
QueryableWorkerProxy.destroy();
apiInitStarted = apiInited = false;
}
function initPlayer(el, options) {
if (el.rlPlayer) return;
if (el.tagName.toLowerCase() != 'picture') {
console.warn('only picture tag allowed');
return;
}
options = options || {};
var rlPlayer = el.rlPlayer = {};
rlPlayer.thumb = el.querySelector('img');
var tgs_sources = el.querySelectorAll('source[type="application/x-tgsticker"]');
var multi_source = el.hasAttribute('data-multi-source');
var urls = [], urls_map = {};
for (var i = 0; i < tgs_sources.length; i++) {
var tgs_source = tgs_sources[i];
var url = tgs_source && tgs_source.getAttribute('srcset') || '';
var frames_align = tgs_source && tgs_source.getAttribute('data-frames-align') || '';
if (url && !urls_map[url]) {
urls_map[url] = true;
urls.push({
url: url,
framesAlign: frames_align
});
if (!multi_source) {
break;
}
}
}
if (!urls.length) {
console.warn('picture source application/x-tgsticker not found');
return;
}
var pic_width = el.clientWidth || el.getAttribute('width');
var pic_height = el.clientHeight || el.getAttribute('height');
var curDeviceRatio = options.maxDeviceRatio ? Math.min(options.maxDeviceRatio, deviceRatio) : deviceRatio;
if (!pic_width || !pic_height) {
pic_width = pic_height = 256;
}
rlPlayer.reqId = ++reqId;
rlottie.players[reqId] = rlPlayer;
rlPlayer.el = el;
rlPlayer.frameNo = false;
rlPlayer.nextFrameNo = false;
rlPlayer.frames = {};
rlPlayer.width = Math.trunc(pic_width * curDeviceRatio);
rlPlayer.height = Math.trunc(pic_height * curDeviceRatio);
rlPlayer.workerProxy = QueryableWorkerProxy.create(rlPlayer.reqId, onFrame, onLoaded);
rlPlayer.options = options;
rlPlayer.isVisible = true;
rlPlayer.paused = !!options.noAutoPlay;
rlPlayer.needPlayOnce = !!options.playOnce;
rlPlayer.needPlayUntilEnd = !!options.playUntilEnd;
rlPlayer.repeatCount = false;
rlPlayer.waitForFirstFrame = false;
rlPlayer.stopOnFirstFrame = false;
rlPlayer.stopOnLastFrame = false;
rlPlayer.forcePlayFrames = 0;
rlPlayer.times = [];
rlPlayer.imageData = new ImageData(rlPlayer.width, rlPlayer.height);
rlPlayer.workerProxy.loadFromData(urls, rlPlayer.width, rlPlayer.height);
triggerEvent(rlPlayer.el, 'tg:init');
}
function destroyPlayer(el) {
if (!el.rlPlayer) return;
var rlPlayer = el.rlPlayer;
delete rlottie.players[rlPlayer.reqId];
delete rlPlayer;
setupMainLoop();
}
function render(rlPlayer, checkViewport) {
if (!rlPlayer.canvas ||
rlPlayer.canvas.width == 0 ||
rlPlayer.canvas.height == 0) {
return false;
}
if (!rlPlayer.forceRender) {
var focused = window.isFocused ? isFocused() : document.hasFocus();
if (!focused ||
rlPlayer.paused ||
!rlPlayer.isVisible ||
!rlPlayer.frameCount) {
return false;
}
var isInViewport = rlPlayer.isInViewport;
if (isInViewport === undefined || checkViewport) {
var rect = rlPlayer.el.getBoundingClientRect();
if (rect.bottom < 0 ||
rect.right < 0 ||
rect.top > (window.innerHeight || document.documentElement.clientHeight) ||
rect.left > (window.innerWidth || document.documentElement.clientWidth)) {
isInViewport = false;
} else {
isInViewport = true;
}
rlPlayer.isInViewport = isInViewport;
}
if (!isInViewport) {
return false;
}
}
var frame = rlPlayer.frameQueue.shift();
if (frame !== null) {
doRender(rlPlayer, frame);
var nextFrameNo = rlPlayer.nextFrameNo;
if (rlPlayer.stopOnLastFrame &&
frame.no == rlPlayer.frameCount - 1) {
rlPlayer.stopOnLastFrame = false;
if (!rlPlayer.paused) {
rlPlayer.paused = true;
triggerEvent(rlPlayer.el, 'tg:pause');
}
}
if (rlPlayer.stopOnFirstFrame &&
frame.no == 0) {
if (rlPlayer.waitForFirstFrame) {
rlPlayer.waitForFirstFrame = false;
} else {
rlPlayer.stopOnFirstFrame = false;
if (!rlPlayer.paused) {
rlPlayer.paused = true;
triggerEvent(rlPlayer.el, 'tg:pause');
}
}
}
if (nextFrameNo !== false) {
rlPlayer.nextFrameNo = false;
requestFrame(rlPlayer.reqId, nextFrameNo);
}
}
return true;
}
function doRender(rlPlayer, frame) {
rlPlayer.forceRender = false;
rlPlayer.imageData.data.set(frame.frame);
rlPlayer.context.putImageData(rlPlayer.imageData, 0, 0);
rlPlayer.frameNo = frame.no;
var now = +(new Date());
if (rlPlayer.frameThen) {
rlPlayer.times.push(now - rlPlayer.frameThen)
}
rlPlayer.frameThen = now - (now % rlPlayer.frameInterval);
if (rlPlayer.thumb) {
rlPlayer.el.removeChild(rlPlayer.thumb);
delete rlPlayer.thumb;
}
// console.log(dT(), '['+rlPlayer.reqId+']', 'render frame#'+frame.no);
}
function requestFrame(reqId, frameNo) {
var rlPlayer = rlottie.players[reqId];
var frame = rlPlayer.frames[frameNo];
if (frame) {
// console.log(dT(), '['+reqId+']', 'request frame#'+frameNo+' (cache)');
onFrame(reqId, frameNo, frame);
} else {
// console.log(dT(), '['+reqId+']', 'request frame#'+frameNo+' (worker)');
rlPlayer.workerProxy.renderFrame(frameNo, !isSafari);
}
}
function onFrame(reqId, frameNo, frame) {
var rlPlayer = rlottie.players[reqId];
if (!rlPlayer || !rlPlayer.frames) {
return;
}
if (!rlPlayer.frames[frameNo] &&
(!frameNo || (rlPlayer.options.cachingModulo && ((reqId + frameNo) % rlPlayer.options.cachingModulo)))) {
rlPlayer.frames[frameNo] = new Uint8ClampedArray(frame)
}
var prevNo = frameNo > 0 ? frameNo - 1 : rlPlayer.frameCount - 1;
var lastQueueFrame = rlPlayer.frameQueue.last();
if (lastQueueFrame &&
lastQueueFrame.no != prevNo) {
return;
}
rlPlayer.frameQueue.push({
no: frameNo,
frame: frame
});
var nextFrameNo = ++frameNo;
if (nextFrameNo >= rlPlayer.frameCount) {
nextFrameNo = 0;
if (rlPlayer.times.length) {
// var avg = 0;
// for (var i = 0; i < rlPlayer.times.length; i++) {
// avg += rlPlayer.times[i] / rlPlayer.times.length;
// }
// console.log('avg time: ' + avg + ', ' + rlPlayer.fps);
rlPlayer.times = [];
}
}
if (rlPlayer.frameQueue.needsMore()) {
requestFrame(reqId, nextFrameNo)
} else {
rlPlayer.nextFrameNo = nextFrameNo;
}
}
function onLoaded(reqId, frameCount, fps) {
var rlPlayer = rlottie.players[reqId];
rlPlayer.canvas = document.createElement('canvas');
rlPlayer.canvas.width = rlPlayer.width;
rlPlayer.canvas.height = rlPlayer.height;
rlPlayer.el.appendChild(rlPlayer.canvas);
rlPlayer.context = rlPlayer.canvas.getContext('2d');
rlPlayer.fps = fps;
rlPlayer.frameInterval = 1000 / rlPlayer.fps;
rlPlayer.frameThen = Date.now();
rlPlayer.frameCount = frameCount;
rlPlayer.forceRender = true;
rlPlayer.frameQueue = new FrameQueue(fps / 4);
setupMainLoop();
requestFrame(reqId, 0);
triggerEvent(rlPlayer.el, 'tg:load');
if (frameCount > 0) {
if (rlPlayer.needPlayOnce) {
delete rlPlayer.needPlayOnce;
delete rlPlayer.needPlayUntilEnd;
rlPlayer.paused = false;
rlPlayer.stopOnFirstFrame = true;
rlPlayer.stopOnLastFrame = false;
if (rlPlayer.frameNo === false ||
rlPlayer.frameNo > 0) {
rlPlayer.waitForFirstFrame = true;
}
} else if (rlPlayer.needPlayUntilEnd) {
delete rlPlayer.needPlayOnce;
delete rlPlayer.needPlayUntilEnd;
rlPlayer.paused = false;
rlPlayer.stopOnFirstFrame = false;
rlPlayer.stopOnLastFrame = true;
}
}
if (!rlPlayer.paused) {
triggerEvent(rlPlayer.el, 'tg:play');
}
}
rlottie.init = function(el, options) {
if (!rlottie.isSupported) {
return false;
}
initApi(function() {
el && initPlayer(el, options);
});
}
rlottie.destroy = function(el) {
destroyPlayer(el);
}
rlottie.playOnce = function(el) {
if (el && el.rlPlayer) {
var rlPlayer = el.rlPlayer;
if (rlPlayer.frameCount > 0) {
rlPlayer.stopOnFirstFrame = true;
rlPlayer.stopOnLastFrame = false;
if (rlPlayer.frameNo > 0) {
rlPlayer.waitForFirstFrame = true;
}
if (rlPlayer.paused) {
rlPlayer.paused = false;
triggerEvent(el, 'tg:play');
}
} else {
rlPlayer.needPlayOnce = true;
}
}
}
rlottie.playUntilEnd = function(el) {
if (el && el.rlPlayer) {
var rlPlayer = el.rlPlayer;
if (rlPlayer.frameCount > 0) {
rlPlayer.stopOnFirstFrame = false;
rlPlayer.stopOnLastFrame = true;
if (rlPlayer.paused) {
rlPlayer.paused = false;
triggerEvent(el, 'tg:play');
}
} else {
rlPlayer.needPlayUntilEnd = true;
}
}
}
rlottie.play = function(el, reset) {
if (el && el.rlPlayer) {
if (reset) {
rlottie.reset(el);
}
el.rlPlayer.paused = false;
}
}
rlottie.pause = function(el) {
if (el && el.rlPlayer) {
el.rlPlayer.paused = true;
}
}
rlottie.reset = function(el) {
if (el && el.rlPlayer) {
var rlPlayer = el.rlPlayer;
rlPlayer.frameQueue.clear();
rlPlayer.forceRender = true;
requestFrame(rlPlayer.reqId, 0);
setupMainLoop();
}
}
rlottie.destroyWorkers = function() {
destroyWorkers();
}
return rlottie;
}());
var QueryableWorkerProxy = (function() {
var workerproxy = {};
var proxyId = 0;
var wReqId = 0;
var rObjs = {};
var wrMap = {};
var proxies = {};
var rlottieWorkers = [], curWorkerNum = 0;
var startTime = +(new Date());
function dT() {
return '[' + ((+(new Date()) - startTime)/ 1000.0) + '] ';
}
function Proxy(playerId, onFrame, onLoaded) {
this.proxyId = ++proxyId;
this.playerId = playerId;
this.onFrame = onFrame;
this.onLoaded = onLoaded;
this.items = [];
this.itemsMap = {};
proxies[this.proxyId] = this;
return this;
};
Proxy.prototype.loadFromData = function(urls, width, height) {
if (this.items.length > 0) {
console.warn('already loaded');
return;
}
this.clampedSize = width * height * 4;
for (var i = 0; i < urls.length; i++) {
var url = urls[i];
var _wReqId = ++wReqId;
var worker = rlottieWorkers[curWorkerNum++];
if (curWorkerNum >= rlottieWorkers.length) {
curWorkerNum = 0;
}
worker.sendQuery('loadFromData', _wReqId, url.url, width, height);
var item = {
reqId: _wReqId,
worker: worker,
url: url.url,
loaded: false,
clamped: new Uint8ClampedArray(this.clampedSize),
frameLoaded: {}
};
if (url.framesAlign) {
item.framesAlign = url.framesAlign;
}
this.items.push(item);
this.itemsMap[_wReqId] = item;
wrMap[_wReqId] = this.proxyId;
}
if (this.items.length > 1) {
this.canvas = document.createElement('canvas');
this.canvas.width = width;
this.canvas.height = height;
this.context = this.canvas.getContext('2d');
this.imageData = new ImageData(width, height);
}
};
Proxy.prototype.renderFrame = function(frameNo, need_clamped) {
for (var i = 0; i < this.items.length; i++) {
var item = this.items[i];
var realFrameNo = frameNo;
if (item.framesAlign == 'right') {
realFrameNo = frameNo - (this.frameCount - item.frameCount);
}
if (need_clamped) {
if(!item.clamped.length) { // fix detached
item.clamped = new Uint8ClampedArray(this.clampedSize);
}
item.worker.sendQuery('renderFrame', item.reqId, realFrameNo, item.clamped);
} else {
item.worker.sendQuery('renderFrame', item.reqId, realFrameNo);
}
// console.log(dT(), '['+this.playerId+'.'+item.reqId+']', 'request frame#'+frameNo+' (worker)');
}
};
function onFrame(wReqId, realFrameNo, frame) {
var proxyId = wrMap[wReqId];
var proxy = proxies[proxyId];
var item = proxy.itemsMap[wReqId];
var frameNo = realFrameNo;
if (item.framesAlign == 'right') {
frameNo = realFrameNo + (proxy.frameCount - item.frameCount);
}
// console.log(dT(), '['+proxy.playerId+'.'+item.reqId+']', 'onframe#'+frameNo+' (worker)');
item.frameLoaded[frameNo] = frame;
var finished = true;
for (var i = 0; i < proxy.items.length; i++) {
var item = proxy.items[i];
var loadedFrame = item.frameLoaded[frameNo];
if (!loadedFrame) {
finished = false;
break;
}
}
if (finished) {
if (proxy.items.length == 1) {
var loadedFrame = proxy.items[0].frameLoaded[frameNo];
proxy.onFrame(proxy.playerId, frameNo, loadedFrame);
delete proxy.items[0].frameLoaded[frameNo];
} else {
var promises = [];
for (var i = 0; i < proxy.items.length; i++) {
var item = proxy.items[i];
var loadedFrame = item.frameLoaded[frameNo];
proxy.imageData.data.set(loadedFrame);
var promise = createImageBitmap(proxy.imageData);
promises.push(promise);
delete item.frameLoaded[frameNo];
}
Promise.all(promises).then(function(bitmaps) {
proxy.context.clearRect(0, 0, proxy.canvas.width, proxy.canvas.height);
for (var i = 0; i < bitmaps.length; i++) {
proxy.context.drawImage(bitmaps[i], 0, 0);
}
var imageData = proxy.context.getImageData(0, 0, proxy.canvas.width, proxy.canvas.height);
proxy.onFrame(proxy.playerId, frameNo, imageData.data);
});
}
} else {
delete frameDatas;
}
}
function onLoaded(wReqId, frameCount, fps) {
var proxyId = wrMap[wReqId];
var proxy = proxies[proxyId];
var item = proxy.itemsMap[wReqId];
item.loaded = true;
item.frameCount = frameCount;
item.fps = fps;
var finished = true;
frameCount = null; fps = null;
for (var i = 0; i < proxy.items.length; i++) {
var item = proxy.items[i];
if (!item.framesAlign) {
if (frameCount === null) {
frameCount = item.frameCount;
} else if (frameCount !== false && frameCount !== item.frameCount) {
frameCount = false;
}
}
if (fps === null) {
fps = item.fps;
} else if (fps !== false && fps !== item.fps) {
fps = false;
}
if (!item.loaded) {
finished = false;
break;
}
}
if (finished) {
if (frameCount === null) {
console.warn('Frame count not defined'); return;
}
if (frameCount === false) {
console.warn('Frame count is different'); return;
}
if (fps === null) {
console.warn('FPS not defined'); return;
}
if (fps === false) {
console.warn('FPS is different'); return;
}
proxy.frameCount = frameCount;
proxy.fps = fps;
proxy.onLoaded(proxy.playerId, frameCount, fps);
}
}
workerproxy.init = function(worker_url, workers_limit, callback) {
var workersRemain = workers_limit;
var firstWorker = rlottieWorkers[0] = new QueryableWorker(worker_url);
firstWorker.addListener('ready', function () {
console.log(dT(), 'worker #0 ready');
firstWorker.addListener('frame', onFrame);
firstWorker.addListener('loaded', onLoaded);
--workersRemain;
if (!workersRemain) {
console.log(dT(), 'workers ready');
callback && callback();
} else {
for (var workerNum = 1; workerNum < workers_limit; workerNum++) {
(function(workerNum) {
var rlottieWorker = rlottieWorkers[workerNum] = new QueryableWorker(worker_url);
rlottieWorker.addListener('ready', function () {
console.log(dT(), 'worker #' + workerNum + ' ready');
rlottieWorker.addListener('frame', onFrame);
rlottieWorker.addListener('loaded', onLoaded);
--workersRemain;
if (!workersRemain) {
console.log(dT(), 'workers ready');
callback && callback();
}
});
})(workerNum);
}
}
});
};
workerproxy.create = function(playerId, onFrame, onLoaded) {
return new Proxy(playerId, onFrame, onLoaded);
};
workerproxy.destroy = function() {
for (var workerNum = 0; workerNum < rlottieWorkers.length; workerNum++) {
rlottieWorkers[workerNum].terminate();
console.log('worker #' + workerNum + ' terminated');
}
console.log('workers destroyed');
rlottieWorkers = [];
};
return workerproxy;
}());
function QueryableWorker(url, defaultListener, onError) {
var instance = this;
var worker = new Worker(url);
var listeners = {};
this.defaultListener = defaultListener || function() {};
if (onError) {worker.onerror = onError;}
this.postMessage = function(message) {
worker.postMessage(message);
}
this.terminate = function() {
worker.terminate();
}
this.addListener = function(name, listener) {
listeners[name] = listener;
}
this.removeListener = function(name) {
delete listeners[name];
}
/*
This functions takes at least one argument, the method name we want to query.
Then we can pass in the arguments that the method needs.
*/
this.sendQuery = function(queryMethod) {
if (arguments.length < 1) {
throw new TypeError('QueryableWorker.sendQuery takes at least one argument');
return;
}
var queryMethod = arguments[0];
var args = Array.prototype.slice.call(arguments, 1);
if (RLottie.isSafari) {
worker.postMessage({
'queryMethod': queryMethod,
'queryMethodArguments': args
});
} else {
var transfer = [];
for(var i = 0; i < args.length; i++) {
if(args[i] instanceof ArrayBuffer) {
transfer.push(args[i]);
}
if(args[i].buffer && args[i].buffer instanceof ArrayBuffer) {
transfer.push(args[i].buffer);
}
}
worker.postMessage({
'queryMethod': queryMethod,
'queryMethodArguments': args
}, transfer);
}
}
worker.onmessage = function(event) {
if (event.data instanceof Object &&
event.data.hasOwnProperty('queryMethodListener') &&
event.data.hasOwnProperty('queryMethodArguments')) {
listeners[event.data.queryMethodListener].apply(instance, event.data.queryMethodArguments);
} else {
this.defaultListener.call(instance, event.data);
}
}
}
function FrameQueue(maxLength) {
this.queue = [];
this.maxLength = maxLength;
}
FrameQueue.prototype.needsMore = function frameQueueNeedsMore() {
return this.queue.length < this.maxLength;
}
FrameQueue.prototype.empty = function frameQueueEmpty() {
return !this.queue.length;
}
FrameQueue.prototype.notEmpty = function frameQueueEmpty() {
return this.queue.length > 0;
}
FrameQueue.prototype.push = function frameQueuePush(element) {
return this.queue.push(element);
}
FrameQueue.prototype.shift = function frameQueueShift() {
return this.queue.length ? this.queue.shift() : null;
}
FrameQueue.prototype.last = function frameQueueLast(element) {
return this.queue.length ? this.queue[this.queue.length - 1] : null;
}
FrameQueue.prototype.clear = function frameQueueClear() {
this.queue = [];
return true;
}
if (!this.CustomEvent || typeof this.CustomEvent === "object") {
(function() {
this.CustomEvent = function CustomEvent(type, eventInitDict) {
var event;
eventInitDict = eventInitDict || {bubbles: false, cancelable: false, detail: undefined};
try {
event = document.createEvent('CustomEvent');
event.initCustomEvent(type, eventInitDict.bubbles, eventInitDict.cancelable, eventInitDict.detail);
} catch (error) {
event = document.createEvent('Event');
event.initEvent(type, eventInitDict.bubbles, eventInitDict.cancelable);
event.detail = eventInitDict.detail;
}
return event;
};
})();
}
function triggerEvent(el, event_type, init_dict) {
var event = new CustomEvent(event_type, init_dict);
el.dispatchEvent(event);
}

1
static/MBViewer/js/tgwallpaper.min.js vendored Normal file
View File

@@ -0,0 +1 @@
var TWallpaper=function(){function x(a){for(var b=[].concat(G);0<a;)b.push(b.shift()),a--;a=[];for(var c=0;c<b.length;c+=2)a.push(b[c]);return a}function B(a,b){b%=90;var c=x(a%p);if(b){var d=x(++a%p);return[{x:c[0].x+(d[0].x-c[0].x)/90*b,y:c[0].y+(d[0].y-c[0].y)/90*b},{x:c[1].x+(d[1].x-c[1].x)/90*b,y:c[1].y+(d[1].y-c[1].y)/90*b},{x:c[2].x+(d[2].x-c[2].x)/90*b,y:c[2].y+(d[2].y-c[2].y)/90*b},{x:c[3].x+(d[3].x-c[3].x)/90*b,y:c[3].y+(d[3].y-c[3].y)/90*b}]}return c}function H(a){for(l+=a;90<=l;)l-=90,g++,g>=p&&(g-=p);for(;0>l;)l+=90,g--,0>g&&(g+=p)}function I(a){C+=a.deltaY;D||(requestAnimationFrame(P),D=!0)}function P(){var a=C/50;C%=50;if(a=0<a?Math.floor(a):Math.ceil(a))H(a),a=B(g,l),y(z(a));D=!1}function Q(){if(0<A.length){var a=A.shift();y(a)}else clearInterval(E)}function z(a){for(var b=f._hctx.createImageData(50,50),c=b.data,d=0,q=0;50>q;q++)for(var h=q/50-.5,F=h*h,v=0;50>v;v++){var m=v/50-.5,e=.35*Math.sqrt(m*m+F);e=e*e*6.4;var r=Math.sin(e),w=Math.cos(e);e=Math.max(0,Math.min(1,.5+m*w-h*r));m=Math.max(0,Math.min(1,.5+m*r+h*w));for(var J=w=r=0,K=0,t=0;t<u.length;t++){var k=e-a[t].x,L=m-a[t].y;k=Math.max(0,.9-Math.sqrt(k*k+L*L));k*=k*k*k;r+=k;w+=k*u[t].r/255;J+=k*u[t].g/255;K+=k*u[t].b/255}c[d++]=w/r*255;c[d++]=J/r*255;c[d++]=K/r*255;c[d++]=255}return b}function y(a){f._hctx.putImageData(a,0,0);f._ctx.drawImage(f._hc,0,0,50,50)}function M(){var a=+Date.now();!document.hasFocus()||a-N<R||(N=a,H(1),a=B(g,l),y(z(a)));O=requestAnimationFrame(M)}var g=0,l=0,N=0,R=1E3/15,A=[],E=null,O=null,u=[],n=[0,.25,.5,.75,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,18.3,18.6,18.9,19.2,19.5,19.8,20.1,20.4,20.7,21,21.3,21.6,21.9,22.2,22.5,22.8,23.1,23.4,23.7,24,24.3,24.6,24.9,25.2,25.5,25.8,26.1,26.3,26.4,26.5,26.6,26.7,26.8,26.9,27],G=[{x:.8,y:.1},{x:.6,y:.2},{x:.35,y:.25},{x:.25,y:.6},{x:.2,y:.9},{x:.4,y:.8},{x:.65,y:.75},{x:.75,y:.4}],p=G.length,D=!1,C=0,f={init:function(a){u=[];var b=a.getAttribute("data-colors")||"";b&&(b=b.split(","));for(var c=0;c<b.length;c++){var d=u,q=d.push;var h=(h=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(b[c]))?{r:parseInt(h[1],16),g:parseInt(h[2],16),b:parseInt(h[3],16)}:null;q.call(d,h)}f._hc||(f._hc=document.createElement("canvas"),f._hc.width=50,f._hc.height=50,f._hctx=f._hc.getContext("2d"));f._canvas=a;f._ctx=f._canvas.getContext("2d");f.update()},update:function(){var a=B(g,l);y(z(a))},toNextPosition:function(){clearInterval(E);A=[];var a=x(g%p);g++;var b=x(g%p),c=(b[0].x-a[0].x)/27,d=(b[0].y-a[0].y)/27,q=(b[1].x-a[1].x)/27,h=(b[1].y-a[1].y)/27,F=(b[2].x-a[2].x)/27,v=(b[2].y-a[2].y)/27,m=(b[3].x-a[3].x)/27;b=(b[3].y-a[3].y)/27;for(var e=0;60>e;e++)A.push(z([{x:a[0].x+c*n[e],y:a[0].y+d*n[e]},{x:a[1].x+q*n[e],y:a[1].y+h*n[e]},{x:a[2].x+F*n[e],y:a[2].y+v*n[e]},{x:a[3].x+m*n[e],y:a[3].y+b*n[e]}]));E=setInterval(Q,1E3/30)},animate:function(a){a?M():cancelAnimationFrame(O)},scrollAnimate:function(a){a?document.addEventListener("wheel",I):document.removeEventListener("wheel",I)}};return f}();

File diff suppressed because it is too large Load Diff