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("")+"":"/>"))},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]:"")+""}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+"")},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' ? '/>' : `>`); + attachmentsHtml += `<${elemTag} controls="true" src="${attachment.url}" alt="${attachment.description?.replaceAll('&', '&')?.replaceAll('"', '"') || ''}"/>`; + } + } html += `
@@ -383,8 +418,9 @@ async function MakeMbHtml (postData, makeMoreWrap) {