diff --git a/public/Assets/Lib/Trasformapi.js b/public/Assets/Lib/Trasformapi.js
index e996671..8b4d5f1 100644
--- a/public/Assets/Lib/Trasformapi.js
+++ b/public/Assets/Lib/Trasformapi.js
@@ -1,5 +1,7 @@
(function(){
+// NOTE: using defiant.js gives us '[undefined]' arrays instead of '[]' ones sometimes, should be fixed
+
const Exp = {};
let MakeTreeFromXml;
@@ -8,6 +10,7 @@ const platformIsBrowser = (typeof window !== 'undefined' && typeof window.docume
if (platformIsNode && !platformIsBrowser) {
MakeTreeFromXml = (xml) => new require('jsdom').JSDOM(xml);
+ // TODO load all other dependencies
}
if (platformIsBrowser) {
@@ -16,6 +19,10 @@ if (platformIsBrowser) {
Exp.Trasformapi = (transformerXml, initOptions={}) => {
var transformerTree = MakeTreeFromXml(transformerXml);
+ initOptions.sets ||= {};
+ for (const attr of transformerTree.querySelector(':scope > set')?.attributes) {
+ initOptions.sets[attr.name] = attr.value;
+ }
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),
@@ -23,51 +30,23 @@ Exp.Trasformapi = (transformerXml, initOptions={}) => {
}
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);
+ const globalSets = { ...initOptions.sets, ...transformOptions.sets };
+ // due to a bug in defiant, we need to prefix something to any key starting with '@'...
+ //
+ function JsonObjectKeysFix (obj) {
+ // TODO avoid collisions? (even if they're unlikely with what we're doing)
+ return (obj !== undefined && obj !== null ? Object.fromEntries(Object.entries(obj).map( ([key,value]) => {
+ const newKey = (key.startsWith('@') ? `_${key}` : key);
+ return typeof value == "object"
+ ? [newKey, JsonObjectKeysFix(value)]
+ : [newKey, value]
+ })) : obj);
}
function MakeApiEntityObject (entityName, upstreamName, dataIn) {
+ if (!dataIn) {
+ // nothing to do
+ return;
+ };
let dataOut = {};
const entitySchema = transformerTree.querySelector(`:scope > entity[name="${entityName}"]`);
for (const propSchema of entitySchema.querySelectorAll(':scope > prop')) {
@@ -79,41 +58,74 @@ function _TransformForInput (transformerTree, initOptions, entityName, upstreamN
// property is not implemented for the current upstream, skip it
continue;
}
- const propContentChildren = propContent.querySelectorAll(`:scope > prop`); // TODO
+ const propContentChildren = propContent.querySelectorAll(`:scope > prop`);
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);
+ const dataKey = SubstituteStringSets(propContent.getAttribute('query'), globalSets);
+ const dataInContent = (dataIn instanceof Node
+ ? GetElementsByXPath(dataKey, dataIn)[0]?.textContent
+ : (dataKey ? /*_.get*/defiant.search(dataIn, dataKey)[0] : dataIn)
+ );
+ // I don't know if this is fully correct
+ if (Array.isArray(propDataType) && Array.isArray(dataInContent)) {
+ for (const itemContent of dataInContent) {
+ dataOut[propName] = (["string", "number", "int", "float"].includes(propType)
+ ? SetOrPush(itemContent, propDataType)
+ : SetOrPush(MakeApiEntityObject(propType, upstreamName, itemContent), propDataType)
+ );
+ }
+ } else {
+ dataOut[propName] = (["string", "number", "int", "float"].includes(propType)
+ ? SetOrPush(dataInContent, propDataType)
+ : SetOrPush(MakeApiEntityObject(propType, upstreamName, dataInContent), propDataType)
+ );
+ }
} 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
+ dataOut[propName] = {}; // NOTE: in some cases, this should be an array, I guess, or maybe not?
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);
+ const childDataKey = SubstituteStringSets(propChildSchema.getAttribute('query'), globalSets);
+ let childDataInContent = [];
+ if (dataIn instanceof Node) {
+ const nodes = GetElementsByXPath(childDataKey, dataIn);
+ if (nodes.length === 1) {
+ childDataInContent = nodes[0]?.textContent
+ } else {
+ for (const node of nodes) {
+ childDataInContent.push(node?.textContent);
+ }
+ }
+ } else {
+ childDataInContent = (childDataKey ? /*_.get*/defiant.search(dataIn, childDataKey)[0] : dataIn);
+ }
+ const childResult = (["string", "number", "int", "float"].includes(propChildType)
+ ? childDataInContent
+ : MakeApiEntityObject(propChildType, upstreamName, childDataInContent)
+ );
+ if (Array.isArray(propDataType)) {
+ if (!Array.isArray(dataOut[propName])) {
+ dataOut[propName] = [];
+ }
+ const childItems = SureArray(childResult);
+ for (const childItemIndex in childItems) {
+ const childItem = childItems[childItemIndex];
+ if (!dataOut[propName][childItemIndex]) {
+ dataOut[propName][childItemIndex] = {};
+ }
+ dataOut[propName][childItemIndex][propChildName] = childItem;
+ }
+ } else {
+ dataOut[propName][propChildName] = childResult;
+ }
}
}
}
- //console.log(dataOut);
return dataOut;
}
- return MakeApiEntityObject (entityName, upstreamName, dataIn);
+ return MakeApiEntityObject (entityName, upstreamName, (dataIn instanceof Node ? dataIn : JsonObjectKeysFix(dataIn)));
}
function _TransformForOutput (transformerTree, initOptions, entityName, upstreamName, dataIn, transformOptions={}) {
@@ -122,7 +134,7 @@ function _TransformForOutput (transformerTree, initOptions, entityName, upstream
//
// TODO: 'document' won't work on nodejs, must change it
-function getElementsByXPath (xpath, parent) {
+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 Array.isArray(dest) ? [...dest, item] : item;
+const SetOrPush = (item, dest) => (Array.isArray(dest) ? [...dest, item] : item);
+
+const SureArray = (item) => (Array.isArray(item) ? item : [item]);
+
+const SubstituteStringSets = (string, sets) => {
+ for (const set in sets) {
+ string = string?.replaceAll(`{${set}}`, sets[set]);
+ }
+ return string;
+}
if (platformIsNode) module.exports = Exp;
if (platformIsBrowser) window.Trasformapi = Exp.Trasformapi;
diff --git a/public/Assets/Lib/defiant.min.js b/public/Assets/Lib/defiant.min.js
new file mode 100644
index 0000000..194680c
--- /dev/null
+++ b/public/Assets/Lib/defiant.min.js
@@ -0,0 +1,6 @@
+/*
+ * defiant.js [v2.2.8]
+ * http://www.defiantjs.com
+ * Copyright (c) 2013-2023 Hakan Bilgin
+ * License GNU AGPLv3
+ */if(function(window,module){"use strict";var defiant={is_ie:/(msie|trident)/i.test(navigator.userAgent),is_safari:/safari/i.test(navigator.userAgent),env:"production",xml_decl:'',namespace:'xmlns:d="defiant-namespace"',tabsize:4,snapshots:{},renderXml:function(e,t){var n=new window.XSLTProcessor,r=document.createElement("span"),a='//xsl:template[@name="'+e+'"]',s=this.node.selectSingleNode(this.xsl_template,a);return(s=this.node.selectSingleNode(this.xsl_template,a)).setAttribute("match","/"),n.importStylesheet(this.xsl_template),r.appendChild(n.transformToFragment(t,document)),s.removeAttribute("match"),r.innerHTML},render:function(e,t){var n,r,a,s,o=new window.XSLTProcessor,i=document.createElement("span"),l={match:"/"};switch(typeof e){case"object":this.extend(l,e),l.data||(l.data=t);break;case"string":l.template=e,l.data=t;break;default:throw"error"}if(l.data=l.data.nodeType?l.data:defiant.json.toXML(l.data),n='//xsl:template[@name="'+l.template+'"]',this.xsl_template||this.gatherTemplates(),l.sorter&&(s=this.node.selectSingleNode(this.xsl_template,n+"//xsl:for-each//xsl:sort"))&&(l.sorter.order&&s.setAttribute("order",l.sorter.order),l.sorter.select&&s.setAttribute("select",l.sorter.select),s.setAttribute("data-type",l.sorter.type||"text")),(a=this.node.selectSingleNode(this.xsl_template,n)).setAttribute("match",l.match),o.importStylesheet(this.xsl_template),i.appendChild(o.transformToFragment(l.data,document)),a.removeAttribute("match"),this.is_safari)for(var c=0,d=(r=i.getElementsByTagName("script")).length;c"+t.replace(/defiant:(\w+)/g,"$1")+"")},registerTemplate:function(e){this.xsl_template=this.xmlFromString('"+e.replace(/defiant:(\w+)/g,"$1")+"")},getSnapshot:function(e,t){return this.json.toXML(e,t||!0)},createSnapshot:function(e,t){var n=this,r="snapshot_"+Date.now();this.json.toXML(e,function(e){n.snapshots[r]=e,t(r)})},getFacets:function(e,t){var n,r,a,s,o,i,l=e.constructor===String&&"snapshot_"===e.slice(0,9)?this.snapshots[e].doc:defiant.json.toXML(e),c=l.cloneNode(!0),d={},u={},p=0,h=function(e){var t=e.childNodes.length;switch(e.nodeType){case 1:t>=p&&(p=t,r=e);case 9:e.childNodes.map(function(e){return h(e)})}};for(i in h(l),r.childNodes.map(function(e){u[e.nodeName]||(u[e.nodeName]=1),u[e.nodeName]++}),p=0,u)p<=u[i]&&(p=u[i],o=i);return this.createFacetTemplate(t),s=defiant.node.selectSingleNode(c,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]'),defiant.node.selectNodes(c,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]/'+o).map(function(e){return e.parentNode.removeChild(e)}),a=defiant.node.selectNodes(l,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]/'+o),n=a.length-1,a.map(function(e,t){if(s.appendChild(e.cloneNode(!0)),t%50==49||t===n){var a=defiant.render("facets",c).replace(/\n|\t/g,"").replace(/"": 0,?/g,"").replace(/,\}/g,"}"),i=JSON.parse(a);d=defiant.concatFacet(i,d),defiant.node.selectNodes(c,'//*[@d:mi="'+r.getAttribute("d:mi")+'"]/'+o).map(function(e){return e.parentNode.removeChild(e)})}}),d},createFacetTemplate:function(e){var t,n,r=[],a=[];for(n in e)r.push(''),a.push('"'+n+'": {"": '+',}'.replace(/\n|\t/g,""));t=r.join("")+'{'+a.join(",")+"}",this.registerTemplate(t)},xmlFromString:function(e){var t;return null===(e=e.replace(/>\s{1,}<")).trim().match(/<\?xml/)&&(e=this.xml_decl+e),"ActiveXObject"in window?((t=new ActiveXObject("Msxml2.DOMDocument")).loadXML(e),t.setProperty("SelectionNamespaces",this.namespace),-1===e.indexOf("xsl:stylesheet")&&t.setProperty("SelectionLanguage","XPath")):t=(new DOMParser).parseFromString(e,"text/xml"),t},concatFacet:function(e,t){for(var n in t)e[n]&&"object"==typeof t[n]?this.concatFacet(e[n],t[n]):e[n]=(e[n]||0)+t[n];return e},extend:function(e,t){for(var n in t)e[n]&&"object"==typeof t[n]?this.extend(e[n],t[n]):e[n]=t[n];return e},node:{selectNodes:function(e,t){if(e.evaluate){for(var n=e.createNSResolver(e.documentElement),r=e.evaluate(t,e,n,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null),a=[],s=0,o=r.snapshotLength;s0?n[0]:null}return e.selectSingleNode(t)},prettyPrint:function(e){var t,n=defiant,r=n.tabsize,a=n.xml_decl.toLowerCase();t=n.is_ie?e.xml:(new XMLSerializer).serializeToString(e),"development"!==n.env&&(t=t.replace(/ \w+\:d=".*?"| d\:\w+=".*?"/g,""));for(var s,o,i=t.trim().replace(/(>)\s*(<)(\/*)/g,"$1\n$2$3").split("\n"),l=-1,c=0,d=i.length;c/g),o=null!==i[c].match(/<\/[\w\:]+>/g),null!==i[c].match(/<.*?\/>/g)&&(s=o=!0),s&&l++,i[c]=String().fill(l,"\t")+i[c],s&&o&&l--,!s&&o&&l--);return i.join("\n").replace(/\t/g,String().fill(r," "))},toJSON:function(e,t){var n=function(e){var t,r,a,s,o,i,l,c,d,u,p={},h=window;switch(e.nodeType){case 1:for("Array"===(o=e.getAttribute("d:constr"))?p=[]:"String"===o&&""===e.textContent&&(p=""),c=0,d=(t=e.attributes).length;c/,rx_constructor:/<(.+?)( d:contr=".*?")>/,rx_namespace:/ xmlns\:d="defiant\-namespace"/,rx_data:/(<.+?>)(.*?)(<\/d:data>)/i,rx_function:/function (\w+)/i,namespace:'xmlns:d="defiant-namespace"',to_xml_str:function(e){return{str:this.hash_to_xml(null,e),map:this.map}},hash_to_xml:function(e,t,n){var r,a,s,o,i,l,c,d,u,p=t.constructor===Array,h=this,m=[],f=[],g=function(t,r){if(null!==(a=r[t])&&void 0!==a&&"NaN"!==a.toString()||(a=null),o="@"===t.slice(0,1),(i=n?e:t)==+i&&r.constructor!==Object&&(i="d:item"),null===a?(l=null,c=!1):(l=a.constructor,c=l.toString().match(h.rx_function)[1]),o)f.push(i.slice(1)+'="'+h.escape_xml(a)+'"'),"String"!==c&&f.push("d:"+i.slice(1)+'="'+c+'"');else if(null===a)m.push(h.scalar_to_xml(i,a));else switch(l){case Function:throw"JSON data should not contain functions. Please check your structure.";case Object:m.push(h.hash_to_xml(i,a));break;case Array:if(t===i){if(s=a.constructor===Array)for(d=a.length;d--;)null!==a[d]&&a[d]&&a[d].constructor!==Array||(s=!0),s||a[d].constructor!==Object||(s=!0);m.push(h.scalar_to_xml(i,a,s));break}case String:if("string"==typeof a&&(a=a.toString()/*octt edit: commented this due to overescaping of HTML content *//*.replace(/\&/g,"&").replace(/\r|\n/g,"
")*/),"#text"===i){h.map.push(r),f.push('d:mi="'+h.map.length+'"'),f.push('d:constr="'+c+'"'),m.push(h.escape_xml(a));break}case Number:case Boolean:if("#text"===i&&"String"!==c){h.map.push(r),f.push('d:mi="'+h.map.length+'"'),f.push('d:constr="'+c+'"'),m.push(h.escape_xml(a));break}m.push(h.scalar_to_xml(i,a))}};if(t.constructor===Array)for(d=0,u=t.length;d"+m.join("")+""+e+">":"/>"))},scalar_to_xml:function(e,t,n){var r,a,s,o="";if(null===e.match(this.rx_validate_name)&&(o+=' d:name="'+e+'"',e="d:name",n=!1),null!==t&&"NaN"!==t.toString()||(t=null),null===t)return"<"+e+' d:constr="null"/>';if(1===t.length&&t.constructor===Array&&!t[0])return"<"+e+' d:constr="null" d:type="ArrayItem"/>';if(1===t.length&&t[0].constructor===Object){var i=(r=this.hash_to_xml(!1,t[0])).match(this.rx_node),l=r.match(this.rx_constructor);return"<"+e+(i=null!==i?i[2].replace(this.rx_namespace,"").replace(/>/,"").replace(/"\/$/,'"'):"")+" "+(l=null!==l?l[2]:"")+' d:type="ArrayItem">'+(r=null!==(r=r.match(this.rx_data))?r[2]:"")+""+e+">"}return 0===t.length&&t.constructor===Array?"<"+e+' d:constr="Array"/>':n?this.hash_to_xml(e,t,!0):(s=(a=t.constructor).toString().match(this.rx_function)[1],r=a===Array?this.hash_to_xml("d:item",t,!0):this.escape_xml(t),o+=' d:constr="'+s+'"',this.map.push(t),o+=' d:mi="'+this.map.length+'"',"#text"===e?this.escape_xml(t):"<"+e+o+">"+r+""+e+">")},escape_xml:function(e){return String(e).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/ /g," ")}},toXML:function(e,t){var n,r,a=defiant.json.interpreter;switch(typeof t){case"function":return void defiant.compiled.to_xml_str(e,function(n){t({doc:defiant.xmlFromString(n.str),src:e,map:n.map})});case"boolean":return n=a.to_xml_str.call(a,e),{doc:defiant.xmlFromString(n.str),src:e,map:n.map};default:return n=a.to_xml_str.call(a,e),r=defiant.xmlFromString(n.str),this.search.map=n.map,r}},search:function(e,t,n){e.constructor===String&&"snapshot_"===e.slice(0,9)&&defiant.snapshots[e]&&(e=defiant.snapshots[e]);var r,a,s=defiant.json,o=e.doc&&e.doc.nodeType,i=o?e.doc:s.toXML(e),l=o?e.map:s.search.map,c=o?e.src:e,d=defiant.node[n?"selectSingleNode":"selectNodes"](i,t.xTransform()),u=[];for(n&&(d=[d]),a=d.length;a--;)switch(d[a].nodeType){case 2:case 3:u.unshift(d[a].nodeValue);break;default:r=+d[a].getAttribute("d:mi"),u.unshift(l[r-1])}return"development"===defiant.env&&(u.trace=s.matchTrace(c,u,d)),u},matchTrace:function(e,t,n){var r=[],a=0,s=window,o=defiant.node.toJSON,i=function(e){return JSON.stringify(e,null,"\t").replace(/\t/g,"")},l=i(e);return n.map(function(e,c){var d,u,p,h,m,f,g,x=0;switch(e.nodeType){case 2:d=n[c].ownerElement?n[c].ownerElement.getAttribute("d:"+n[c].nodeName):"String",h=s[d](t[c]),m='"@'+n[c].nodeName+'": '+h,f=l.indexOf(m,a);break;case 3:d=n[c].parentNode.getAttribute("d:constr"),h=s[d](t[c]),m='"'+n[c].parentNode.nodeName+'": '+("Number"===m?h:'"'+h+'"'),f=l.indexOf(m,a);break;default:d=e.getAttribute("d:constr"),["String","Number"].indexOf(d)>-1?(u=o(n[c].parentNode),p=i(u),h=s[d](t[c]),m='"'+n[c].nodeName+'": '+("Number"===d?h:'"'+h+'"'),f=l.indexOf(p,a)+p.indexOf(m)):(m=i(t[c]),f=l.indexOf(m),x=m.split("\n").length-1)}a=f+1,g=l.slice(0,f).split("\n").length,r.push([g,x])}),r}}},x10={id:1,work_handler:function(e){var t=Array.prototype.slice.call(e.data,2),n=e.data[0],r=e.data[1],a=tree[n].apply(tree,t);a.map=JSON.parse(JSON.stringify(a.map)),postMessage([r,n,a])},setup:function(e){var t=window.URL||window.webkitURL,n="var tree = {"+this.parse(e).join(",")+"};",r=new Blob([n+'self.addEventListener("message", '+this.work_handler.toString()+", false);"],{type:"text/javascript"}),a=new Worker(t.createObjectURL(r));return a.onmessage=function(e){var t=Array.prototype.slice.call(e.data,2),n=e.data[0],r=e.data[1];x10.observer.emit("x10:"+r+n,t),x10.observer.off("x10:"+r+n)},a},call_handler:function(e,t){return function(){var n=Array.prototype.slice.call(arguments,0,-1),r=arguments[arguments.length-1],a=x10.id++;n.unshift(a),n.unshift(e),x10.observer.on("x10:"+e+a,function(e){r(e.detail[0])}),t.postMessage(n)}},compile:function(e){var t,n=this.setup("function"==typeof e?{func:e}:e),r={};if("function"==typeof e)return r.func=this.call_handler("func",n),r.func;for(t in e)r[t]=this.call_handler(t,n);return r},parse:function(e,t){var n,r,a,s=[];for(n in e)if(null!==(a=e[n]))if(void 0!==a){switch(a.constructor){case Date:r="new Date("+a.valueOf()+")";break;case Object:r="{"+this.parse(a).join(",")+"}";break;case Array:r="["+this.parse(a,!0).join(",")+"]";break;case String:r='"'+a.replace(/"/g,'\\"')+'"';break;case RegExp:case Function:r=a.toString();break;default:r=a}t?s.push(r):s.push(n+":"+r)}else s.push(n+":undefined");else s.push(n+":null");return s},observer:(stack={},{on:function(e,t){stack[e]||(stack[e]=[]),stack[e].unshift(t)},off:function(e,t){if(stack[e]){var n=stack[e].indexOf(t);stack[e].splice(n,1)}},emit:function(e,t){if(stack[e])for(var n={type:e,detail:t,isCanceled:!1,cancelBubble:function(){this.isCanceled=!0}},r=stack[e].length;r--;){if(n.isCanceled)return;stack[e][r](n)}}})},stack;String.prototype.fill||(String.prototype.fill=function(e,t){var n=this;for(t=t||" ";n.length
+
diff --git a/public/MBViewer/js/MBViewer.js b/public/MBViewer/js/MBViewer.js
index 5075282..5e07045 100644
--- a/public/MBViewer/js/MBViewer.js
+++ b/public/MBViewer/js/MBViewer.js
@@ -77,12 +77,15 @@ function MakeSiteRestUrl (path='') {
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",
@@ -100,6 +103,13 @@ function MakeApiEndpoint (type, options={}) {
}
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;
}
@@ -201,7 +211,18 @@ async function MbViewerInit () {
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;
}
@@ -216,6 +237,7 @@ async function MbViewerInit () {
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;
}
@@ -228,16 +250,17 @@ async function MbViewerInit () {
$('.tgme_channel_info_header_username').html(`${GetDomainFromUrl(siteLink).toLowerCase()}`);
$('a[name="goBack"]')[0].hidden = false;
}
- MbState.siteData.iconUrl = (MbState.siteData.site_icon_url || MbState.siteData.icon?.img || MbState.siteData.icon?.ico);
+ 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 (["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) {
$('a[name="goBack"]')[0].hidden = true;
$('section.tgme_channel_history.js-message_history').html(MakeMoreWrapperHtml());
@@ -281,6 +304,12 @@ async function MbViewerInit () {
* Initial support for handling data via Trasformapi lib
* Initial, experimental support for RSS feeds specifically, via Transformapi (very broken)
`, time: '2024-01-23T01:00' }, { content: `
+ New changes:
+
* 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)
+
* Only slightly better RSS support
+
* Initial, experimental support for Mastodon profiles (broken)
+
`, time: '2024-01-24T01:00' }, { content: `
Copyright notice: MBViewer uses code borrowed from t.me,
specially modified to handle customized data visualizations in an MB-style.
@@ -288,6 +317,7 @@ async function MbViewerInit () {
all rights upon the original materials (which are: everything not strictly related to the "MBViewer" mod) belong to the original owners.
` }]);
}
+ 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);
@@ -324,7 +354,10 @@ function MakeMoreWrapperHtml (wrapType) {
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')).reverse();
+ 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);
@@ -343,13 +376,15 @@ async function MakeMbHtml (postData, makeMoreWrap) {
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 = '';
- // TODO change this after fixing Trasformapi
- //for (const attachment of postData.attachments?.url) {
- //for (const attachment of postData.attachments) {
- // TODO more media types
- // attachmentsHtml += ``;
- //}
+ 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('&', '&')?.replaceAll('"', '"') || ''}"/>`;
+ }
+ }
html += `