const dev = {error:()=>{}};String.prototype.prettyHashCode = function() { var hash = 0; for (var i = 0; i < this.length; i++) { var char = this.charCodeAt(i); hash = ((hash<<5)-hash)+char; hash = hash & hash; } return 'h' + Math.abs(hash); } const defined = '__ophs_defined'; const htmlProperties = ["name", "id"]; const htmlAttrs = ["accept","accept-charset","accesskey","action","align","alt","async","autocomplete","autofocus","autoplay","bgcolor","border","charset","checked","cite","color","cols","colspan","content","contenteditable","controls","coords","data","datetime","defer","dir","disabled","download","draggable","enctype","for","form","headers","height","hidden","high","href","hreflang","http-equiv","id","integrity","ismap","itemprop","keytype","kind","label","lang","list","loop","low","max","maxlength","media","method","min","multiple","muted","name","novalidate","open","optimum","pattern","placeholder","poster","preload","radiogroup","readonly","rel","required","reversed","rows","rowspan","sandbox","scope","scoped","selected","shape","size","sizes","span","spellcheck","src","srcdoc","srclang","srcset","start","step","style","tabindex","target","title","translate","type","usemap","value","width","wrap"]; const htmlEvents = ["onabort", "onautocomplete", "onautocompleteerror", "onblur", "oncancel", "oncanplay", "oncanplaythrough", "onchange", "onclick", "onclose", "oncontextmenu", "oncuechange", "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragexit", "ondragleave", "ondragover", "ondragstart", "ondrop", "ondurationchange", "onemptied", "onended", "onerror", "onfocus", "oninput", "oninvalid", "onkeydown", "onkeypress", "onkeyup", "onload", "onloadeddata", "onloadedmetadata", "onloadstart", "onmousedown", "onmouseenter", "onmouseleave", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onmousewheel", "onpause", "onplay", "onplaying", "onprogress", "onratechange", "onreset", "onresize", "onscroll", "onseeked", "onseeking", "onselect", "onshow", "onsort", "onstalled", "onsubmit", "onsuspend", "ontimeupdate", "ontoggle", "onvolumechange", "onwaiting"]; const ophAttrs = ["_name"]; class ___render___ { static __placedOphoseInstances = []; /** * Renders an ophose object to a DOM node. * @param {*} oph the ophose object to render * @returns {Node} the DOM node */ static toNode(oph, shouldBePlaced = false) { if(oph === undefined || oph === null || oph === false) return document.createTextNode(""); if(!___render___.isOphoseObject(oph)) { dev.error("RenderException: Invalid ophose object:"); console.log(oph); return undefined; } // Adding HTML & Event attributes to a node from object const giveAttrsAndEventsToNode = (element, node) => { for (let attribute in element) { if (ophAttrs.includes(attribute)) { node.setAttribute(attribute, element[attribute]); continue; } if(element[attribute] === defined) { node.setAttribute(attribute, ''); } if (element.hasOwnProperty(attribute)) { if (element[attribute] !== undefined && htmlAttrs.includes(attribute)) { node.setAttribute(attribute, element[attribute]); if(htmlProperties.includes(attribute)) node[attribute] = element[attribute]; continue; } if (htmlEvents.includes(attribute)) { node[attribute] = element[attribute]; continue; } if (element.className || element.class) { if (element.class) element.className = element.class; for (let className of element.className.split(" ")) { if(className == "") continue; node.classList.add(className); } } } } }; if (Array.isArray(oph)) { if(oph.length == 0) return ___render___.toNode(undefined, shouldBePlaced); let fragment = document.createDocumentFragment(); let oList = []; for (let child of oph) { let childNode = ___render___.toNode(child, shouldBePlaced); fragment.appendChild(childNode); if(childNode) oList.push(childNode); } fragment.oList = oList; return fragment; }; if(oph instanceof Node) { return oph; } // Case: oph is a string if(typeof oph == "string" || typeof oph == "number") { return document.createTextNode(oph); } // Case: oph is a component if (oph instanceof ___component___) { let rendered = oph.render(); if(rendered instanceof PlacedLive) { rendered.selfClassName = oph.__getComponentUniqueId(); } let node = ___render___.toNode(rendered, shouldBePlaced); let nodeToGiveAttrsAndEvents = node; if(oph.__propsOn) { // loop on children to find the input for (let child of node.children) { let name = child.getAttribute('_name'); if(!name) continue; if(name == oph.__propsOn) { nodeToGiveAttrsAndEvents = child; break; } } } giveAttrsAndEventsToNode(oph.props, nodeToGiveAttrsAndEvents); if (shouldBePlaced) { oph.__place(node); ___render___.__placedOphoseInstances.push(oph); node['o'] = oph; } return node; } // Case: oph is a Live if (oph instanceof Live) { let textNode = document.createTextNode(oph.get()); oph.__placedLiveTextNodes.push(textNode); return textNode; } // Case: oph is a placed live (with callback so on) if (oph instanceof PlacedLive) { let lives = oph.lives; let callback = oph.callback; let args = lives.map((live) => live.get()); let newOph = callback(...args); let node = ___render___.toNode(newOph, shouldBePlaced); oph.node = node; if(oph.selfClassName) oph.node.classList.add(oph.selfClassName); return node; } // Case Default: oph is a dict if (typeof oph == "object") { if (___base___.registered[oph._]) { let copyOph = {...oph}; let clsComponent = ___base___.registered[oph._]; delete copyOph._; let component = new clsComponent(copyOph); return ___render___.toNode(component, shouldBePlaced); } let ophNode = document.createElement(oph._); if(oph.c) oph.children = oph.c; if(oph.watch) { ophNode.value = oph.watch.get(); oph.watch.subscribe(() => { ophNode.value = oph.watch.get(); }); ophNode.addEventListener("input", (e) => { oph.watch.set(ophNode.value); }); ophNode.addEventListener("change", (e) => { oph.watch.set(ophNode.value); }); } if (oph.children) { if(Array.isArray(oph.children)){ for (let child of oph.children) { if (Array.isArray(child)) { for (let childChild of child) { let childNode = ___render___.toNode(childChild, shouldBePlaced); if(childNode) { ophNode.appendChild(childNode); } } continue; } let childNode = ___render___.toNode(child, shouldBePlaced); if(childNode) { ophNode.appendChild(childNode); } } } else { let childNode = ___render___.toNode(oph.children, shouldBePlaced); if(childNode) { ophNode.appendChild(childNode); } } } if (oph.text) { ophNode.textContent = oph.text; } giveAttrsAndEventsToNode(oph, ophNode); if (oph.html || oph.innerHTML) { ophNode.innerHTML = oph.html || oph.innerHTML; } return ophNode; } return undefined; } static isOphoseObject(object) { return typeof object == "string" || typeof object == "number" || object instanceof ___component___ || object instanceof Live || object instanceof PlacedLive || (typeof object == "object" && object._) || Array.isArray(object) || object instanceof Node; } } /** * Shortcut function to create an ophose object. * @param {string} tag the HTML tag * @param {object|string|number|Array|___component___|Live|PlacedLive|Node} propsOrChildren the props if object without '_' key, else children */ function _(tag, ...propsOrChildren) { let object = {_: tag}; let children = []; if(propsOrChildren[0] && !___render___.isOphoseObject(propsOrChildren[0])) { object = {...object, ...propsOrChildren.shift()}; } for(let arg of propsOrChildren) { if(Array.isArray(arg)) { children.push(...arg); continue; } children.push(arg); } if(children.length > 0) object.children = children; return object; } /** * Returns a safe ophose object (preventing XSS attacks). * @param {*} oph the ophose object to sanitize * @returns {*} the sanitized ophose object */ function safe(oph) { if(Array.isArray(oph)) { return oph.map((o) => safe(o)); } if(typeof oph == "object") { // Return null if the object has tag script, to prevent XSS attacks if(oph._ == "script") return null; if(oph.children) { oph.children = safe(oph.children); } htmlEvents.forEach((event) => { if(oph[event]) delete oph[event]; }) if(oph.innerHTML) delete oph.innerHTML; return oph; } return oph; };class ___import___ { static __imported = []; static __lastExport = undefined; static __fixedPath(path) { if(path.startsWith("@/")) path = path.replace("@/", ".ext/"); return path.trim().replaceAll("//", "/"); } static __use(path) { if(___import___.__imported.includes(path)) return; if(__OPH_APP_BUILD__) return; ___import___.__imported.push(path); ___script___.run(path); } /** * Imports once the class component * @param {string} path the path */ static useComponent(path) { ___import___.__use("/@component/" + ___import___.__fixedPath(path) + '.js'); } /** * Imports once the class module * @param {string} path the path */ static useModule(path) { ___import___.__use("/@module/" + ___import___.__fixedPath(path) + '.js'); } /** * Imports once the environment script or a component * @param {string} path the path */ static useEnvironment(path) { ___import___.__use(___import___.__fixedPath("/@envjs/" + path)); } /** * Imports once the CSS * @param {string} path the path */ static importCss(path) { let link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; link.href = path; document.head.appendChild(link); } /** * Imports once the script * @param {string} path the path */ static importScript(path) { $.ajax({ url: path, dataType: "script", async: false }); } } const importCss = ___import___.importCss; const importScript = ___import___.importScript; const oimpc = ___import___.useComponent; const oimpm = ___import___.useModule; const oimpe = ___import___.useEnvironment;;class ___script___ { static run(url, asynchronous = false, s = null, e = null) { $.ajax({ url: url, dataType: "script", async: asynchronous, success: s, error: e }); } };var page = undefined; class ___app___ { static __base = undefined; static __$nodePage; static __$baseAppNode; static __$pageStyle; static __$baseStyle; static __histories = []; static __loadedPages = {}; static __currentURL = null; static CURRENT_URL = null; static __lastShared = undefined; /** * Loads base component it is not loaded */ static __loadBase() { if (___app___.__base) return; // Loading Base ___app___.__base = new Base({ children: { _: 'main', id: 'oapp' } }); let baseOph = ___app___.__base; let renderedNode = ___render___.toNode(baseOph, true); document.body.appendChild(renderedNode); ___app___.__$baseAppNode = document.getElementById("oapp"); ___render___.__placedOphoseInstances = []; // Loading style node ___app___.__$pageStyle = document.createElement("style"); document.head.appendChild(___app___.__$pageStyle); // Init } /** * Loads ophose page at requested URL * @param {*} requestUrl requested URL */ static __loadAt(requestUrl) { /** * Returns the URL with pages prefix * @param {string} url the URL * @returns the URL with pages prefix */ let prepareUrlWithPagesPrefix = (url) => { if (url.startsWith('/')) url = url.substring(1, url.length); if (url.endsWith('/')) url = url.substring(0, url.length - 1); return '/pages/' + url; } let scrollToUrlId = (ms) => { setTimeout(() => { if(!window.location.hash) { window.scrollTo(0, 0); return; }; let id = window.location.hash.substring(1, window.location.hash.length); let element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth' }); }else{ window.scrollTo(0, 0); } }, ms); } ___app___.__loadBase(); ___event___.callEvent("onPageLoad", requestUrl); ___app___.CURRENT_URL = requestUrl; requestUrl = prepareUrlWithPagesPrefix(requestUrl); let urlAndQuery = requestUrl.split("?"); let urlFullPath = urlAndQuery[0].split('#')[0]; let urlPath = urlAndQuery[0].split('/'); if (urlPath[urlPath.length - 1] == "") { urlPath.pop(urlPath.length - 1); } if (requestUrl == "/pages/") { urlFullPath = "/pages/index"; } if(___app___.__currentURL == urlFullPath) { scrollToUrlId(0); return; } ___app___.__currentURL = urlFullPath; // Returns fixed JSON response let getUrlQueries = () => { let result; $.ajax({ type: 'POST', url: '/@query/', async: false, data: {url: urlFullPath}, success: function (r) { result = r; } }); return result; } let jsonResponse = getUrlQueries(); let urlExists = jsonResponse["valid"]; let urlRequest = jsonResponse["path"]; // Define URL queries let urlQueries = {}; urlQueries.query = jsonResponse["variables"]; if(urlAndQuery.length == 2) { urlQueries.get = {}; let urlQueriesArray = urlAndQuery[1].split('#')[0].split("&"); for (let urlQuery of urlQueriesArray) { let urlQueryArray = urlQuery.split("="); urlQueries.get[urlQueryArray[0]] = urlQueryArray[1]; } } if (!urlExists && urlFullPath != "error") { ___app___.__loadAt("error"); return; } let Page = undefined; let loadPage = (PageClass) => { if(!___app___.__loadedPages[urlRequest]) ___app___.__loadedPages[urlRequest] = {}; ___app___.__loadedPages[urlRequest]["pageClass"] = PageClass; let loadedPage = new PageClass(urlQueries); for (let ophoseInstance of ___render___.__placedOphoseInstances) { let node = ophoseInstance.__node; if(!___app___.__$baseAppNode.contains(node)) continue; ophoseInstance.__processRemove(); } ___render___.__placedOphoseInstances = []; loadedPage.onCreate(); // Check if page has been redirected if (loadedPage.__redirected) { ___app___.__loadAt(loadedPage.__redirected); return; } // Load content let pageNode = ___render___.toNode(loadedPage, true); loadedPage.onLoad(); ___app___.__$baseAppNode.replaceWith(pageNode); ___app___.__$baseAppNode = pageNode; loadedPage.__applyStyle(); ___app___.__pageInstance = loadedPage; loadedPage.__setNode(pageNode); loadedPage.onPlace(pageNode); page = loadedPage; scrollToUrlId(100); ___event___.callEvent("onPageLoaded", requestUrl); } if (urlFullPath != "error" && ___app___.__loadedPages[urlRequest]) { let pageClass = ___app___.__loadedPages[urlRequest]['pageClass']; loadPage(pageClass); return; } // Page loading ___script___.run(urlRequest, false, () => { Page = ___app___.__getShared(); loadPage(Page); }, () => { if (urlFullPath == "error") return; ___app___.__loadAt("error"); } ); } /** * Sets application title * @param {string} newTitle the new title */ static setTitle(newTitle) { document.title = newTitle; } /** * Used to export an object * @param {object} object the object */ static share(object) { ___app___.__lastShared = object; } /** * Used to get the last shareed object and reset it * @returns {object} the last shareed object */ static __getShared() { let e = ___app___.__lastShared; ___app___.__lastShared = undefined; return e; } /** * Returns the base component * * @returns {Base} the base component */ static getBase() { return ___app___.__base; } } const app = ___app___; const oshare = ___app___.share; ;/** * Class representing ophose event */ class ___event___ { // Associative array of eventname: callback static __eventCallbacks = {}; /** * Registers a callback for an event * (note that callbacks are registered in apparition order) * @param {string} eventName the event name ("onPageLoad" for example) * @param {object} callback the callback (called with: value passed * by event caller, event name) */ static addListener(eventName, callback) { eventName = eventName.trim().toLowerCase(); if (!___event___.__eventCallbacks[eventName]) { ___event___.__eventCallbacks[eventName] = []; } ___event___.__eventCallbacks[eventName].push(callback); } static callEvent(eventName, value) { eventName = eventName.trim().toLowerCase(); if (!___event___.__eventCallbacks[eventName]) { return; } for (const callback of ___event___.__eventCallbacks[eventName]) { callback(value, eventName); } } } // History listener window.addEventListener("popstate", (event) => { if (event.state === null) return; ___app___.__loadAt(event.state); });;class ___element___ { constructor() { } /** * Abstract method to define style of the element */ style() { return ''; } /** * Returns page base * @returns {___base___} base */ getBase() { return ___app___.__base; } };/** * Represents an ophose component. */ class ___component___ extends ___element___ { static __allComponents = {}; static __SCREENS_SIZES = { sm: 576, md: 768, lg: 992, xl: 1200 } /** * Returns the component ID * @param {*} componentId the component id * @returns {___component___} component matching id */ static getComponent(componentId) { return ___component___.__allComponents[componentId]; } constructor(props = {}) { super(); this.props = (props && typeof props == "object" && props) || {}; if(this.props.c) this.props.children = this.props.c; /** * @private */ this.__propsOn /** * @private */ this.__styleNode = undefined; /** * @private */ this.__node = undefined; /** * @private */ this.__loadingContext = null; /** * @private */ this.compUID = this.constructor.name.toLowerCase(); /** * @private */ this.__componentUniqueClassName = "__oc_" + this.compUID; this.__componentUniqueClassName = this.__componentUniqueClassName.prettyHashCode(); /** * @private */ this.__componentUniqueId = this.__componentUniqueClassName + (Object.keys(___component___.__allComponents).length + 1); ___component___.__allComponents[this.__componentUniqueId] = this; /** * @private */ this.__myModules = []; /** * @private */ this.__lives = []; } /** * Sets component node * @param {HTMLElement} node the node * @private */ __setNode(node) { this.__node = node; } /** * @returns component unique id (unique for every component) * @private */ __getComponentUniqueId() { return this.__componentUniqueId; } // Style /** * @returns {object} Returns the additional styles as {screen: style} (for example: { * md: ` * %self { * background-color: red; * } * ` * }) */ styles() { return null; } /** * @returns {HTMLStyleElement} component style node * @private */ __getStyleNode() { return this.__styleNode; } /** * Creates style node * @returns {HTMLStyleElement} style node (or undefined if already exists) * @private */ __createStyleNode() { if(this.__getStyleNode()) return undefined; let styleNode = document.createElement('style'); document.head.append(styleNode); this.__styleNode = styleNode; return styleNode; } /** * Reloads component style * @private */ ___reloadStyle() { let style = this.style().replaceAll('%self', '.' + this.__getComponentUniqueId()); let additionalStyles = this.styles(); if(additionalStyles) { for(let screen in additionalStyles) { if(!___component___.__SCREENS_SIZES[screen]) { dev.error('Invalid screen size: ' + screen); continue; } let additionalStyle = additionalStyles[screen].replaceAll('%self', '.' + this.__getComponentUniqueId()); style += '\n@media screen and (max-width: ' + ___component___.__SCREENS_SIZES[screen] + 'px) {\n' + additionalStyle + '\n}\n'; } } let styleNode = this.__getStyleNode(); styleNode.innerText = style; } /** * Applies style * @private */ __applyStyle() { if (this.__getStyleNode()) return; this.__createStyleNode(); Live.__currentReadingStyleComponent = this; this.___reloadStyle(); Live.__currentReadingStyleComponent = undefined; } /** * Moves html properties to the child with the given name (set undefined to remove it) * @param string name the name */ propsOn(name) { this.__propsOn = name; } /** * @returns {HTMLElement} component node */ getNode() { return this.__node; } /** * Return rendered node * @param {HTMLElement} node the created node * @returns {HTMLElement} node * @private */ __place(node = undefined) { if(node instanceof DocumentFragment) { for(let n of node.oList) { n.classList.add(this.__getComponentUniqueId()); n.ophoseInstance = this; } }else{ node.classList.add(this.__getComponentUniqueId()); } node.ophoseInstance = this; this.__setNode(node); this.__applyStyle(); this.onPlace(node); for(let module of this.__myModules) { node.classList.add(module._getClassName()); module.onPlace(this, node); } } /** * Abstract method to render HTML * @returns HTML */ render() { } /** * Removes component from the DOM & Ophose instances */ remove() { this.__processRemove(); this.__node.remove(); ___render___.__placedOphoseInstances.splice(___render___.__placedOphoseInstances.indexOf(this), 1); } /** * Append child to component * * @param {object} child the child */ appendChild(child) { this.__node.append(___render___.toNode(child, true)); } /** * Called when component will be removed from the DOM * @private */ __processRemove() { this.onRemove(this.__node); for(let module of this.__myModules) { module.__onRemove(this, this.__node); } if(this.__styleNode) { this.__styleNode.remove(); this.__styleNode = undefined; } for(let live of this.__lives) { live.__placedStyleComponents.splice(live.__placedStyleComponents.indexOf(this), 1); for(let placedLive of [...live.__placedLiveNodes]) { if(this.__node.contains(placedLive.node)) { live.__placedLiveNodes.splice(live.__placedLiveNodes.indexOf(placedLive), 1); } } for(let placedTextLive of [...live.__placedLiveTextNodes]) { if(this.__node.contains(placedTextLive)) { live.__placedLiveTextNodes.splice(live.__placedLiveTextNodes.indexOf(placedTextLive), 1); } } } ___component___.__allComponents[this.__componentUniqueId] = undefined; } // Relatives /** * Returns the first parent component of the given type * @param {*} componentType the component type * @returns {Ophose.Component} the component or null if not found */ findFirstParentComponentOfType(componentType) { let parent = this.__node.parentElement; while(parent) { if(parent.ophoseInstance && parent.ophoseInstance instanceof componentType) { return parent.ophoseInstance; } parent = parent.parentElement; } return null; } // Lifecycle /** * Called when component is added to the DOM * @param {HTMLElement} element the element */ onPlace(element) { } /** * Called when component will be removed from the DOM * @param {HTMLElement} element the element */ onRemove(element) { } // MODULE /** * Add module to the current component * @param {___module___} module the module */ addModule(module) { this.__myModules.push(module); module.addComponent(this); } };class ___module___ extends ___element___ { static __addedModuleStyles = {}; /** * Constructor * @param {string} moduleUID the module UID (unique name) * @param {___component___} component the listen component */ constructor() { super(); /** * @private */ this.__moduleUID = "__ocmodule__" + this.constructor.name.toLowerCase(); /** * @private */ this.__components = []; this.__applyStyle(); } /** * @returns class name (as DOM element) * @private */ _getClassName() { return this.__moduleUID; } /** * @returns {HTMLStyleElement} component style node * @private */ __getStyleNode() { return ___module___.__addedModuleStyles[this._getClassName()]; } /** * @private */ __reloadStyle() { let styleNode = this.__getStyleNode(); if (!styleNode) return; styleNode.innerText = this.style().replaceAll('%self', '.' + this._getClassName()); } /** * Applies style * @private */ __applyStyle() { if (this.__getStyleNode()) return; let styleNode = document.createElement('style'); ___module___.__addedModuleStyles[this._getClassName()] = styleNode; document.head.append(styleNode); this.__reloadStyle(); } /** * Sets component to the current module instance * @param {___component___} the component * @private */ addComponent(component) { if(!this.__components.includes(component)) return; this.__components.push(component); this.onComponentAdded(component); } /** * @returns {___component___[]} components of the module */ getComponents() { return this.__components; } /** * Removes component from the current module instance * @param {___component___} the component */ removeComponent(component) { // Remove style node if no components associated if (this.__components.length == 0) { this.__getStyleNode().remove(); } this.onComponentRemoved(component); } /** * This function is called when a component is added to the module * @param {___component___} component the component * @abstract */ onComponentAdded(component) { } /** * This function is called when a component is removed from the module * @param {___component___} component the component * @abstract */ onComponentRemoved(component) { } /** * This function is called when an element implementing this * module is placed and rendered. * @param {___component___} component the component * @param {HTMLElement} element DOM element */ onPlace(component, element) { } /** * This function is called when an element implementing this * module is removed. * @param {___component___} component the component * @param {HTMLElement} element DOM element */ onRemove(component, element) { } /** * @private */ __onRemove(component, element) { this.__components.splice(this.__components.indexOf(component), 1); this.onRemove(component, element); } };/** * Represents an ophose page */ class ___page___ extends ___component___ { /** * Page constructor * @param {dict} urlQueries url queries if any (ex: pageId, productId...) */ constructor(urlQueries) { super(); this.urlQueries = urlQueries; this.__redirected = false; } /** * This method is called once the page is created * (only once) */ onCreate() { } /** * This method is called when the page is left * through route.go(other_page) */ onLeave() { } /** * This method is called when the page is loaded * (every time) */ onLoad() { } redirect(url) { if (this.__redirected) { dev.error("This page has already been redirected."); return; } this.__redirected = url; } };class ___base___ extends ___component___ { static registered = {}; constructor(props) { super(props); } /** * Register a new custom element * @param {*} tagName the name of the custom element * @param {*} clsComponent the class of the component */ register(tagName, clsComponent) { // Check if the tag name is not a native HTML tag like div, span, etc. let element = document.createElement(tagName); if(!(element instanceof HTMLUnknownElement) && element.constructor !== HTMLElement){ dev.error(`The tag name ${tagName} is not a valid custom element.\nThe element is: ${element}`); return; } ___base___.registered[tagName] = clsComponent; } };class ___route___ { /** * To go to another page (without reloading base) * @param {page} requestUrl requested URL */ static go(requestUrl) { if (requestUrl === undefined || requestUrl === null) { return; } if(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/.test(requestUrl)) { window.location = requestUrl; return; } ___app___.__loadAt(requestUrl); if (requestUrl != ___app___.__currentURL) { window.history.pushState(requestUrl, '', requestUrl); } } } const route = ___route___;;/** * Class representing ophose environment */ class ___env___ { /** * Sends asychronous request (POST) to the environment * and return JSON parsed result * @param {string} endpoint the environment and its endpoint (for example: /myEnv/myEndpoint) * @param {Array} data post data */ static async post(endpoint, data = null) { let jsonResult = null; let error = false; if(data instanceof Live) data = data.get(); let options = { method: 'POST', headers: {}, useDirectives: true, ...(data && data.options) ?? {} } if(data && data.options) data.options = undefined; if(options.toFormData) { let formData = new FormData(); for(let key in data) { formData.append(key, data[key]); } data = formData; } if(!(data instanceof FormData)) { if(typeof data === 'object') { options.headers['Content-Type'] = 'application/json'; data = JSON.stringify(Live.flatten(data)); } else if(typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean' || data === null) { if(data === null) data = ''; options.headers['Content-Type'] = 'text/plain'; } } await $.ajax({ type: options.method, url: (endpoint.startsWith('/@/') || endpoint.startsWith('/api/' || endpoint.startsWith('/@api/') || endpoint.startsWith('/@env/'))) ? endpoint : '/@/' + endpoint, data: data, cache: false, processData: false, contentType: false, beforeSend: (xhr) => { let token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); xhr.setRequestHeader('X-Csrf-Token', token); for(let header in options.headers) { xhr.setRequestHeader(header, options.headers[header]); } }, success: (result) => { jsonResult = result; }, error: (result) => { error = true; try { jsonResult = JSON.parse(result.responseText); } catch (e) { jsonResult = result.responseText; } } }); if (error) { throw jsonResult; } if(options.useDirectives && jsonResult?.ophose_encoded_directives) { for(let directive of jsonResult.ophose_encoded_directives) { switch(directive.type) { case 'redirect': route.go(directive.data); break; } } return; } return jsonResult; } /** * Sends files to an environment request * @param {string} env the environment path * @param {string} request the rest request * @param {string} name the file(s) name * @param {boolean} multiple if user can send multiple file */ static async sendFiles(env, request, filename, callbackSuccess, callbackError, multiple = false, acceptTypes = "*") { let url = ___env___.constructURL(env, request); let __form = document.createElement("form"); __form.enctype = "multipart/form-data"; let __fileInput = document.createElement("input"); __fileInput.type = "file"; __fileInput.accept = acceptTypes; if (multiple) { __fileInput.multiple = true; } __fileInput.id = filename; __fileInput.name = filename; __form.appendChild(__fileInput); __fileInput.click(); let dataReceived = null; __fileInput.onchange = e => { let form_data = new FormData(__form); $.ajax({ url: url, // <-- point to server-side PHP script dataType: 'json', // <-- what to expect back from the PHP script, if anything cache: false, contentType: false, processData: false, data: form_data, type: 'POST', method: 'POST', success: (r) => { callbackSuccess(r) // <-- display response from the PHP script, if any }, error: (r) => { callbackError(r); } }); }; } } /** * Class representing an environment entity */ class ___env_entity___ { constructor(id, data) { this.__id = id; this.data = data; } /** * Returns entity id * @returns {*} id */ getId() { return this.__id; } /** * Method called when entity is updated * @param {*} data the new data * @abstract */ update(data) { } } /** * Class representing an environment entity manager */ class ___env_entity_manager___ { /** * Environment constructor * @param {___env_entity___} * @param {boolean} saveEntitiesInCache if environment should saves */ constructor(EntityClass, saveEntitiesInCache = true) { this.__EntityClass = EntityClass; this.__entities = {}; this.__saveEntitiesInCache = saveEntitiesInCache; } /** * Returns entity (null if not overriden) * (Note that if this function isn't overriden, it'll * return entity from array, or entity created from * createEntity(id)) * @param {*} id the entity id * @returns {___env_entity___} entity */ async getEntity(id) { if (this.saveEntitiesInCache && this.__entities[id]) { return this.__entities[id]; } return await this.__generateEntity(id); } /** * Generates entity then returns it * @param {*} id the entity id * @returns entity */ async __generateEntity(id) { let entity = await this.createEntity(id); if (entity === undefined) { return undefined; } if (this.saveEntitiesInCache) { this.__entities[id] = entity; } return entity; } /** * This method is called when an entity is needed to * be created * @param {*} id the entity id * @returns created entity (null by default) */ async createEntity(id) { return null; } /** * Saves entity in cache with data * @param {*} id the entity id * @param {*} data the entity data */ saveEntity(id, data) { if(!this.__saveEntitiesInCache) return; if(this.__entities[id]) { this.__entities[id].data = data; this.__entities[id].update(data); return; } this.__entities[id] = new this.__EntityClass(id, data); } /** * Cleans all created entities */ cleanEntities() { this.__entities = {}; } } const oenv = ___env___.post; const oenvSendFiles = ___env___.sendFiles;;class Live { static __currentReadingStyleComponent = undefined; static __calledLives = {}; constructor(value) { this.value = value; this.__placedLiveTextNodes = []; this.__placedLiveNodes = []; this.__placedStyleComponents = []; this.__callbackListeners = []; this.__ruleId = '' + Math.random(); this.__rules = []; this.__rulesDependencies = []; this.__updateOnlyIfValueChanges = true; } /** * Adds a callback listener * @param {function} callback the callback * @returns {Live} the live variable */ subscribe(callback) { this.__callbackListeners.push(callback); return this; } /** * Removes a callback listener * * @param {function} callback the callback * @returns the callback */ unsubscribe(callback) { this.__callbackListeners.splice(this.__callbackListeners.indexOf(callback), 1); } /** * Toggles the value if it is a boolean * * @returns the value or undefined if not a boolean */ toggle() { if(typeof this.value != 'boolean') { dev.error('Cannot toggle a non-boolean value', this.value); return; }; this.set(!this.value); return this.value; } /** * Returns live value and automatically assigns as observer of this * @returns the value */ get() { if (Live.__currentReadingStyleComponent) { let component = Live.__currentReadingStyleComponent; this.__placedStyleComponents.push(component); component.__lives.push(this); } for(let id in Live.__calledLives) { if(id === this.__ruleId) continue; Live.__calledLives[id].push(this); } return this.value; } /** * Updates the value * @param {*} value the value */ set(value) { let oldValue = this.value; this.value = value; for(let rule of this.__rules) { this.value = rule(this.value, oldValue); } if(this.__updateOnlyIfValueChanges) { if(typeof this.value !== 'object') if(this.value === oldValue) return; } Live.__onValueChange(this, this.value, oldValue); } /** * Updates the value with a callback * @param {function} callback the callback (takes the current value as argument and should return the new value) * @returns the value */ update(callback) { let newValue = callback(this.value); this.set(newValue); return newValue; } /** * Adds a rule to the live variable (a rule is a function that takes the current value and returns the new value) * @param {*} callback the callback * @returns the live variable */ rule(callback) { this.__rules.push(callback); Live.__calledLives[this.__ruleId] = []; callback(this.value, this.value); for(let live of Live.__calledLives[this.__ruleId]) { if(this.__rulesDependencies.indexOf(live) === -1) { this.__rulesDependencies.push(live); live.subscribe(() => { this.set(this.value); }); } } // Remove key from called lives delete Live.__calledLives[this.__ruleId]; return this; } /** * Adds minimum value rule to the live variable * @param {number} min the minimum value * @returns the live variable */ min(min) { return this.rule((value) => Math.max(value, min)); } /** * Adds maximum value rule to the live variable * @param {number} max the maximum value * @returns the live variable */ max(max) { return this.rule((value) => Math.min(value, max)); } /** * Updates the value only if the value changes * @param {boolean} value the value * @returns the live variable */ updateOnlyIfValueChanges(value) { this.__updateOnlyIfValueChanges = value; return this; } /** * Adds a value to the current value * @param {*} value the value to add */ add(value) { if(Array.isArray(this.value)) { this.value.push(value); this.set(this.value); return; } this.set(value + this.value); } /** * Removes a value from the current value * @param {*} value the value to remove */ remove(value) { if(Array.isArray(this.value)) { this.value.splice(this.value.indexOf(value), 1); this.set(this.value); return; } this.set(this.value - value); } /** * Called when value changes and process needed updates * @param {*} liveVar the live variable * @param {*} newValue the new value * @param {*} oldValue the old value */ static __onValueChange(liveVar, newValue, oldValue) { for(let node of liveVar.__placedLiveTextNodes) { node.textContent = newValue; } for(let placedLive of liveVar.__placedLiveNodes) { let args = placedLive.lives.map((live) => live.get()); let newNode = ___render___.toNode(placedLive.callback(...args), true); if(placedLive.node instanceof DocumentFragment) { let node = placedLive.node.oList[0]; for(let i = 1; i < placedLive.node.oList.length; i++) { placedLive.node.oList[i].remove(); } node.replaceWith(newNode); placedLive.node = newNode; node.remove(); } else { placedLive.node.replaceWith(newNode); placedLive.node = newNode; } if(placedLive.selfClassName) placedLive.node.classList.add(placedLive.selfClassName); } for (let component of liveVar.__placedStyleComponents) { component.___reloadStyle(); } for (let callback of liveVar.__callbackListeners) { callback(newValue, oldValue); } } /** * Local lives loaded from local storage */ static __localLives = {}; /** * Returns a live variable from a local storage key * * @param {*} key the key * @param {*} defaultValue the default value if the key is not set * @returns {Live} the live variable */ static local(key, defaultValue = null) { key = '__local_live.' + key; let value = localStorage.getItem(key); let live = Live.__localLives[key]; if(value === null) { localStorage.setItem(key, defaultValue); value = defaultValue; } if(live === undefined) { live = new Live(value); live.addCallbackListener((newValue) => { localStorage.setItem(key, newValue); }); Live.__localLives[key] = live; } return live; } /** * Flattens an object with lives (replaces lives with their values recursively) * @param {*} obj the object * @returns the flattened object */ static flatten(obj) { obj = Object.assign({}, obj); for (let key in obj) { if (obj[key] instanceof Live) { obj[key] = obj[key].get(); } if (typeof obj[key] === 'object') { obj[key] = Live.flatten(obj[key]); } } return obj; } } class PlacedLive { constructor(...livesAndCallback) { let callback = livesAndCallback.pop(); this.lives = livesAndCallback; this.callback = callback; this.node = undefined; this.selfClassName = undefined; for(let live of this.lives) { live.__placedLiveNodes.push(this); } } } Live.prototype.valueOf = function (liveId) { return this.get(liveId); } /** * Creates a dynamic rendering depending on the live variables * @param {...Live} livesAndCallback the live variables and the callback (last argument) * @returns {PlacedLive} the placed live */ function dyn(...livesAndCallback) { return new PlacedLive(...livesAndCallback); } /** * Creates a live variable * @param {any} value * @returns {Live} the live variable */ function live(value) { if (value instanceof Live) return value; return new Live(value); } /** * Reacts to live variables when they change * @param {...any} livesAndCallback the live variables and the callback (last argument) */ function watch(...livesAndCallback) { let callback = livesAndCallback.pop(); for(let live of livesAndCallback) { live.subscribe(async () => { let args = livesAndCallback.map((live) => live.get()); callback(...args); }); } (async () => callback(...livesAndCallback.map((live) => live.get())))(); };var __OPH_APP_BUILD__ = false; var __OPHOSE_PAGES = {}; const Ophose = { App: ___app___, Render: ___render___, Base: ___base___, Component: ___component___, Page: ___page___, Module: ___module___, Environment: ___env___, Event: ___event___, } const ofi = {};;;__OPH_APP_BUILD__=true;