Update MBViewer, with Trasformapi

This commit is contained in:
octospacc 2024-01-23 01:52:51 +01:00
parent ed9bdaf9fa
commit 0cf7946e6d
5 changed files with 316 additions and 25 deletions

View File

@ -0,0 +1,139 @@
(function(){
const Exp = {};
let MakeTreeFromXml;
const platformIsNode = (typeof module === 'object' && typeof module.exports === 'object');
const platformIsBrowser = (typeof window !== 'undefined' && typeof window.document !== 'undefined');
if (platformIsNode && !platformIsBrowser) {
MakeTreeFromXml = (xml) => new require('jsdom').JSDOM(xml);
}
if (platformIsBrowser) {
MakeTreeFromXml = (xml) => new DOMParser().parseFromString(xml, 'text/xml');
}
Exp.Trasformapi = (transformerXml, initOptions={}) => {
var transformerTree = MakeTreeFromXml(transformerXml);
return {
TransformForInput: (entityName, upstreamName, dataIn, transformOptions) => _TransformForInput(transformerTree, initOptions, entityName, upstreamName, dataIn, transformOptions),
TransformForOutput: (entityName, upstreamName, dataIn, transformOptions) => _TransformForOutput(transformerTree, initOptions, entityName, upstreamName, dataIn, transformOptions),
};
}
function _TransformForInput (transformerTree, initOptions, entityName, upstreamName, dataIn, transformOptions={}) {
// TODO: restructure prototype
// TODO: make the propDataType inside this function, for both main and secondary
function temp1 (upstreamName, propName, propType, propDataType, propContent, dataIn, dataOut, propNameSecondary, propTypeSecondary, propDataTypeSecondary) {
// const propDataType =
// const propDataTypeSecondary =
const dataKey = propContent.getAttribute('key');
//console.log(propName, propType, propDataType, propContent, dataIn, dataOut, propNameSecondary, propTypeSecondary, propDataTypeSecondary)
// TODO: inside here somehow happens the array error with prop > content > prop nestings, probably we need to handle secondary and primary types separately
const dataAttr = propContent.getAttribute('attr');
let dataInContent;
if (dataIn instanceof Node) {
// TODO: 'document' won't work on nodejs, must change it
//const dataNode = document.evaluate(dataKey, dataIn, ((ns) => ns), XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
//dataInContent = (dataAttr ? dataNode?.getAttribute(dataAttr) : dataNode?.textContent);
// TODO: finish this to actually handle arrays properly (even below)
const dataNodes = getElementsByXPath(dataKey, dataIn);
if (!Array.isArray(propDataTypeSecondary || propDataType) && dataNodes.length > 0) {
dataInContent = (dataAttr ? dataNodes[0].getAttribute(dataAttr) : dataNodes[0].textContent);
} else {
dataInContent = [];
for (const dataNode of dataNodes) {
// ... TODO push every item //dataInContent = (dataAttr ? dataNode?.getAttribute(dataAttr) : dataNode?.textContent);
dataInContent.push(dataAttr ? dataNodes[0].getAttribute(dataAttr) : dataNodes[0].textContent);
}
}
} else {
dataInContent = (dataKey ? _.get(dataIn, dataKey) : dataIn);
}
//const dataInContent = (dataIn instanceof Node
// ? (document.evaluate(dataKey, dataIn, (ns) => ns, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue || {}
// )['getAttribute' || 'textContent'](dataAttr)
// : (dataKey ? _.get(dataIn, dataKey) : dataIn));
// TODO: make this readable lmao
// TODO: make no type mean any/object type and remove the distinction maybe
// TODO: readd type casting
const result = (["any", "object", "string", "number", "int", "float"].includes(propTypeSecondary || propType)
? SetOrPush(dataInContent, (propDataTypeSecondary || propDataType))
// ? SetOrPush((["any", "object"].includes(propType)
// ? dataInContent
// : { int: parseInt, float: parseFloat, string: String, number: Number }[propType](dataInContent)
// ), propDataType)
: SetOrPush(MakeApiEntityObject((propTypeSecondary || propType), upstreamName, dataInContent), (propDataTypeSecondary || propDataType)));
!propNameSecondary ? (dataOut[propName] = result) : (dataOut[propName][propNameSecondary] = result);
}
function MakeApiEntityObject (entityName, upstreamName, dataIn) {
let dataOut = {};
const entitySchema = transformerTree.querySelector(`:scope > entity[name="${entityName}"]`);
for (const propSchema of entitySchema.querySelectorAll(':scope > prop')) {
const propName = propSchema.getAttribute('name');
const propType = propSchema.getAttribute('type').split('[]')[0];
const propDataType = (propSchema.getAttribute('type').endsWith('[]') ? [] : {});
const propContent = propSchema.querySelector(`:scope > content[upstream="${upstreamName}"]`);
if (!propContent) {
// property is not implemented for the current upstream, skip it
continue;
}
const propContentChildren = propContent.querySelectorAll(`:scope > prop`); // TODO
if (propContentChildren.length === 0) {
//const dataKey = propContent.getAttribute('key');
//const dataAttr = propContent.getAttribute('attr');
//const dataInContent = (dataIn instanceof Node
// // TODO: 'document' won't work on nodejs, must change it
// ? (document.evaluate(dataKey, dataIn, (ns) => ns, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue || {}
// )[dataAttr || 'textContent']
// : (dataKey ? _.get(dataIn, dataKey) : dataIn));
//dataOut[propName] = (["string", "number", "int", "float"].includes(propType)
// ? SetOrPush(dataInContent, propDataType)
// : SetOrPush(MakeApiEntityObject(propType, upstreamName, dataInContent), propDataType));
temp1(upstreamName, propName, propType, propDataType, propContent, dataIn, dataOut);
} else {
dataOut[propName] = {}; // should this be an array in some cases or not?
// TODO: wrap this and the above in a function, to allow for code reuse, right now the else condition does less things than what it should because of the duplication
for (const propChildSchema of propContentChildren) {
const entityChildSchema = transformerTree.querySelector(`:scope > entity[name="${propType}"]`);
const propChildName = propChildSchema.getAttribute('name');
const propChildProp = entityChildSchema.querySelector(`:scope > prop[name="${propChildName}"]`);
const propChildType = propChildProp.getAttribute('type').split('[]')[0];
const propChildDataType = (propChildProp.getAttribute('type').endsWith('[]') ? [] : {});
//const childDataKey = propChildSchema.getAttribute('key');
//const childDataInContent = childDataKey ? _.get(dataIn, childDataKey) : dataIn;
//dataOut[propName][propChildName] = (["string", "number", "int", "float"].includes(propChildType)
// ? SetOrPush(childDataInContent, propDataType)
// : null); // TODO other recursions? //SetOrPush(MakeApiEntityObject(propType, upstreamName, childDataInContent), childDataInContent));
temp1(upstreamName, propName, propType, propDataType, propChildSchema, dataIn, dataOut, propChildName, propChildType, propChildDataType);
}
}
}
//console.log(dataOut);
return dataOut;
}
return MakeApiEntityObject (entityName, upstreamName, dataIn);
}
function _TransformForOutput (transformerTree, initOptions, entityName, upstreamName, dataIn, transformOptions={}) {
// TODO
}
// <https://stackoverflow.com/questions/36303869/how-to-use-document-evaluate-and-xpath-to-get-a-list-of-elements/42600459#42600459>
// TODO: 'document' won't work on nodejs, must change it
function getElementsByXPath (xpath, parent) {
let results = [];
let query = document.evaluate(xpath, parent || document, ((ns) => ns), XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i=0, length=query.snapshotLength; i<length; ++i) {
results.push(query.snapshotItem(i));
}
return results;
}
const SetOrPush = (item, dest) => Array.isArray(dest) ? [...dest, item] : item;
if (platformIsNode) module.exports = Exp;
if (platformIsBrowser) window.Trasformapi = Exp.Trasformapi;
})();

14
public/Assets/Lib/lodash.custom.min.js vendored Normal file
View File

@ -0,0 +1,14 @@
/**
* @license
* Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
* Build: `lodash include="get"`
*/
;(function(){function t(){}function e(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}function n(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}function r(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}function o(t,e){for(var n=t.length;n--;)if(f(t[n][0],e))return n;return-1}function i(t){if(null==t)t=t===b?"[object Undefined]":"[object Null]";else if(I&&I in Object(t)){var e=T.call(t,I),n=t[I];
try{t[I]=b;var r=true}catch(t){}var o=P.call(t);r&&(e?t[I]=n:delete t[I]),t=o}else t=P.call(t);return t}function a(t){if(typeof t=="string")return t;if(M(t)){for(var e=-1,n=null==t?0:t.length,r=Array(n);++e<n;)r[e]=a(t[e]);return r+""}return y(t)?G?G.call(t):"":(e=t+"","0"==e&&1/t==-g?"-0":e)}function c(t,e){var n=t.__data__,r=typeof e;return("string"==r||"number"==r||"symbol"==r||"boolean"==r?"__proto__"!==e:null===e)?n[typeof e=="string"?"string":"hash"]:n.map}function u(t,e){var n=null==t?b:t[e];
return(!h(n)||C&&C in n?0:(p(n)?k:z).test(s(n)))?n:b}function s(t){if(null!=t){try{return E.call(t)}catch(t){}return t+""}return""}function l(t,e){function n(){var r=arguments,o=e?e.apply(this,r):r[0],i=n.cache;return i.has(o)?i.get(o):(r=t.apply(this,r),n.cache=i.set(o,r)||i,r)}if(typeof t!="function"||null!=e&&typeof e!="function")throw new TypeError("Expected a function");return n.cache=new(l.Cache||r),n}function f(t,e){return t===e||t!==t&&e!==e}function p(t){return!!h(t)&&(t=i(t),"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t);
}function h(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function _(t){return null!=t&&typeof t=="object"}function y(t){return typeof t=="symbol"||_(t)&&"[object Symbol]"==i(t)}function d(t){return null==t?"":a(t)}var b,g=1/0,v=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,j=/^\w*$/,m=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,O=/\\(\\)?/g,z=/^\[object .+?Constructor\]$/,S=typeof self=="object"&&self&&self.Object===Object&&self,S=typeof global=="object"&&global&&global.Object===Object&&global||S||Function("return this")(),w=typeof exports=="object"&&exports&&!exports.nodeType&&exports,x=w&&typeof module=="object"&&module&&!module.nodeType&&module,$=Array.prototype,A=Object.prototype,F=S["__core-js_shared__"],E=Function.prototype.toString,T=A.hasOwnProperty,C=function(){
var t=/[^.]+$/.exec(F&&F.keys&&F.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}(),P=A.toString,k=RegExp("^"+E.call(T).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),A=S.Symbol,R=$.splice,I=A?A.toStringTag:b,N=u(S,"Map"),q=u(Object,"create"),G=($=A?A.prototype:b)?$.toString:b;e.prototype.clear=function(){this.__data__=q?q(null):{},this.size=0},e.prototype.delete=function(t){return t=this.has(t)&&delete this.__data__[t],this.size-=t?1:0,
t},e.prototype.get=function(t){var e=this.__data__;return q?(t=e[t],"__lodash_hash_undefined__"===t?b:t):T.call(e,t)?e[t]:b},e.prototype.has=function(t){var e=this.__data__;return q?e[t]!==b:T.call(e,t)},e.prototype.set=function(t,e){var n=this.__data__;return this.size+=this.has(t)?0:1,n[t]=q&&e===b?"__lodash_hash_undefined__":e,this},n.prototype.clear=function(){this.__data__=[],this.size=0},n.prototype.delete=function(t){var e=this.__data__;return t=o(e,t),!(0>t)&&(t==e.length-1?e.pop():R.call(e,t,1),
--this.size,true)},n.prototype.get=function(t){var e=this.__data__;return t=o(e,t),0>t?b:e[t][1]},n.prototype.has=function(t){return-1<o(this.__data__,t)},n.prototype.set=function(t,e){var n=this.__data__,r=o(n,t);return 0>r?(++this.size,n.push([t,e])):n[r][1]=e,this},r.prototype.clear=function(){this.size=0,this.__data__={hash:new e,map:new(N||n),string:new e}},r.prototype.delete=function(t){return t=c(this,t).delete(t),this.size-=t?1:0,t},r.prototype.get=function(t){return c(this,t).get(t)},r.prototype.has=function(t){
return c(this,t).has(t)},r.prototype.set=function(t,e){var n=c(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this};var L=function(t){t=l(t,function(t){return 500===e.size&&e.clear(),t});var e=t.cache;return t}(function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(""),t.replace(m,function(t,n,r,o){e.push(r?o.replace(O,"$1"):n||t)}),e});l.Cache=r;var M=Array.isArray;t.memoize=l,t.eq=f,t.get=function(t,e,n){if(null==t)t=b;else{var r=t;if(!M(e)){if(M(e))r=false;else var o=typeof e,r=!("number"!=o&&"symbol"!=o&&"boolean"!=o&&null!=e&&!y(e))||(j.test(e)||!v.test(e)||null!=r&&e in Object(r));
e=r?[e]:L(d(e))}for(r=0,o=e.length;null!=t&&r<o;){var i;if(i=e[r++],typeof i!="string"&&!y(i)){var a=i+"";i="0"==a&&1/i==-g?"-0":a}t=t[i]}t=r&&r==o?t:b}return t===b?n:t},t.isArray=M,t.isFunction=p,t.isObject=h,t.isObjectLike=_,t.isSymbol=y,t.toString=d,t.VERSION="4.17.5",typeof define=="function"&&typeof define.amd=="object"&&define.amd?(S._=t, define(function(){return t})):x?((x.exports=t)._=t,w._=t):S._=t}).call(this);

View File

@ -204,7 +204,10 @@
<script src="js/tgsticker.js"></script> <script src="js/tgsticker.js"></script>
<script src="js/widget-frame.js"></script> <script src="js/widget-frame.js"></script>
<script src="js/telegram-web.js"></script> <script src="js/telegram-web.js"></script>
<script src="js/MBViewer.js"></script> <script src="../Assets/Lib/lodash.custom.min.js"></script>
<script src="../Assets/Lib/Trasformapi.js"></script>
<script src="./js/TrasformapiSchema.js.xml"></script>
<script src="./js/MBViewer.js"></script>
<script> <script>
TWeb.init(); TWeb.init();
ResizeLayouts(); ResizeLayouts();

View File

@ -19,6 +19,7 @@
// * show, and/or sort by, posts tags/categories // * show, and/or sort by, posts tags/categories
let MbState = {}; let MbState = {};
let MbApiTransformer;
function ArgsRewrite (props={}) { function ArgsRewrite (props={}) {
for (const key in props) { for (const key in props) {
@ -71,9 +72,9 @@ function MakeSiteRestUrl (path='') {
if (GetDomainFromUrl(siteUrl).toLowerCase() === 'octospacc.altervista.org') { 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)}`; return `${siteUrl}/wp-content/uploads/${siteUrl.split('.').slice(0, 1)[0].split('//')[1]}/scripts/stuff.php?&Thing=SiteWpJsonCors&AccessToken=9ab6e20c&$Query=${encodeURIComponent(path)}`;
} else { } else {
if (MbState.platform === 'wordpress.org') { if (["atom", "rss", "wordpress.org"].includes(MbState.platform)) {
const proxies = ["corsproxy.io", "corsproxy.org"]; const proxies = ["corsproxy.io", "corsproxy.org"];
return `https://${proxies[~~(Math.random() * proxies.length)]}/?${siteUrl}/wp-json/${path}`; return `https://${proxies[~~(Math.random() * proxies.length)]}/?${siteUrl}/${MbState.platform === 'wordpress.org' ? `wp-json/${path}` : ''}`;
} else if (MbState.platform === 'wordpress.com') { } else if (MbState.platform === 'wordpress.com') {
return `https://public-api.wordpress.com/rest/v1.1/sites/${GetDomainFromUrl(siteUrl)}/${path}`; return `https://public-api.wordpress.com/rest/v1.1/sites/${GetDomainFromUrl(siteUrl)}/${path}`;
} }
@ -89,7 +90,7 @@ function MakeApiEndpoint (type, options={}) {
"wordpress.com": { "wordpress.com": {
count: "number", count: "number",
orderBy: "order_by", orderBy: "order_by",
} },
} }
let query = ''; let query = '';
for (const option in options) { for (const option in options) {
@ -117,6 +118,9 @@ async function MbViewerInit () {
if (!location.hash) { if (!location.hash) {
location.hash = '/'; location.hash = '/';
} }
if (!MbApiTransformer) {
MbApiTransformer = Trasformapi(MbViewerTrasformapiSchema).TransformForInput;
}
MbState = { MbState = {
args: {}, args: {},
siteData: { siteData: {
@ -194,7 +198,9 @@ async function MbViewerInit () {
} }
try { try {
const siteRequest = await fetch(MakeSiteRestUrl()); const siteRequest = await fetch(MakeSiteRestUrl());
MbState.siteData = await siteRequest.json(); MbState.siteData = (["atom", "rss"].includes(MbState.platform)
? new DOMParser().parseFromString(await siteRequest.text(), 'text/xml')
: await siteRequest.json());
} catch(err) { } catch(err) {
setTimeout(MbViewerInit, 1000); setTimeout(MbViewerInit, 1000);
return; return;
@ -213,7 +219,7 @@ async function MbViewerInit () {
setTimeout(MbViewerInit, 1000); setTimeout(MbViewerInit, 1000);
return; return;
} }
} else { } else if (!["atom", "rss"].includes(MbState.platform)) {
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml('before')); $('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml('before'));
TWeb.loadMore($('.js-messages_more_wrap > a')); TWeb.loadMore($('.js-messages_more_wrap > a'));
} }
@ -223,11 +229,15 @@ async function MbViewerInit () {
$('a[name="goBack"]')[0].hidden = false; $('a[name="goBack"]')[0].hidden = false;
} }
MbState.siteData.iconUrl = (MbState.siteData.site_icon_url || MbState.siteData.icon?.img || MbState.siteData.icon?.ico); MbState.siteData.iconUrl = (MbState.siteData.site_icon_url || MbState.siteData.icon?.img || MbState.siteData.icon?.ico);
MbState.siteData.acroName ||= (!MbState.siteData.iconUrl ? MakeAcroName(MbState.siteData.name) : ''); MbState.siteData.acroName ||= (!MbState.siteData.iconUrl ? MbState.siteData.name && MakeAcroName(MbState.siteData.name) : '');
MbState.siteData.bgColor = ~~(Math.random() * 7); MbState.siteData.bgColor = ~~(Math.random() * 7);
if (MbState.siteData.iconUrl && !["http", "https"].includes(MbState.siteData.iconUrl.split('://')[0])) { if (MbState.siteData.iconUrl && !["http", "https"].includes(MbState.siteData.iconUrl.split('://')[0])) {
MbState.siteData.iconUrl = `${MbState.siteUrl}${MbState.siteData.iconUrl}`; MbState.siteData.iconUrl = `${MbState.siteUrl}${MbState.siteData.iconUrl}`;
} }
if (["atom", "rss"].includes(MbState.platform)) {
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml());
TWeb.loadMore($('.js-messages_more_wrap > a'), MbState.siteData);
}
if (!MbState.siteUrl) { if (!MbState.siteUrl) {
$('a[name="goBack"]')[0].hidden = true; $('a[name="goBack"]')[0].hidden = true;
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml()); $('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml());
@ -236,7 +246,7 @@ async function MbViewerInit () {
This is my personal experiment to make an MB-style frontend for sources that are by default not really friendly to that concept. 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. 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>. 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>`, date: '2024-01-13T21:00' }, { content: `<p> </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: 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/> * 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 a back button to return to this page here from a real site stream.
@ -245,7 +255,7 @@ async function MbViewerInit () {
<br/> <br/>
I also just now realized that wordpress.com uses a different REST API with different endpoints and parameters, 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... so I will need to handle that...
</p>`, date: '2024-01-14T02:00' }, { content: `<p> </p>`, time: '2024-01-14T02:00' }, { content: `<p>
New changes: New changes:
<br/> * Correctly handle wordpress.com blogs <br/> * Correctly handle wordpress.com blogs
<br/> * Show specific users as post authors whenever possible <br/> * Show specific users as post authors whenever possible
@ -255,13 +265,22 @@ async function MbViewerInit () {
<br/> * Made URL hash parameter names case-insensitive <br/> * Made URL hash parameter names case-insensitive
<br/> * Now sites without an icon will display a random color and their acronicized name <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... <br/> * Hopefully fixed all the scrolling-loading issues for real this time...
</p>`, date: '2024-01-15T01:00' }, { content: `<p> </p>`, time: '2024-01-15T01:00' }, { content: `<p>
New changes: New changes:
<br/> * Adapt newly-added icons for dark mode <br/> * Adapt newly-added icons for dark mode
<br/> * Improved visualization of info column for small screens <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/> * 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 <br/> * Allow opening the stream at the point in time of a specific post ID for a website
</p>`, date: '2024-01-16T00:00' }, { content: `<p> </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 Transformapi (very broken)
</p>`, time: '2024-01-23T01:00' }, { content: `<p>
Copyright notice: MBViewer uses code borrowed from <a href="https://t.me">t.me</a>, 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. specially modified to handle customized data visualizations in an MB-style.
<br/> <br/>
@ -298,27 +317,39 @@ function MakeMoreWrapperHtml (wrapType) {
relativeOpts[wrapType] = MbState.startingPost.date; relativeOpts[wrapType] = MbState.startingPost.date;
} }
return `<div class="tgme_widget_message_centered js-messages_more_wrap"> return `<div class="tgme_widget_message_centered js-messages_more_wrap">
<a href="${MbState.siteUrl && MakeSiteRestUrl(MakeApiEndpoint('posts', { count: 1, offset: offset, orderBy: "date", ...(MbState.startingPost && relativeOpts) }))}" data-${wrapType}="" class="tme_messages_more js-messages_more"></a> <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>`; </div>`;
} }
async function MakeMbHtml (postData, makeMoreWrap) { async function MakeMbHtml (postData, makeMoreWrap) {
postData = (typeof(postData) === 'string' ? JSON.parse(postData) : postData); postData = (typeof(postData) === 'string' ? JSON.parse(postData) : postData);
if (["atom", "rss"].includes(MbState.platform)) {
postData = Array.from(postData.querySelectorAll(':scope > channel > item')).reverse();
}
let html = ''; let html = '';
const siteLink = (MbState.siteData.url || MbState.siteData.URL || MbState.siteLink); const siteLink = (MbState.siteData.url || MbState.siteData.URL || MbState.siteLink);
const siteHref = (siteLink ? `href="${siteLink}"` : ''); const siteHref = (siteLink ? `href="${siteLink}"` : '');
for (postData of (postData.posts ? postData.posts : SureArray(postData))) { for (postData of (postData.posts ? postData.posts : SureArray(postData))) {
const postLink = (postData.link || postData.URL); if (MbState.platform) {
const authorId = (postData.author?.ID || postData.author || postData._links?.author[0]?.href?.split('/')?.slice(-1)[0]); 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]) { if (authorId && !MbState.authors[authorId]) {
MbState.authors[authorId] = (typeof(postData.author) === 'object' MbState.authors[authorId] = (typeof(postData.author) === 'object' && Object.keys(postData.author).join(' ') !== 'id'
? postData.author ? postData.author
: await (await fetch(MakeSiteRestUrl(MakeApiEndpoint('users', { id: authorId })))).json()); : await (await fetch(MakeSiteRestUrl(MakeApiEndpoint('users', { id: authorId })))).json());
} }
const authorData = MbState.authors[authorId]; const authorData = MbState.authors[authorId];
const authorLink = (authorData?.link || (siteLink && `${siteLink}/author/${authorData?.name}`)); const authorLink = (authorData?.link || (siteLink && `${siteLink}/author/${authorData?.name}`));
const authorHref = (authorLink ? `href="${authorLink}"` : ''); const authorHref = (authorLink ? `href="${authorLink}"` : '');
const iconUrl = (Object.values(authorData?.avatar_urls || {}).slice(-1)[0] || authorData?.avatar_URL || MbState.siteData.iconUrl); const iconUrl = (Object.values(authorData?.avatar_urls || {}).slice(-1)[0] || authorData?.icon?.url || MbState.siteData.iconUrl);
//let attachmentsHtml = '';
// TODO change this after fixing Trasformapi
//for (const attachment of postData.attachments?.url) {
//for (const attachment of postData.attachments) {
// TODO more media types
// attachmentsHtml += `<img src="${attachment.url}"/>`;
//}
html += ` html += `
<div class="tgme_widget_message_wrap js-widget_message_wrap date_visible"> <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 text_not_supported_wrap js-widget_message" data-post="${postData.id || postData.ID}">
@ -352,14 +383,15 @@ async function MakeMbHtml (postData, makeMoreWrap) {
</div> </div>
<div class="tgme_widget_message_text js-message_text before_footer" dir="auto"> <div class="tgme_widget_message_text js-message_text before_footer" dir="auto">
<div class="MbPost"> <div class="MbPost">
<!--${/*attachmentsHtml*/JSON.stringify(postData.attachments)}-->
${ReformatPostHtml(postData.content?.rendered || postData.content)} ${ReformatPostHtml(postData.content?.rendered || postData.content)}
</div> </div>
</div> </div>
<div class="tgme_widget_message_footer compact js-message_footer"> <div class="tgme_widget_message_footer compact js-message_footer">
<div class="tgme_widget_message_info short js-message_info"> <div class="tgme_widget_message_info short js-message_info">
<span class="tgme_widget_message_meta"> <span class="tgme_widget_message_meta">
<a class="tgme_widget_message_date" ${postLink ? `href="${postLink}"` : ''}> <a class="tgme_widget_message_date" ${postData.url ? `href="${postData.url}"` : ''}>
<time datetime="${postData.date}" class="time"></time> <time datetime="${postData.time}" class="time"></time>
<!-- TODO: show edited status --> <!-- TODO: show edited status -->
</a> </a>
</span> </span>
@ -393,22 +425,24 @@ function ReformatPostHtml (html) {
videoElem.preload = 'none'; videoElem.preload = 'none';
const frameElem = document.createElement('iframe'); const frameElem = document.createElement('iframe');
frameElem.style = 'border: none; width: 100%;'; frameElem.style = 'border: none; width: 100%;';
frameElem.allow = 'fullscreen'; frameElem.allowFullscreen = true;
frameElem.src = `data:text/html;utf8,<!DOCTYPE html><body> frameElem.src = `data:text/html;utf8,<!DOCTYPE html><body>
<style> <style>
html, body { margin: 0; overflow: hidden; } html, body { margin: 0; overflow: hidden; }
video { max-width: 100%; } video { max-width: 100%; }
</style> </style>
${encodeURIComponent(videoElem.outerHTML)} ${encodeURIComponent(videoElem.outerHTML)}
<button style="position: absolute; top: 0; right: 0; z-index: 1;">
Reload Media
</button>
<script> <script>
var videoElem = document.querySelector('video'); var videoElem = document.querySelector('video');
var buttonElem = document.querySelector('button');
buttonElem.onclick = function(){
videoElem.load();
};
videoElem.onloadedmetadata = function(){ videoElem.onloadedmetadata = function(){
top.postMessage((videoElem.src + ' ' + getComputedStyle(videoElem).height), '*'); top.postMessage((videoElem.src + ' ' + getComputedStyle(videoElem).height), '*');
var buttonElem = document.createElement('button');
buttonElem.style = 'position: absolute; top: 0; right: 0; z-index: 1;';
buttonElem.innerHTML = 'Fullscreen';
buttonElem.onclick = function(){ videoElem.requestFullscreen(); };
document.body.appendChild(buttonElem);
}; };
videoElem.load(); videoElem.load();
</script> </script>
@ -438,7 +472,7 @@ function ResizeLayouts () {
} }
$('a[name="goBack"]')[0].onclick = function(){ $('a[name="goBack"]')[0].onclick = function(){
ArgsRewrite({ siteurl: null, postid: null, /*postslug: null*/ }); ArgsRewrite({ siteurl: null, postid: null, platform: null, /*postslug: null*/ });
}; };
window.onmessage = function(event){ window.onmessage = function(event){

View File

@ -0,0 +1,101 @@
const MbViewerTrasformapiSchema = `<schema>
<!-- 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" key="./guid"/>
<content upstream="wordpress.com" key="ID"/>
<content upstream="wordpress.org" key="id"/>
<content upstream="mastodon" key="id"/>
</prop>
<prop name="url" type="string">
<content upstream="rss" key="./link"/>
<content upstream="wordpress.com" key="URL"/>
<content upstream="wordpress.org" key="link"/>
<content upstream="mastodon" key="url"/>
</prop>
<prop name="title" type="string">
<content upstream="rss" key="title"/>
<content upstream="wordpress.com" key="title"/>
<content upstream="wordpress.org" key="title.rendered"/>
</prop>
<prop name="content" type="string">
<!-- TODO optional multiple 'key' attrs -->
<!--<content upstream="rss" key="content:encoded"/>-->
<content upstream="rss" key="./description"/>
<content upstream="wordpress.com" key="content"/>
<content upstream="wordpress.org" key="content.rendered"/>
<content upstream="mastodon" key="url"/>
</prop>
<!-- TODO: fix this, it's broken with somehow we ending up with an object with urls array, not an attachments array -->
<prop name="attachments" type="file[]">
<!--<content upstream="rss" key="./media:content"/>-->
<content upstream="rss">
<prop name="url" key="*[name()='media:content']" attr="url"/>
</content>
<!--<content upstream="mastodon" key="media_attachments"/>-->
</prop>
<prop name="author" type="profile">
<content upstream="rss"/>
<content upstream="wordpress.com" key="author"/>
<content upstream="wordpress.org"/>
<content upstream="mastodon" key="account"/>
</prop>
<prop name="time" type="string">
<content upstream="rss" key="pubDate"/>
<content upstream="wordpress.com" key="date"/>
<content upstream="wordpress.org" key="date"/>
<content upstream="mastodon" key="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" key="reblog"/>
</prop>
<!--<prop name="replying" type="message">
<content upstream="mastodon" key=""/>
</prop>-->
</entity>
<entity name="revision">
<prop name="time" type="string">
<content upstream="wordpress.com" key="modified"/>
<content upstream="wordpress.org" key="modified"/>
<content upstream="mastodon" key="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">
<content upstream="wordpress.com" key="ID"/>
<content upstream="wordpress.org" key="author"/>
</prop>
<prop name="url" type="string">
<content upstream="wordpress.com" key="profile_URL"/>
</prop>
<prop name="name" type="string">
<!--<content upstream="rss" key="dc:creator"/>-->
<content upstream="rss" key="dc:creator"/>
<content upstream="wordpress.com" key="name"/>
</prop>
<prop name="icon" type="file">
<content upstream="wordpress.com">
<prop name="url" key="avatar_URL"/>
</content>
</prop>
</entity>
<entity name="file">
<prop name="url" type="string"/>
</entity>
</schema>`;