/** Datei: plugins/rest/assets/js/components/button.js **/ /** * /arunox/components/button.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ ARUNOX.Templates.register( 'button', { hooks : { config : function( config ) { if( config.type ) config.attributes.type ??= config.type; if( config.name ) config.attributes.name ??= config.name; if( config.value ) config.attributes.value ??= config.value; if( config.title ) config.attributes.title ??= config.title; if( config.target ) config.attributes.target ??= config.target; return config; }, wrapper : function( root ) { const config = this.instance.config; if( !config.dropdowns ) return root; const wrapper = ARUNOX.Templates.render( 'div', { classes : 'button__wrapper', }) this.instance.nodes.wrapper = wrapper; this.instance.apply('dropdown'); wrapper.root.append( root ); return wrapper.root; }, dropdown : function() { const config = this.instance.config; const wrapper = this.instance.nodes.wrapper; if( !wrapper || !config.dropdowns ) return; const menu = wrapper.render('div', { nodeName : 'menu', classes : 'button__dropdown' }); for( const item of config.dropdowns ) { const itemContainer = menu.render( 'div', { classes : 'button__dropdown__item' }); itemContainer.render( 'button', Object.assign({ color : 'secondary', type : 'button', block : 'button__dropdown' }, item.config ), this.instance.data); } this.instance.root.addEventListener( 'click', ( event ) => { event.stopPropagation(); wrapper.root.classList.toggle('is--open'); }); wrapper.root.addEventListener( 'mouseleave', () => { wrapper.root.classList.remove( 'is--open'); const focusedElement = wrapper.root.querySelector(':focus'); if( focusedElement ) focusedElement.blur(); }); }, }, htmlTag : function( config, data, ctx ) { return config.href ? 'a' : 'button'; }, builder : function( config, data ) { const className = config?.name || 'button'; const block = config.block ? `${className} ${config.block}__${className}` : className; this.root.classList.add( ...block.split(' ') ); if( config.label ) { this.root.classList.add( 'has--label' ); this.render( 'span', { classes : `${block}__label label`, content : config.label, }); } if( config.href ) { this.root.href = ARUNOX.Router.parseUrl( config.href, data ); this.root.addEventListener( 'click', ( event ) => { event.stopPropagation(); }); return; } if( config.type ) { const form = this.root.closest( 'form' ); if( form ) { if( !form.id ) form.id = crypto.randomUUID(); this.root.setAttribute( 'form', form.id ); } } if( config.action && !config.dropdowns ) { this.root.addEventListener( 'click', ( event ) => { event.preventDefault(); event.stopPropagation(); if( this.root.name && this.root.value !== '' && this.root.value !== 'undefined' ) { ARUNOX.Router.handle( config.action, { [`${this.name}`] : this.value }, data, 'rest' ); } else ARUNOX.Router.handle( config.action, {}, data, 'rest' ); }); } }, }); ARUNOX.Templates.register( 'buttons', { htmlTag : 'div', hooks : { config : function( config ) { config.datasets.arx.node = 'buttons'; config.content ??= config.buttons || []; return config; }, }, builder : function( config, data ) {} }); /** Datei: plugins/rest/assets/js/components/tabs.js **/ /** * /arunox/components/tabs.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ ARUNOX.Templates.register( 'tabs', { htmlTag : 'div', hooks : { config : function( config, data ) { config.classes.push( 'tabs' ); return config; }, buildRootAfter : function() { const config = this.instance.config; config.query = window.location.hash.match( /\?(.*)/ ); config.hash = window.location.hash.substring(1).replace( /\?.*/, '' ); config.filter = new URLSearchParams( config.query ? config.query[1] : '' ); config.isNested = this.instance.root.closest( '.dialog, .tabs__content' ) !== null; if( config.isNested ) { let match = ARUNOX.Router.history.match( /.*(?:#([a-zA-Z0-9]+)).*/ ); config.hash = this.instance.root.dataset.activeTab || match ? match[1] : ''; config.filter = new URLSearchParams(); } }, }, builder : function( config, data ) { this.render( 'tabs.buttons', { nodeName : 'buttons', tabs : config.tabs, }); this.render( 'tabs.contents', { nodeName : 'contents', tabs : config.tabs, }); }, }); ARUNOX.Templates.register( 'tabs.buttons', { extends : 'buttons', hooks : { config : function( config, data ) { config.classes.push( 'tabs__list' ); return config; }, content : function( content, config, data ) { for( let [ index, tabConfig ] of config.tabs.entries() ) { tabConfig = Object.assign( { index }, tabConfig ); delete tabConfig.content; content.push( ARUNOX.Templates.config( 'tabs.button', tabConfig, data ) ); } return content; }, }, }); ARUNOX.Templates.register( 'tabs.button', { extends : 'button', hooks : { config : function( config, data ) { config.classes.push( 'tabs__item' ); config.color = 'secondary'; config.arx.tab = config.id; return config; }, select : function() { const tabs = this.instance.closest( 'tabs' ); const tabsConfig = tabs.config; const config = this.instance.config; const buttons = tabs.nodes.buttons; const contents = tabs.nodes.contents; for( const button of buttons.root.childNodes ) { button.classList.remove('tabs__item--active' ); if( button.dataset.arxTab == config.arx.tab ) { this.instance.root.classList.add( 'tabs__item--active' ); } } for( const content of Object.values( contents.root.childNodes ) ) { if( content.dataset.arxTab == config.arx.tab ) { content.classList.add('tabs__content--active' ); content.style.display = ''; content.ARX.apply('show'); } else { content.classList.remove('tabs__content--active' ); content.style.display = 'none'; content.ARX.apply('hide'); } } if( tabsConfig.isNested ) return; if( this.instance.root.dataset.tabFilter ) history.pushState(null, null, `#${config.arx.tab}?${config.arx.filter}` ); else if( config.arx.tab ) history.pushState(null, null, `#${config.arx.tab}` ); else history.pushState(null, null, '' ); }, afterRender : function( config, data ) { const tabs = this.instance.closest( 'tabs' ); const tabsConfig = tabs.config; if( config.action ) { this.instance.root.classList.add( 'tabs__item--action' ); this.instance.root.addEventListener( 'click', () => ARUNOX.Router.handle( config.action, {}, data, 'rest' ) ); return; } if( config.type === 'submit' ) { this.instance.root.classList.add( 'tabs__item--action' ); return; } this.instance.root.addEventListener( 'click', () => this.instance.apply('select') ); if( ( tabsConfig.hash && tabsConfig.hash === config.arx.tab ) || ( !tabsConfig.hash && ( config.active || ( config.index === 0 && !tabsConfig.tabs.some( t => t.active ) ) ) ) ) { queueMicrotask( () => this.instance.apply('select') ); } }, }, }); ARUNOX.Templates.register( 'tabs.contents', { htmlTag : 'div', hooks : { config : function( config, data ) { config.classes.push( 'tabs__content', 'scroll__viewport' ); config.arx.tab = config.id; return config; }, }, builder : function( config, data ) { for( const tabConfig of config.tabs ) { if( tabConfig.action || tabConfig.type === 'submit' ) continue; this.render( 'tabs.content', tabConfig ); } }, }); ARUNOX.Templates.register( 'tabs.content', { htmlTag : 'div', hooks : { config : function( config, data ) { config.classes.push( 'tabs__content__item' ); config.arx.tab = config.id; config.styles.display = 'none'; delete config.icon; delete config.label; return config; }, }, builder : function( config, data ) {}, }); /** Datei: plugins/rest/assets/js/components/table.js **/ /** * /arunox/components/table.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ ARUNOX.Templates.register( 'table', { hooks : { config : function( config, data ) { config.classes.push( 'table' ); return config; }, wrapper : function( root ) { const wrapper = ARUNOX.Templates.render( 'div', { classes : 'table__wrapper', }) this.instance.nodes.wrapper = wrapper; this.instance.apply('toolbar'); wrapper.root.append( root ); return wrapper.root; }, toolbar : function() { const config = this.instance.config; const filters = this.instance.filter( 'filters', config.filter, config.columns ); const wrapper = this.instance.nodes.wrapper; const toolbar = wrapper.render( 'div', { nodeName : 'toolbar', classes : 'table__toolbar', }); if( config.search ) { toolbar.render( 'div', { nodeName : 'searchContainer', classes : 'table__search', content : [ config.search ? ARUNOX.Templates.config( 'input', { nodeName : 'searchInput', type : 'text', classes : 'table__search__input', attributes : { placeholder : 'Suchen...', }, onInput : () => this.instance.apply( 'search', toolbar.nodes.searchInput.root.value ), }) : null, ARUNOX.Templates.config( 'button', { nodeName : 'searchReset', color : 'danger', type : 'button', block : 'table__search', title : 'Suche zurücksetzen', icon : 'bi bi-funnel', apply : [ 'active', false ], onClick : () => this.instance.apply( 'reset', 'search', config ), }), ], }); } if( filters ) { toolbar.render( 'buttons', { nodeName : 'filterContainer', classes : 'table__filter', content : [ ...filters, ARUNOX.Templates.config( 'button', { nodeName : 'filterReset', color : 'danger', type : 'button', block : 'table__filter', label : 'Filter zurücksetzen', icon : 'bi bi-funnel', apply : [ 'active', false ], onClick : () => this.instance.apply( 'reset', 'filter', config ), }) ], }); } if( config.buttons ) { const buttons = toolbar.render( 'buttons', { content : config.buttons, nodeName : 'buttons', classes : 'table__buttons', }); } }, filters : function( filters, columns ) { const buttons = []; if( !Array.isArray( filters ) ) filters = [ filters ]; for( const column of filters ) { const config = columns.find( item => item.key == column ); if( !config?.filterable || !config?.options ) continue; for( const option of config.options ) { buttons.push( ARUNOX.Templates.config( 'button', { nodeName : `filter${config.key.toUpperCaseFirst()}-${option.value}`, color : option?.color, type : 'button', block : 'table__filter', label : option?.label, icon : option?.icon, apply : [ 'active', false ], onClick : () => this.instance.apply( 'filter', config.key, option?.value ), }) ); } } return buttons; }, filter : function( column, filterValue ) { // console.log( 'FILTER', column, filterValue ); const toolbar = this.instance.nodes.toolbar; const tableBody = this.instance.nodes.body; const tableRows = tableBody.root.querySelectorAll('tr'); filterValue = filterValue.toString().toLowerCase(); let visibleRows = []; tableRows.forEach(row => { const filterData = row.dataset?.[`arxFilter${column.toUpperCaseFirst()}`] || ''; const isVisible = filterData == filterValue; row.style.display = isVisible ? null : 'none'; if( isVisible ) { visibleRows.push( row ); } }); toolbar.nodes.filterContainer.root.childNodes.forEach( element => { element.ARX.apply( 'active', element.ARX.config.nodeName == `filter${column.toUpperCaseFirst()}-${filterValue}` ); }); toolbar.nodes.filterReset.apply( 'active', visibleRows.length != tableRows.length ); visibleRows.forEach( row => tableBody.root.append( row ) ); }, search : function( searchValue ) { // console.log( 'SEARCH', searchValue ); const toolbar = this.instance.nodes.toolbar; const tableBody = this.instance.nodes.body; const tableRows = tableBody.root.querySelectorAll('tr'); searchValue = searchValue.toLowerCase(); let visibleRows = []; tableRows.forEach(row => { const searchData = row.dataset?.arxSearch || ''; const isVisible = searchData.includes( searchValue ); row.style.display = isVisible ? null : 'none'; if( isVisible ) { visibleRows.push( row ); } }); toolbar.nodes.searchReset.apply( 'active', visibleRows.length != tableRows.length ); visibleRows.forEach( row => tableBody.root.append( row ) ); }, reset : function( config, type ) { // console.log( 'RESET', type ); const toolbar = this.instance.nodes.toolbar; const tableBody = this.instance.nodes.body; const tableRows = tableBody.root.querySelectorAll('tr'); tableRows.forEach(row => { row.style.display = null; }); const query = window.location.hash.match( /\?(.*)/ ); const hash = window.location.hash.substring(1).replace( /\?.*/, '' ); const filter = new URLSearchParams( query ? query[1] : '' ); if( config?.query ) { for( const [ index, value ] of config.query.entries() ) { if( filter.has( index ) && filter.get( index ) == value ) { filter.delete( index ); } } } if( hash && filter?.size ) history.pushState(null, null, `#${hash}?${filter.toString()}`); else if( filter?.size ) history.pushState(null, null, `?${filter.toString()}`); else if( hash ) history.pushState(null, null, `#${hash}`); else history.pushState(null, null, ); toolbar.nodes.filterContainer.root.childNodes.forEach( element => { element.ARX.apply( 'active', false ); }); if( toolbar.nodes.filterReset ) toolbar.nodes.filterReset.apply( 'active', false ); if( toolbar.nodes.searchReset ) toolbar.nodes.searchReset.apply( 'active', false ); if( toolbar.nodes.searchInput ) toolbar.nodes.searchInput.root.value = ''; }, sort : function( column, order ) { // console.log( 'SORT', column, order ); const config = this.instance.config; const thead = this.instance.nodes.header; const tbody = this.instance.nodes.body; const tabelCell = thead.root.querySelector( `[data-arx-column="${column}"]` ); const tableRows = Array.from( tbody.root.querySelectorAll( 'tr' ) ); if( !tabelCell || !tableRows ) return; if( !order ) order = tabelCell.dataset.arxOrder === 'asc' ? 'desc' : 'asc'; const columns = config.columns || []; const rules = config.order.rules || {}; if( rules[ column ]?.replace ) { for( const [ oldCol, newCol ] of Object.entries( rules[ column ].replace ) ) { this.instance.root.querySelectorAll(`td[data-arx-column="${oldCol}"]`).forEach(td => { const rowData = td.closest('tr').rowData; td.innerHTML = rowData[ newCol ] || ''-''; // td.dataset.arxColumn = newCol; // Key anpassen }); } } if( rules[ column ]?.show ) { for( const showCol of rules[ column ].show ) { this.instance.root.querySelectorAll(`td[data-arx-column="${showCol}"], th[data-arx-column="${showCol}"]`).forEach(el => { el.style.display = ''; // Wieder anzeigen }); } } if( rules[ column ]?.hide ) { for( const hideCol of rules[ column ].hide ) { this.instance.root.querySelectorAll(`td[data-arx-column="${hideCol}"], th[data-arx-column="${hideCol}"]`).forEach(el => { el.style.display = 'none'; // Wieder anzeigen }); } } // Entferne vorherige Sortierklassen this.instance.root.querySelectorAll('.table__header__cell').forEach(cell => { cell.classList.remove('sorting--asc', 'sorting--desc', 'sorting--current'); }); // Setze neue Sortierklasse if( tabelCell ) { tabelCell.dataset.arxOrder = order; tabelCell.classList.add( `sorting--${order}`, 'sorting--current' ); } this.instance.root.dataset.arxSortingColumn = column; this.instance.root.dataset.arxSortingOrder = order; // Reihen sortieren tableRows.sort( (rowA, rowB) => { let a = rowA.ARX.data[ column ]?.trim() || ''; let b = rowB.ARX.data[ column ]?.trim() || ''; // Versuche als Zahl zu interpretieren, falls möglich if (!isNaN(a) && !isNaN(b)) { a = parseFloat(a); b = parseFloat(b); } return order === 'asc' ? String(a).localeCompare(String(b)) : String(b).localeCompare(String(a)); }); // Sortierte Reihen neu einfügen tbody.root.innerHTML = ''; tableRows.forEach( row => tbody.root.append( row ) ); }, loaded : function() { const config = this.instance.config; if( config.order && config.order.column ) { this.instance.apply( 'sort', config.order.column, config.order.order ); } }, }, builder : function( config, data ) { this.render( 'thead', { nodeName : 'header', columns : config.columns, }); const body = this.render( 'tbody', { nodeName : 'body', columns : config.columns, apply : [ 'generate', data, true ], }, data ); if( config.api ) { if( !config.api.resolved ) { config.api = ARUNOX.Router.parseRoute( config.api, data ); } ARUNOX.Router.fetch( config.api ).then( data => body.apply( 'generate', data, false ) ); } }, }); ARUNOX.Templates.register( 'thead', { hooks : { config : function( config, data ) { config.classes.push( 'table__header' ); return config; }, }, builder : function( config, data ) { const headerRow = this.render( 'tr', { classes : [ 'table__header__row' ], columns : config.columns, }); } }); ARUNOX.Templates.register( 'tbody', { hooks : { config : function( config, data ) { config.classes.push( 'table__body' ); return config; }, generate : async function( data, loading ) { const table = this.instance.closest( 'table' ); this.instance.root.innerHTML = ''; if( data && data.length ) { for( const rowData of data ) { this.instance.render( 'tr', { classes : [ 'table__body__row' ], columns : this.instance.config.columns, }, rowData ); } table.apply( 'loaded' ); return; } this.instance.render( 'tr', { classes : [ 'table__body__row' ], columns : [ { key : 'hint', attributes : { colSpan : this.instance.config.columns.length, }, innerText : loading ? 'Daten werden geladen ...' : 'Keine Datensätze gefunden', } ], }); }, }, }); ARUNOX.Templates.register( 'tr', { hooks : { config : function( config, data ) { config.arx.filter ??= {}; config.arx.search ??= []; config.classes.push( 'table_row' ); return config; }, afterRender : function( config, data ) { const table = this.instance.closest( 'table' ); this.instance.setUUID( data?.hashid ); if( table.config.action && data ) { this.instance.root.classList.add('cursor--pointer'); this.instance.root.addEventListener('click', async ( event ) => { if( event.target.tagName.toLowerCase() === 'a' ) return; await ARUNOX.Router.handle( table.config.action, {}, data, 'rest' ); }); } }, afterContent : function( config, data ) { this.instance.config.arx.search = this.instance.config.arx.search.join( ' ' ).toLowerCase(); }, update : function( data, status ) { const updatedRow = this.instance.replace( 'tr', { columns : this.instance.config.columns, classes : status ? 'table__body__row--updated' : 'table__body__row--failed', }, data ); setTimeout(() => updatedRow.root.classList.remove( 'table__body__row--updated', 'table__body__row--failed' ), 200 ); }, }, builder : function( config, data ) { const cellTag = this.parent.template == 'thead' ? 'th' : 'td'; for( const column of config.columns ) { this.render( cellTag, column, data ); } }, }); ARUNOX.Templates.register( 'th', { htmlTag : 'th', hooks : { config : function( config, data ) { config.type ??= 'text'; config.innerText ??= config?.label || null; config.arx.column ??= config?.key || null; config.classes.push( 'table__header__cell' ); config.classes.push( `table__header__cell--type-${config.type}` ); config.classes.push( 'table__cell' ); config.classes.push( `table__cell--type-${config.type}` ); if( config.sortable ) { config.classes.push( 'sortable' ); config.arx.column = config.key; config.arx.order = config.order?.order || 'asc'; config.events.click = () => this.instance.apply( 'sort', null ); } if( !config.visible ) { config.styles.display = 'none'; } return config; }, sort : function( order ) { const table = this.instance.closest( 'table' ); const column = this.instance.root.dataset?.arxColumn || null; table.apply( 'sort', column, order ); }, }, builder : function( config, data ) {} }); ARUNOX.Templates.register( 'td', { hooks : { config : function( config, data ) { config.type ??= 'text'; config.arx.column ??= config?.key || null; config.classes.push( 'table__cell' ); config.classes.push( `table__cell--type-${config.type}` ); if( !config.visible ) { config.styles.display = 'none'; } return config; }, data : function( data, config ) { if( data === undefined ) return data; let value = '-'; if( typeof config.output === 'object' ) { let keys = Array.isArray( config.output.keys ) ? config.output.keys : [ config.output.keys ]; let values = []; for( const key of keys ) { values.push( data[ key ] || null ); } if( config.output.format == 'string' ) { value = values.join( config.output.seperator || ' ' ); } else value = values.join(' '); data[ config.key ] = value; } if( data[ config.key ] !== undefined ) { value = data[ config.key ]; } else if( config.key.indexOf('.') > -1 ) { let current = data; for( const key of config.key.split('.') ) { current = current[ key ]; } if( current !== undefined ) { value = current; } } return value; }, beforeRender : function( config, data ) { this.instance.config.innerText ??= data || '-'; if( data !== null && data !== undefined ) { if( config?.filterable ) this.instance.parent.config.arx.filter[ config.key ] = data.toString().toLowerCase(); if( config?.searchable ) this.instance.parent.config.arx.search.push( data.trim() ); } }, afterRender : function( config, data ) { this.instance.apply( `type.${config.type}` ); }, }, }); ARUNOX.Hooks.register( 'td->type.icon', function() { const data = this.instance.data; const config = this.instance.config; const option = config.options[ data ] || {}; this.instance.root.textContent = null; const icon = this.instance.render( 'icon', { name : option?.icon || 'bi bi-question-diamond', color : { target : 'text', prefix : null, color : option?.color || 'default', }, attributes : { title : option?.label || `${config.title} nicht definiert`, }, }); }); ARUNOX.Hooks.register( 'td->type.status', function() { this.instance.apply( 'type.icon' ); const table = this.instance.closest( 'table' ); const config = this.instance.config; const data = this.instance.parent.data; const action = Object.assign( {}, { query : table.config.api?.query || {}, }, config.action ); this.instance.root.classList.add( 'js--click-ownbound' ); this.instance.root.addEventListener( 'click', async event => { event.preventDefault(); event.stopPropagation(); await ARUNOX.Router.handle( action, {}, data, 'rest' ); }); }); /** Datei: plugins/rest/assets/js/components/headline.js **/ /** * /components/headline.js * * Headline-Component mit Icon-Support * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File created */ ARUNOX.Templates.register( 'headline', { htmlTag : function( config, data, ctx ) { return `h${config.size || 2}`; }, builder : function( config, data ) { const className = 'headline'; const block = config.block ? `${className} ${config.block}__${className}` : className; this.root.classList.add( ...block.split(' ') ); // Title als Text-Node if( config.title ) { const textNode = document.createTextNode( config.title ); this.root.appendChild( textNode ); } } }); /** Datei: plugins/rest/assets/js/components/icon.js **/ /** * /arunox/components/icon.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ ARUNOX.Templates.register( 'icon', { htmlTag : 'i', builder : function( config, data ) { const className = 'icon'; const block = config.block ? `${className} ${config.block}__${className}` : className; this.root.classList.add( ...config.name.split(' ') ); this.root.classList.add( ...block.split(' ') ); this.parent.root?.classList.add( 'has--icon' ); }, }); /** Datei: plugins/rest/assets/js/components/form.js **/ /** * rest/arunox/templates/form.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ ARUNOX.Templates.register( 'form', { htmlTag : 'form', hooks : { config : function( config ) { config.attributes.id = crypto.randomUUID(); config.datasets.arx.node = this.instance.htmlTag; config.classes.push( 'form__container' ); return config; }, content : function( content, config, data ) { if( config.buttons ) { content.push( ARUNOX.Templates.config( 'buttons', { classes : [ 'form__buttons' ], content : config.buttons.map( button => { button.config.attributes ??= {}; button.config.attributes.form = this.instance.config.attributes.id; return button; }), }, data ) ); } return content; }, afterRender : function( config, data ) { if( !config.action ) return; let lastSearchParams = ARUNOX.Router.history?.match( /\?(.*)/) | {}; let formSearchParams = config.action.route.match( /\?(.*)/); if( lastSearchParams ) lastSearchParams = new URLSearchParams( lastSearchParams[1] ); if( formSearchParams ) formSearchParams = new URLSearchParams( formSearchParams[1] ); if( lastSearchParams ) { if( lastSearchParams.has('columns') && ( !formSearchParams || !formSearchParams.has('columns') ) ) { if( formSearchParams ) lastSearchParams = '&columns=' + lastSearchParams.get('columns'); else lastSearchParams = '?columns=' + lastSearchParams.get('columns'); config.action.route += lastSearchParams; } } this.instance.noValidate = true; this.instance.actionConfig = ARUNOX.Router.parseRoute( config.action, data, {}, 'rest' ); } }, }); ARUNOX.Templates.register( 'control', { htmlTag : 'div', hooks : { config : function( config ) { config.input = Object.assignRecursive( {}, config || {} ); config.template = `input/${config.type}`; config.datasets.arx.node = 'control'; config.datasets.arx.control = config.type; if( !ARUNOX.Templates.exists( config.template ) ) { if( config.type == 'select' ) config.template = 'select' else if( config.type == 'textarea' ) config.template = 'textarea' else if( config.type == 'datalist' ) config.template = 'datalist' else config.template = 'input'; } config.classes.push( 'form__group', `form__control--${config.type}` ); return config; }, }, builder : function( config, data ) { if( config.label ) { const label = this.render( 'label', { classes : [ 'form__label' ], content : config.label, attributes : { for : `control_${config.key}`, }, }); } const element = this.render( config.template, Object.assignRecursive( { classes : [ 'form__control' ], datasets : { arx : { node : 'input', }, }, }, config.input || {} ), data ); }, }); ARUNOX.Hooks.register( 'input->beforeRender', function( config, data ) { config.attributes = { name : config.key, type : config.type, required : !!config.required, }; }); ARUNOX.Hooks.register( 'select->beforeRender', function( config, data ) { config.content = []; config.attributes = { name : config.key, required : !!config.required, multiple : !!config.multiple, }; // console.log( config, data ); if( config.choose ) { config.content.push( ARUNOX.Templates.config( 'option', { content : config.choose, attributes : { selected : true, }, } ) ); } for( const option of config.options ) { config.content.push( ARUNOX.Templates.config( 'option', { content : option.label, attributes : { value : option.value, selected : data?.[ config.key ] == option.value, }, } ) ); } }); ARUNOX.Hooks.register( 'textarea->beforeRender', function( config, data ) { config.attributes = { name : config.key, required : !!config.required, }; }); if( !window.formSubmitListener ) { document.addEventListener('submit', async event => { if( !event.target.matches('form') ) return; event.preventDefault(); const form = event.target; const inputs = form.querySelectorAll('input, select, textarea'); const promises = []; console.log( inputs ); inputs.forEach(input => { if( typeof input.ARX.validate === 'function' ) { promises.push(new Promise((resolve) => { input.addEventListener('validationComplete', () => { resolve( !!input.closest('.is-valid') ); }, { once: true }); input.dispatchEvent( new Event('validate', { bubbles: true } ) ); })); } }); const results = await Promise.all( promises ); const isValid = results.every( result => result ); if( isValid ) { ARUNOX.Router.handle( form.ARX.actionConfig, new FormData( form ), {}, 'rest' ); } }); window.formSubmitListener = true; } /** Datei: plugins/rest/assets/js/components/notifications.js **/ /** * /arunox/components/notifications.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ ARUNOX.Templates.register( 'notifications', { htmlTag : 'div', hooks : { shouldRebuild : function( shouldRebuild, config, data ) { return document.querySelector('.notification__wrapper'); }, config : function( config, data ) { config.classes.push( 'notification__wrapper', 'notification--show-on-hover', 'notification--stacked' ); return config }, }, addNotification : function( config, data ) { this.render( 'notification', config, data ); } }); ARUNOX.Templates.register( 'notification', { htmlTag : 'div', hooks : { config : function( config, data ) { return { type : config.type, color : config.color || config.type, duration : config.duration, message : config.message || config.title, classes : [ 'notification' ], content : [ config.icon ? ARUNOX.Templates.config( 'icon', { name: config.icon, block: 'notification' }) : null, ARUNOX.Templates.config( 'div', { classes : [ 'notification__inner' ], content : [ config.title ? ARUNOX.Templates.config( 'div', { innerText : config.title, classes : 'notification__title', }) : null, config.message ? ARUNOX.Templates.config( 'div', { innerText : config.message, classes : 'notification__message', }) : null, ], }), ARUNOX.Templates.config( 'button', { classes : [ 'notification__close' ], innerHTML : '×', onClick : () => this.apply( 'close' ), }), ], }; }, close : function() { this.instance.root.classList.add( 'is-closing' ); setTimeout(() => this.instance.root.remove(), 300 ); }, afterRender : function( config, data ) { if( config.duration === 0 ) return; setTimeout(() => this.instance.apply( 'close' ), config.duration || 5000 ); } }, }); /** Datei: plugins/rest/assets/js/hooks/colorize.js **/ /** * rest/arunox/hooks/colorize.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Adaptation to new ctx method * @since 1.0.0 File is created */ ARUNOX.Hooks.register( 'colorize', function( cellColorConfig, optionColorConfig, iconContainer ) { if( typeof cellColorConfig === 'string' ) cellColorConfig = { color: cellColorConfig } if( typeof optionColorConfig === 'string' ) optionColorConfig = { color: optionColorConfig } let colorConfig = Object.assign( { target : 'background', prefix : false, }, optionColorConfig, cellColorConfig ); // console.log( colorConfig ); if( colorConfig.prefix ) { this.instance.root.classList.removeRexEx( /.*--colored-.*/ ); this.instance.root.classList.add( `${colorConfig.prefix}--colored-${colorConfig.color || 'default'}` ); return ; } if( this.instance.root && ['button'].includes( colorConfig.target ) ) { this.instance.root.classList.removeRexEx( /button--colored-.*/ ); this.instance.root.classList.add( `button--colored-${colorConfig.color || 'default'}` ); this.instance.root.style.backgroundColor = colorConfig.value; } if( this.instance.root && ['background'].includes( colorConfig.target ) ) { this.instance.root.classList.removeRexEx( /bg--colored-.*/ ); this.instance.root.classList.add( `bg--colored-${colorConfig.color || 'default'}` ); this.instance.root.style.backgroundColor = colorConfig.value; } if( iconContainer && ['icon','both'].includes( colorConfig.target ) ) { iconContainer.classList.removeRexEx( /text--colored-.*/ ); iconContainer.classList.add( `text--colored-${colorConfig.color || 'default'}` ); iconContainer.style.color = colorConfig.value; } if( this.instance.root && ['text','both'].includes( colorConfig.target ) ) { this.instance.root.classList.removeRexEx( /text--colored-.*/ ); this.instance.root.classList.add( `text--colored-${colorConfig.color || 'default'}` ); this.instance.root.style.color = colorConfig.value; } }); /** Datei: plugins/rest/assets/js/hooks/_general.js **/ /** * /arunox/hooks/_general.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ import '/plugins/rest/assets/js/hooks/colorize.js'; import '/plugins/rest/assets/js/components/icon.js'; ARUNOX.Hooks.register( 'applyConfigAfter', function( element, config ) { if( config.color ) this.instance.apply( 'colorize', typeof config.color === 'object' ? config.color : { color: config.color }, { prefix: 'theme' } ); if( config.icon ) this.instance.render( 'icon', { name: config.icon } ); }); ARUNOX.Hooks.register( 'config', function( config, data ) { config.arx ??= {}; config.events ??= []; config.styles ??= {}; config.classes ??= []; config.datasets ??= {}; config.attributes ??= {}; config.datasets.arx ??= config.arx; if( typeof config.color === 'string' ) config.color = { color: config.color }; if( typeof config.classes === 'string' ) config.classes = config.classes.split(' '); return config; }, 2 ); ARUNOX.Hooks.register( 'config', function( config, data ) { if( config.nodeName ) { config = this.instance.filter( `config.node->${config.nodeName}`, config, data ); } return config; }); /** Datei: plugins/rest/assets/js/core/templates.js **/ /** * /arunox/core/templates.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Creation of new ctx methods */ window.ARUNOX ??= {}; ARUNOX.Templates = { _type : 'templates', _componentData : { content : undefined, config : undefined, data : undefined, htmlTag : 'div', template : null, container : null, // DOM-Object root : null, // DOM-Object parent : null, // ARX-Object children : [], // ARX-Object nodes : () => Object.create(null), hooks : () => Object.create(null), keepOnReplace : () => ({ classes : [], // CSS-Klassen die erhalten bleiben sollen attributes : [], // Attribute die erhalten bleiben sollen datasets : [], // Data-Attribute die erhalten bleiben sollen structure : null, // DOM-Struktur die erhalten werden soll }), takeOnReplace : () => ({ classes : [], // CSS-Klassen die übernommen werden sollen attributes : [], // Attribute die übernommen werden sollen datasets : [], // Data-Attribute die übernommen werden sollen }), mounted : false, destroyed : false, }, _componentProto : { apply : function( hook, ...args ) { ARUNOX.Hooks.apply.call( this, hook, ...args ); if( this.extends ) ARUNOX.Hooks.apply.call( this, `${this.extends}->${hook}`, ...args ); ARUNOX.Hooks.apply.call( this, `${this.template}->${hook}`, ...args ); // if( this.nodeName ) console.log( 'apply', `${this.template}->${hook}`, this.nodeName ); }, filter : function( hook, value, ...args ) { value = ARUNOX.Hooks.filter.call( this, hook, value, ...args ); if( this.extends ) value = ARUNOX.Hooks.filter.call( this, `${this.extends}->${hook}`, value, ...args ); // if( this.nodeName ) console.log( 'filter', `${this.template}->${hook}`, this.nodeName ); return ARUNOX.Hooks.filter.call( this, `${this.template}->${hook}`, value, ...args ); }, render : function( templateName, config, data, container ) { return ARUNOX.Templates.render( templateName, config, data, container || this.root, this ); }, replace : function( template, config, data ) { const newCtx = typeof template === 'object' ? template : ARUNOX.Templates.render( template, config, data, null, this.parent ); const newRoot = newCtx && newCtx.root instanceof HTMLElement ? newCtx.root : null; if( !newRoot ) return this.root; const preserveData = this._collectPreservables( this, newCtx ); if( this.mounted && this.root instanceof HTMLElement ) { this.root.replaceWith( newRoot ); } this._applyPreserved( newRoot, preserveData ); this.root = newRoot; this.nodes = newCtx.nodes || Object.create(null); newCtx.mounted = true; return this; }, node : function( nodeName, nodeConfig = {}, placement = 'append', refElement = null ) { if( !this.nodes ) this.nodes = {}; if( !this.nodes[ nodeName ] ) { nodeConfig = typeof nodeConfig === 'object' ? nodeConfig : { textNode: nodeConfig }; nodeConfig = Object.assign( { htmlTag : 'div', datasets : { arx : { node : nodeName, }, }, }, nodeConfig ); nodeConfig = this.filter(`node`, nodeConfig ); nodeConfig = this.filter(`node${nodeName.toUpperCaseFirst()}`, nodeConfig ); refElement = refElement instanceof HTMLElement ? refElement : refElement?.root || null; const nodeCtx = ARUNOX.Templates._createContext( nodeName, nodeConfig, this, this.root, { htmlTag: nodeConfig.htmlTag }); const ctxRender = nodeCtx.render; const ctxReplace = nodeCtx.replace; nodeCtx.render = function( template, config, data, container ) { if( !this.root ) { const tmp = ARUNOX.Templates.render( this.config.htmlTag, this.config, {}, this.parent?.root, this.parent ); this.root = tmp.root; this.nodes = tmp.nodes; this.nodeName = nodeName; this.root.ARX = this; if( placement === 'prepend' ) this.parent.root.prepend( this.root ); else if( placement === 'append' ) this.parent.root.append( this.root ); else if( placement === 'before' && refElement ) refElement.before( this.root ); else if( placement === 'after' && refElement ) refElement.after( this.root ); this.mounted = true; } return ctxRender.call( this, template, config, data, container || this.root ) || this; }; nodeCtx.replace = function( template, config, data ) { if( !this.root ) this.render(); return ctxReplace.call( this, template, config, data ); } nodeCtx.nodeName = nodeName; // console.log(nodeCtx); this.nodes[ nodeName ] = nodeCtx; } return this.nodes[ nodeName ]; }, closest : function( template ) { if( this.template == template || this.extends == template ) { return this; } return this.parent.closest( template ); }, dataset : function( dataset, namespace = 'arx' ) { dataset = typeof dataset === 'string' ? { [dataset] : true } : dataset; for( const [ key, value ] of Object.entries( dataset ) ) { this.root.setAttribute( `data-${namespace}-${key}`, Array.isArray( value ) ? value.join(',') : value ); } }, setUUID : function( hashid, context = '') { this.dataset({ 'uuid': btoa( [ this.template, hashid, context ].filter( Boolean ).join(':') ).replace( /[^a-zA-Z0-9]/g, '' ) }); }, _collectPreservables : function( oldCtx, newCtx ) { const preserveData = { classes : new Set(), attributes : new Map(), datasets : new Map(), structure : null }; if( oldCtx.keepOnReplace ) this._collectPreserveData( oldCtx.root, oldCtx.keepOnReplace, preserveData ); if( newCtx.takeOnReplace ) this._collectPreserveData( oldCtx.root, newCtx.takeOnReplace, preserveData ); return preserveData; }, _collectPreserveData : function( element, rules, preserveData ) { if( !element ) return; // CSS Classes if( rules.classes ) { rules.classes.forEach( pattern => { const matchingClasses = this._matchClasses( element, pattern ); matchingClasses.forEach( cls => preserveData.classes.add( cls ) ); }); } // Attributes if( rules.attributes ) { rules.attributes.forEach( pattern => { const matchingAttrs = this._matchAttributes( element, pattern ); matchingAttrs.forEach( ([name, value]) => { preserveData.attributes.set( name, value ); }); }); } // Datasets if( rules.datasets ) { rules.datasets.forEach( pattern => { const matchingData = this._matchDatasets( element, pattern ); matchingData.forEach( ([name, value]) => { preserveData.datasets.set( name, value ); }); }); } // Structure (komplexerer Fall - erstmal placeholder) if( rules.structure ) { preserveData.structure = rules.structure; } }, _applyPreserved : function( newRoot, preserveData ) { preserveData.classes.forEach( cls => { newRoot.classList.add( cls ); }); preserveData.attributes.forEach( (value, name) => { newRoot.setAttribute( name, value ); }); preserveData.datasets.forEach( (value, name) => { newRoot.setAttribute( `data-${name}`, value ); }); // Structure handling würde hier kommen (komplexer) if( preserveData.structure ) { // TODO: Implementierung für Structure-Preservation console.log( 'Structure preservation not yet implemented:', preserveData.structure ); } }, _matchClasses : function( element, pattern ) { const classes = Array.from( element.classList ); if( pattern.includes('*') ) { // Wildcard matching: "lcars-*" matches "lcars-panel", "lcars-frame", etc. const regex = new RegExp( '^' + pattern.replace(/\*/g, '.*') + '$' ); return classes.filter( cls => regex.test( cls ) ); } else { // Exact match return classes.includes( pattern ) ? [pattern] : []; } }, _matchAttributes : function( element, pattern ) { const attributes = Array.from( element.attributes ); const matching = []; attributes.forEach( attr => { if( pattern.includes('*') ) { const regex = new RegExp( '^' + pattern.replace(/\*/g, '.*') + '$' ); if( regex.test( attr.name ) ) { matching.push( [attr.name, attr.value] ); } } else if( attr.name === pattern ) { matching.push( [attr.name, attr.value] ); } }); return matching; }, _matchDatasets : function( element, pattern ) { const matching = []; // Durchsuche alle data-* Attribute Array.from( element.attributes ).forEach( attr => { if( attr.name.startsWith('data-') ) { const dataName = attr.name.substring(5); // Remove 'data-' prefix if( pattern.includes('*') ) { const regex = new RegExp( '^' + pattern.replace(/\*/g, '.*') + '$' ); if( regex.test( dataName ) ) { matching.push( [dataName, attr.value] ); } } else if( dataName === pattern ) { matching.push( [dataName, attr.value] ); } } }); return matching; }, }, register : function( template, callback, priority ) { return ARUNOX.Registry.register( this._type, template, callback, priority ); }, once : function( template, callback, priority = 10 ) { return ARUNOX.Registry.once( this._type, template, callback, priority ); }, remove : function( template, callback ) { return ARUNOX.Registry.remove( this._type , template, callback ); }, resolve : function( template ) { const mergeHooks = (baseHooks={}, childHooks={}) => { const out = {}; for( const [ key, fn ] of Object.entries( baseHooks ) ) { out[ key ] = Array.isArray( fn ) ? fn : [ fn ]; } for( const [ key, fn ] of Object.entries( childHooks ) ) { const hooks = Array.isArray( fn ) ? fn : [ fn ]; out[ key ] = out[ key ] ? [ ...out[ key ], ...hooks ] : hooks; } return out; }; const extendsSupport = function( childName ) { const base = ARUNOX.Templates.resolve( this.extends ); const merged = { ...base, ...this }; merged.hooks = mergeHooks( base.hooks, this.hooks ); // merged.extends = base; // delete merged.extends; return merged; }; let def = ARUNOX.Registry.resolve( this._type, template, extendsSupport ); if( !def ) { const source = ARUNOX.Loader.findSource(template); if( source ) { if( ARUNOX.Loader.loadSync( source.path ) ) { // Hooks laden (nur registrierte) if( source.source.hooks.has( template ) ) { ARUNOX.Loader.loadSync( source.source.hooks.get( template ) ); } // Plugin-Hooks for( const [ name, plugin ] of ARUNOX.Loader._sources.plugins ) { if( plugin.hooks.has( template ) ) { ARUNOX.Loader.loadSync( plugin.hooks.get( template ) ); } } // Theme-Hooks if( ARUNOX.Loader._sources.theme.hooks.has( template ) ) { ARUNOX.Loader.loadSync( ARUNOX.Loader._sources.theme.hooks.get( template ) ); } def = ARUNOX.Registry.resolve( this._type, template, extendsSupport ); } } } if( !def ) { if( template.match(/\//) ) return false; if( !( document.createElement( template ) instanceof HTMLUnknownElement ) ) { return { htmlTag: template }; } return false } if( typeof def === 'function' ) return { builder: def }; if( typeof def === 'object') { if( !def.htmlTag ) { if( !( document.createElement( template ) instanceof HTMLUnknownElement ) ) { return { ...def, htmlTag: template }; } throw new Error(`[ARUNOX.Templates] Invalid definition for: ${template}`); } if( def.hooks ) { // Normalisiere alle hooks zu Arrays for (const [key, fn] of Object.entries(def.hooks)) { if (!Array.isArray(fn)) { def.hooks[key] = [fn]; } } } return def; } throw new Error(`[Templates] Invalid definition for: ${template}`); }, exists : function( template ) { return ARUNOX.Registry.exists( this._type, template ); }, all : function( template ) { return [ ...ARUNOX.Registry.all( this._type, template ) ].reverse(); }, config : function( template, config, data ) { return { template, config, data }; }, render : function( template, config, data, container, parentCtx ) { if( !template ) return; const definition = this.resolve( template ); if( !definition ) return; config = Object.assign( {}, config ); const ctx = this._createContext( template, config, parentCtx || null, container, definition ); this._registerHooks( template, ctx ); config = ctx.config = ctx.filter( 'config', config, data ); data = ctx.data = ctx.filter( 'data', data, config ); const doRebuild = ctx.filter( 'shouldRebuild', null, config, data ); const doRender = ctx.filter( 'shouldRender', true, config, data ); if( doRebuild ) { const rebuildCtx = doRebuild?.ARX ? doRebuild.ARX : doRebuild; if( rebuildCtx ) { rebuildCtx.apply( 'beforeRebuild', config, data ); this._applyConfig( rebuildCtx, config ); // Config kann sich geändert haben this._renderContent( rebuildCtx, config, data ); // Neuer Content queueMicrotask( ()=> { this._collectNodes( rebuildCtx ); rebuildCtx.apply( 'afterRebuild', config, data ); }); } return rebuildCtx; } if( doRender ) { ctx.apply( 'beforeRender', config, data ); this._buildRoot( definition, ctx, config, data ); this._collectNodes( ctx ); this._renderContent( ctx, config, data ); this._applyConfig( ctx, config ); ctx.root = ctx.filter( 'wrapper', ctx.root ); this._mount( ctx, container ); queueMicrotask( ()=>{ this._collectNodes( ctx ); ctx.apply( 'afterRender', config, data ); }); return ctx; } return ctx; }, _registerHooks : function( template, ctx ) { for( const [ hookKey, hookDef ] of Object.entries( ctx.hooks ) ) { const hookArray = Array.isArray( hookDef ) ? hookDef : [ hookDef ]; hookArray.forEach( def => { // Parse Hook-Definition const hooks = this._parseHookDefinition( hookKey, def, template ); // Registriere alle geparsten Hooks hooks.forEach( hookConfig => { const originalCallback = hookConfig.callback; const wrappedCallback = function( ...args ) { this.owner = ctx; return originalCallback.call( this, ...args ); }; wrappedCallback._originalCallback = originalCallback; if( !ARUNOX.Hooks.exists( `${hookConfig.template}->${hookConfig.hook}`, originalCallback ) ) { if( hookConfig.once ) { ARUNOX.Hooks.once( `${hookConfig.template}->${hookConfig.hook}`, wrappedCallback, hookConfig.priority ); } else { ARUNOX.Hooks.register( `${hookConfig.template}->${hookConfig.hook}`, wrappedCallback, hookConfig.priority ); } } }); }); } }, _parseHookDefinition : function( hookKey, hookDef, currentTemplate ) { // Function → Standard Hook für eigenes Template if( typeof hookDef === 'function' ) { return [{ template : currentTemplate, hook : hookKey, callback : hookDef, priority : 9, once : false }]; } // Object → Vollständige Definition let templates, hookName; // Templates ermitteln if( hookDef.templates ) { templates = hookDef.templates; } else if( hookDef.template ) { templates = [ hookDef.template ]; } else if( hookKey.includes('->') ) { templates = [ hookKey.split('->')[0] ]; } else { templates = [ currentTemplate ]; } // Hook-Name ermitteln if( hookDef.hook ) { hookName = hookDef.hook; } else if( hookKey.includes('->') ) { hookName = hookKey.split('->')[1]; } else { hookName = hookKey; } // Für jedes Template eine Hook-Config erstellen return templates.map( templateName => ({ template : templateName, hook : hookName, callback : hookDef.callback, priority : hookDef.priority || 9, once : hookDef.once || false })); }, _createContext : function( template, config, parent = null, container = null, definition ) { const prototype = Object.create( this._componentProto ); const context = Object.assign( prototype, definition ); for( const key in this._componentData ) { if( typeof context[ key ] !== 'undefined' ) continue; const val = this._componentData[ key ]; context[ key ] = typeof val === 'function' ? val() : val; } if( definition.keepOnReplace || config.keepOnReplace ) { context.keepOnReplace = this._mergeReplaceRules( definition.keepOnReplace || {}, config.keepOnReplace || {} ); } if( definition.takeOnReplace || config.takeOnReplace ) { context.takeOnReplace = this._mergeReplaceRules( definition.takeOnReplace || {}, config.takeOnReplace || {} ); } context.extends = definition.extends; context.template = template; context.parent = parent; context.container = container || null; context.config = config || {}; // console.log( definition ); // console.log( template, context ); return context; }, _mergeReplaceRules : function( templateRules, configRules ) { return { classes: [ ...(templateRules.classes || []), ...(configRules.classes || []) ], attributes: [ ...(templateRules.attributes || []), ...(configRules.attributes || []) ], datasets: [ ...(templateRules.datasets || []), ...(configRules.datasets || []) ], structure: configRules.structure || templateRules.structure || null }; }, _createElement : function( tagResolver, config, data, ctx ) { // Tag ermitteln (String oder Function(config, data, ctx) -> String) const tag = ( typeof tagResolver === 'function' ) ? tagResolver( config, data, ctx ) : ( tagResolver || 'div' ); return document.createElement( String(tag || 'div') ); }, _buildRoot : function( definition, ctx, config, data ) { ctx.apply( 'buildRootBefore' ); // 1) Root vorab erstellen (Definition darf optional `tag` liefern: string | fn(cfg,data,ctx) -> string) if( !ctx.root ) { ctx.root = this._createElement( definition.htmlTag, config, data, ctx ); } // 2) Definition ausführen – nur wenn render existiert (permissive) if( typeof definition.builder === 'function' ) { const out = definition.builder.call( ctx, config, data ); if( out instanceof HTMLElement ) { ctx.root = out; } else if( typeof out === 'string' ) { const tpl = document.createElement('template'); tpl.innerHTML = out.trim(); ctx.root = tpl.content.firstElementChild || ctx.root; } } // 3) Inputs persistieren ctx.config = config; ctx.data = data; ctx.root.ARX = ctx; ctx.apply( 'buildRootAfter' ); return ctx.root; }, _collectNodes : function( ctx ) { ctx.apply( 'collectNodesBefore' ); if( !ctx.root ) return; // console.log( ctx.root.querySelectorAll('[data-arx-node]') ); ctx.root.querySelectorAll('[data-arx-node]').forEach( element => { const nodeName = element.getAttribute('data-arx-node'); if( nodeName && element.ARX && !ctx.nodes[ nodeName ] ) { element.ARX.nodeName = nodeName; ctx.nodes[ nodeName ] = element.ARX; } }); ctx.apply( 'collectNodesAfter' ); }, _applyConfig : function( ctx, config ) { if( !ctx.root || !config ) return; ctx.apply( 'applyConfigBefore', ctx.root, config ); if( config.classes ) { if( typeof config.classes === 'string' ) ctx.root.classList.add( ...config.classes.split(/\s+/).filter( Boolean ) ); else if( Array.isArray( config.classes ) ) ctx.root.classList.add( ...config.classes ); else if( typeof config.classes === 'object' ) { const { add = [], remove = [] } = config.classes; if( add.length ) ctx.root.classList.add( ...add ); if( remove.length ) ctx.root.classList.remove( ...remove ); } } if( typeof config === 'object' ) { for( const key of Object.keys( config ) ) { const match = key.match( /^on([a-z].*)/i ); if( !match ) continue; config.events ??= {}; config.events[ match[1].toLowerCase() ] = config[ key ]; } } if( typeof config.events === 'object' ) { for( const [ name, callback ] of Object.entries( config.events ) ) ctx.root.addEventListener( name, callback ); } if( typeof config.styles === 'object' ) { for (const [k, v] of Object.entries( config.styles )) ctx.root.style[k] = v; } if( typeof config.nodeName === 'string' ) { config.datasets.arx.node = config.nodeName; if( ctx.parent ) { ctx.parent.nodes[ config.nodeName ] = ctx; } } if( typeof config.datasets === 'object' ) { const applyDataset = ( obj, prefix = '' ) => { for( const [ k, v ] of Object.entries(obj)) { const key = prefix ? `${prefix}-${k}` : k; if( v && typeof v === 'object' && !Array.isArray( v ) ) { applyDataset( v, key ); // rekursiv } else { ctx.root.setAttribute( `data-${key}`, String( v ) ); } } }; applyDataset( config.datasets ); } if( typeof config.attributes === 'object' ) { for (const [k, v] of Object.entries( config.attributes )) { if (v === false || v == null) ctx.root.removeAttribute(k); else ctx.root.setAttribute(k, String(v)); } } if( typeof config.apply === 'object' ) ctx.apply( ...config.apply ); if( typeof config.innerHTML === 'string' ) ctx.root.innerHTML = config.innerHTML; if( typeof config.innerText === 'string' ) ctx.root.innerText = config.innerText; ctx.apply( 'applyConfigAfter', ctx.root, config ); }, _renderContent : function( ctx, config, data ) { const content = ctx.filter( 'content', config?.content || [], config, data ); ctx.apply( 'beforeContent', config, data ); if( typeof content === 'string' ) { ctx.root.innerText = content; } else if( Array.isArray( content ) ) { content.filter( Boolean ).forEach( item => { if( item.history ) { if( location.pathname != item.history ) { window.history.pushState({}, "", item.history ); } } ctx.render( item.template || item.generator, item.config, item.data ); }); } else if( typeof content === 'object' ) { if( content.history ) { if( location.pathname != content.history ) { window.history.pushState({}, "", content.history ); } } ctx.render( content.template || content.generator, content.config, content.data ); } ctx.apply( 'afterContent', config, data ); }, _mount : function( ctx, container ) { if( !ctx || !ctx.root ) return ctx; ctx.apply( 'mount' ); // Ziel ermitteln: explizites container-Arg → sonst ctx.container let target = ( container !== undefined ? container : ctx.container ); if( !target ) return ctx; // Selector-String erlauben if( typeof target === 'string' ) target = document.querySelector( target ); // ARX als Container erlauben else if( target && typeof target === 'object' && target.root instanceof HTMLElement ) target = target.root; if( !( target instanceof HTMLElement ) ) return ctx; target.appendChild( ctx.root ); ctx.container = target; ctx.mounted = true; ctx.apply( 'mounted' ); return ctx; }, }; /** Datei: plugins/rest/assets/js/core/utilities.js **/ /** * /arunox/core/utilities.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Creation of new ctx methods */ window.ARUNOX ??= {}; ARUNOX.Utils = { device: { get isRotatable() { return window.orientation > -1; }, get isMobile() { return this.isRotatable || screen.availHeight <= 800 || screen.availWidth <= 800; }, get screenSize() { return { width: screen.availWidth, height: screen.availHeight }; } }, // Weitere Utils können hier hinzugefügt werden dom: { ready(callback) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', callback); } else { callback(); } } } }; /** Datei: plugins/rest/assets/js/core/prototypes.js **/ /** * /arunox/core/prototypes.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Creation of new ctx methods */ Object.defineProperty( DOMTokenList.prototype, 'removeRexEx', { value: function( regex ) { if( !( regex instanceof RegExp ) ) { throw new TypeError('DOMTokenList.removeRegex erwartet ein RegExp-Objekt.'); } const toRemove = [ ...this ].filter( cls => regex.test( cls ) ); if( toRemove.length ) this.remove( ...toRemove ); }, enumerable : false, writable : true, configurable: true }); Object.defineProperty( DOMTokenList.prototype, 'match', { value: function( regex, returnMatchIndex = 1 ) { if( !( regex instanceof RegExp ) ) { throw new TypeError('DOMTokenList.match erwartet ein RegExp-Objekt.'); } return [ ...this ] .map( cls => regex.exec( cls ) ) .filter( Boolean ) .map( m => m[ returnMatchIndex ] ); }, enumerable : false, writable : true, configurable: true }); Object.defineProperty( Object.prototype, 'assignRecursive', { value: function( target, ...sources ) { for( const source of sources ) { for( const [ key, item ] of Object.entries( source ) ) { if( item === null ) { delete target[ key ]; continue; } if( Array.isArray( item ) ) { target[ key ] = [].concat( item, target[ key ] || [] ); continue; } if( typeof item === 'object' && item !== null ) { if( typeof target[ key ] !== 'object' || target[ key ] === null ) { target[ key ] = target[ key ] === undefined ? {} : { [key] : target[ key ] }; } target[ key ] = Object.assignRecursive( target[ key ] || {}, item ); continue; } target[ key ] = item; } } return target; }, enumerable : false, writable : true, configurable: true }); Object.defineProperty( String.prototype, 'toUpperCaseFirst', { value: function() { return String( this ).charAt(0).toUpperCase() + String( this ).slice(1); }, enumerable : false, writable : true, configurable: true }); /** Datei: plugins/rest/assets/js/core/loader.js **/ /** * /arunox/core/loader.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Creation of new ctx methods */ window.ARUNOX ??= {}; ARUNOX.Loader = { _loaded : new Set(), _loading : new Map(), _sources : { core : { basePath: '/plugins/rest/assets/js', templates: new Map(), hooks: new Map() }, theme : { basePath: '/themes/lcars/assets/js', templates: new Map(), hooks: new Map() }, plugins : new Map() }, load : async function( path, options = {} ) { const { force = false, cache = true, type = 'module' } = options; if( !force && this._loaded.has( path ) ) return Promise.resolve(); // Already loaded? if( this._loading.has( path ) ) return this._loading.get( path ); // Currently loading? const loadPromise = this._loadModule( path, type, cache ); this._loading.set( path, loadPromise ); try { await loadPromise; this._loaded.add( path ); } catch( error ) { console.error( `Failed to load module: ${path}`, error ); throw error; } finally { this._loading.delete( path ); } return loadPromise; }, loadAll : async function( modules ) { const promises = modules.map( module => { if( typeof module === 'string' ) { return this.load( module ); } return this.load( module.path, module.options || {} ); }); return Promise.all( promises ); }, loadIf : async function( condition, path, options = {} ) { if( typeof condition === 'function' ) { condition = condition(); } if( condition ) return this.load( path, options ); return Promise.resolve(); }, loadSync : function( path ) { if( this._loaded.has( path ) || this._loaded.has(path + ':404' ) ) return this._loaded.has( path ); try { const xhr = new XMLHttpRequest(); xhr.open('GET', path, false); // SYNCHRON! xhr.send(); if( xhr.status === 200 ) { // Extrahiere und entferne import Statements const importRegex = /import\s+['"](.*?)['"]\s*;?/g; const imports = []; let code = xhr.responseText; let match; while ((match = importRegex.exec(code)) !== null) { imports.push(match[1]); } // Entferne imports aus dem Code code = code.replace(importRegex, ''); // Lade alle imports zuerst (rekursiv) imports.forEach(importPath => { this.loadSync(importPath); }); const script = document.createElement('script'); // script.type = 'module'; script.textContent = code; document.head.appendChild( script ); document.head.removeChild( script ); this._loaded.add( path ); return true; } if( xhr.status === 404 ) { // 404 merken, damit wir es nicht nochmal versuchen this._loaded.add( path + ':404' ); return false; } return false; } catch( e ) { return false; } }, isLoaded : function( path ) { return this._loaded.has( path ); }, unload : function( path ) { this._loaded.delete( path ); }, registerSource : function( type, name, config ) { if( type === 'core' ) { this._sources.core.basePath = config.basePath || this._sources.core.basePath; this._sources.core.templates = this._parseTemplates( config.templates, this._sources.core.basePath ); this._sources.core.hooks = this._parseHooks( config.hooks, this._sources.core.basePath ); } else if( type === 'theme' ) { this._sources.theme.basePath = config.basePath || this._sources.theme.basePath; this._sources.theme.templates = this._parseTemplates( config.templates, this._sources.theme.basePath ); this._sources.theme.hooks = this._parseHooks( config.hooks, this._sources.theme.basePath ); } else if( type === 'plugin' ) { this._sources.plugins.set( name, { basePath : config.basePath, templates : this._parseTemplates( config.templates, config.basePath ), hooks : this._parseHooks( config.hooks, config.basePath ) }); } }, findSource : function( template ) { // Theme (höchste Priorität) if( this._sources.theme.templates.has( template ) ) { return { type : 'theme', path : this._sources.theme.templates.get( template ), source : this._sources.theme }; } // Plugins for( const [ name, plugin ] of this._sources.plugins ) { if( plugin.templates.has( template ) ) { return { type : 'plugin', name, path : plugin.templates.get( template ), source : plugin }; } } // Core (Fallback) if( this._sources.core.templates.has( template ) ) { return { type : 'core', path : this._sources.core.templates.get( template), source : this._sources.core }; } return null; }, _loadModule : function( path, type, cache ) { return new Promise(( resolve, reject ) => { const script = document.createElement('script'); script.onload = () => { document.head.removeChild( script ); resolve(); }; script.onerror = () => { document.head.removeChild( script ); reject( new Error( `Failed to load script: ${path}` ) ); }; if( type === 'module' ) { script.type = 'module'; } if( !cache ) { const url = new URL( path, window.location.origin ); url.searchParams.set( '_t', Date.now() ); script.src = url.toString(); } else { script.src = path; } document.head.appendChild( script ); }); }, _parseTemplates : function( templates, basePath ) { if( Array.isArray( templates ) ) { // Einfache Liste → nutzt basePath const map = new Map(); templates.forEach( t => map.set( t, `${basePath}/components/${t}.js` ) ); return map; } else if( typeof templates === 'object' ) { // Explizite Pfade return new Map( Object.entries( templates ) ); } return new Map(); }, _parseHooks : function( hooks, basePath ) { if( Array.isArray( hooks ) ) { const map = new Map(); hooks.forEach( h => map.set( h, `${basePath}/hooks/${h}.js` ) ); return map; } else if( typeof hooks === 'object' ) { return new Map( Object.entries( hooks ) ); } return new Map(); }, }; /** Datei: plugins/rest/assets/js/core/hooks.js **/ /** * /arunox/core/hooks.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Creation of new ctx methods */ window.ARUNOX = window.ARUNOX || {}; ARUNOX.Hooks = { _type : 'hooks', register : function( name, callback, priority ) { return ARUNOX.Registry.register( this._type, name, callback, priority ); }, once : function( name, callback, priority = 10 ) { return ARUNOX.Registry.once( this._type, name, callback, priority ); }, remove : function( name, callback ) { return ARUNOX.Registry.remove( this._type , name, callback ); }, exists : function( name, callback ) { return ARUNOX.Registry.exists( this._type, name, callback ); }, resolve : function( name ) { return ARUNOX.Registry.resolve( this._type, name ); }, all : function( name ) { return [ ...ARUNOX.Registry.all( this._type, name ) ].reverse(); }, filter : function( name, value, ...args ) { let hookNames = Array.isArray( name ) ? name : name.split(' '); let callbacks = hookNames.map( n => ARUNOX.Hooks.all( n ) ).flat(2).filter(Boolean); let stopped = false; let context = { stop : function() { stopped = true; }, instance : this }; // console.log( hookNames ); if( this.hooks?.hooks ) { callbacks = callbacks.concat( hookNames.map( n => this.hooks.hooks[ n ] ).filter(Boolean).flat() ); } // if( callbacks.length ) console.log( callbacks, context.instance ); for( const fn of callbacks ) { if( stopped ) break; if( fn.ARX?.once && fn.ARX?.done ) continue; value = fn.call( context, value, ...args ); if( fn.ARX?.once ) fn.ARX.done = true; } return value; }, apply : function( name, ...args ) { let hookNames = Array.isArray( name ) ? name : name.split(' '); let callbacks = hookNames.map( n => ARUNOX.Hooks.all( n ) ).flat(2).filter(Boolean); let stopped = false; let context = { stop : function() { stopped = true; }, instance : this }; // console.log( hookNames ); if( this.hooks?.hooks ) { callbacks = callbacks.concat( hookNames.map( n => this.hooks.hooks[ n ] ).filter(Boolean).flat() ); } for( const fn of callbacks ) { if( stopped ) break; if( fn.ARX?.once && fn.ARX?.done ) continue; fn.call( context, ...args ); if( fn.ARX?.once ) fn.ARX.done = true; } }, Instance : class { constructor( owner, hooks ) { this.owner = owner; this.hooks = hooks || {}; } _hooks( name ) { return [...new Set([ name, this.owner.constructor?.extends ? `${this.owner.constructor.extends.type}->${name}` : null, `${this.owner.type}->${name}`, this.owner.constructor?.extends ? `${this.owner.constructor.extends.hook}->${name}` : null, `${this.owner.hook}->${name}`, ].filter(Boolean))]; } register( name, callback ) { this.hooks[ name ] ??= []; this.hooks[ name ].push( callback ); return this; } exists( name ) { return ARUNOX.Hooks.exists( name ); } filter( name, value, ...args ) { return ARUNOX.Hooks.filter.call( this.owner, this._hooks( name ), value, ...args ); } apply( name, ...args ) { ARUNOX.Hooks.apply.call( this.owner, this._hooks( name ), ...args ); } } }; /** Datei: plugins/rest/assets/js/core/router.js **/ /** * /arunox/core/router.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Creation of new ctx methods */ window.ARUNOX = ARUNOX || {}; ARUNOX.Router = new ( class { constructor() { this._routes = {}; this._cache = new Map(); this._lastCacheKey = null; this._isReloaded = false; this._CACHE_TIMEOUT = 500; } async handle( config, sendData = {}, parseData = {}, mode = 'data' ) { const debug = false; if( !config ) { console.warn("⚠️ Ungültige Konfiguration für handleAction()."); return; } if( !config.resolved ) { config = this.parseRoute( config, parseData, true, mode ); } if( debug ) console.groupCollapsed( config.resolved ); // console.log( config ); let response = { click : [], route : [], dialog : [], updater : [], wrapper : [], template : [], generator : [], javascript : [], }; for( const item of await this.fetch( config, sendData, {}, mode ) ) { response[ item.type ].push( item ); } // console.log( config, response ); ARUNOX.Router.history = config.history; if( response.dialog ) { for( const item of response.dialog ) { let dialog = null; if( item.name === '#ALL' ) { document.querySelectorAll('dialog').forEach( dialog => dialog.closeDialog() ); continue; } if( dialog = document.querySelector(`dialog[data-id="${item.name}"]`) ) { dialog.closeDialog(); continue; } if( dialog = document.querySelector('dialog') ) { dialog.closeDialog(); } } } if( response.route.length ) { for( const item of response.route ) { await ARUNOX.Router.handle( item, {}, {}, 'rest' ); } ARUNOX.Router.history = config.history; } if( response.template.length && !response.route.length ) { document.body.innerHTML = ''; ARUNOX.Router.template = ARUNOX.Templates.render( response.template[0].template, response.template[0], Object.assign( {}, config ), document.body ); } // if( response.template.length && !response.route.length ) { // document.body.innerHTML = ''; // template = ARUNOX.Theme.template.render( response.template[0].template, Object.assign( {}, config ), response.template[0], document.body ); // } if( response.wrapper.length && !response.route.length ) { for( const item of response.wrapper ) { ARUNOX.Router.template.nodes.content.render( item.generator, item.config, item.list || item.data || {} ); if( item.history ) { if( location.pathname != item.history ) { window.history.pushState({}, "", item.history ); } } } } // if( response.generator.length && !response.route.length ) { // for( const item of response.generator ) { // ARUNOX.Theme.generator.render( item.generator, config, item, template ); // } // } // if( response.updater.length && !response.route.length ) { for( const item of response.updater ) { document.querySelectorAll(`[data-arx-uuid="${item.updater}"]`).forEach( element => element.ARX.apply( 'update', item.data, item.status ) ); } } // // if( response.click.length ) { // for( const item of response.click ) { // const element = document.querySelector(`[data-uuid="${item.parent}"] ${item.selector ?? ''}` ); // // element.click(); // } // } if( response.javascript.length ) { for( const item of response.javascript ) { eval( item.script ); } } if( debug ) console.groupEnd(); ARUNOX.Router.history = config.history; } async routes() { if( !this._isLoaded() ) { await this._loadList(); } return this._routes; } async fetch(config, params = {}, options = {}, mode = 'data') { try { let cached = this.getCache(!config, {}, mode); if (cached !== false) return cached; cached = this.getCache(config, params, mode); if (cached !== false) return cached; if (!config.resolved) config = this.parseRoute(config, params, true, mode); if (!config.resolved) throw new Error(`❌ Fehlerhafte API-URL: ${config.resloved.data}`); const cacheKey = `${config.method}:${config.cachekey}`; const defaultOptions = { method: config.method, headers: {}, ...options }; if (config.method === "GET" || config.method === "DELETE") { const queryString = new URLSearchParams(params).toString(); config.resolved += queryString ? `?${queryString}` : ""; } else { if (params instanceof FormData) { defaultOptions.body = params; } else { defaultOptions.body = JSON.stringify(params); defaultOptions.headers["Content-Type"] = "application/json"; } } const response = await fetch(config.resolved, defaultOptions); const data = await response.json(); const fullcache = data.caching || false; delete data.caching; this._cache.set(cacheKey, { response: data, fullcache: false, timestamp: Date.now() }); this._lastCacheKey = cacheKey; return data; } catch (error) { console.error('❌ Fehler beim Abrufen der API-Daten:', error); return null; } } async fetchData(config, params = {}, options = {}) { const response = await this.fetch(config, params, options); return response.data; } async fetchList(config, params = {}, options = {}) { const response = await this.fetch(config, params, options); return response.list; } async fetchConfig(config, params = {}, options = {}) { const response = await this.fetch(config, params, options); return response.config; } async fetchGenerator(config, params = {}, options = {}) { const response = await this.fetch(config, params, options); return response.generator; } async fetchStream(config, params = {}, options = {}, onChunk) { try { if (!config.resolved) config = this.parseRoute(config, params, true); if (!config.resolved) throw new Error(`❌ Fehlerhafte API-URL: ${config.resloved.data}`); const defaultOptions = { method: config.method, headers: {}, ...options }; const response = await fetch(config.resolved, defaultOptions); if (!response.body) return; const decoder = new TextDecoder(); const reader = response.body.getReader(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; if (onChunk) { buffer += decoder.decode(value, { stream: true }); let lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { if (!line.trim()) continue; try { onChunk(JSON.parse(line)); } catch { onChunk(line); } } } } if (onChunk && buffer.trim()) { try { onChunk(JSON.parse(buffer)); } catch { onChunk(buffer); } } } catch (error) { console.error('❌ Fehler beim Abrufen des API-Stream:', error); return null; } } async find(path, includeHidden = false) { await this._loadList(); return this._routes.find(route => { if (!includeHidden && route.visibility === "hidden") return false; const routePattern = new RegExp(`^${route.path.replace(/{\w+}/g, "([^/]+)")}$`); return routePattern.test(path); }); } getCache(config, params = {}, mode = 'data') { if (config === true && this._lastCacheKey) { const cached = this._cache.get(this._lastCacheKey); if (cached && Date.now() - cached.timestamp < this._CACHE_TIMEOUT) { cached.timestamp = Date.now(); return cached.response; } return null; } if (typeof config === 'object' || typeof config === 'string') { if (!config.resolved) config = this.parseRoute(config, params, {}, mode); const cacheKey = `${config.method}:${config.cachekey}`; const cached = this._cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this._CACHE_TIMEOUT) { this._lastCacheKey = cacheKey; return cached.response; } if (cached && cached.fullcache && location.pathname == config.cachekey) { return cached.response; } } return false; } getBreadcrumbSegments(path) { let segments = path.split('/').filter(Boolean); let breadcrumbs = []; let currentPath = ""; if (!segments.length) { segments = ['']; } segments.forEach(segment => { currentPath += `/${segment}`; const route = this._routes.find(r => r.path && new RegExp(`^${r.path.replace(/{\w+}/g, "([^/]+)")}$`).test(currentPath)); const group = this._routes.find(r => r.name && (new RegExp(`^${r.name.replace(/{\w+}/g, "([^/]+)")}$`).test(currentPath) || r.name == segment)); if (route) { breadcrumbs.push({ label: route.breadcrumb || route.label || segment, path: currentPath, }); } else if (group) { breadcrumbs.push({ label: group.breadcrumb || group.label || segment, path: false, }); } }); return breadcrumbs; } getDataRoute(config, params = {}, queryIncludes = true) { config = this.parseRoute(config ?? location.pathname, params, queryIncludes, 'data'); return config.resolved; } parseRoute( config, params = {}, queryIncludes = true, mode = 'data' ) { let route = typeof config === "string" ? config : config.route; let query = new URLSearchParams( config?.query || [] ); let connector = /\?/.test( route ) ? '&' : '?'; let resolved = null; let cachekey = null; if( query.size ) route += connector + query; if( !queryIncludes ) route = route.replace(/\?.*/, ''); switch (mode) { case 'rest': resolved = this._resolveApiUrl(route, params, '/rest'); cachekey = this._resolveApiUrl(route, params, '/rest', false); break; case 'data': resolved = this._resolveApiUrl(route, params, true); cachekey = this._resolveApiUrl(route, params, false, false); break; } return { route : route, resolved: resolved, cachekey: cachekey, history : this._resolveApiUrl(route, params, false), method : config?.method || "GET", query : config?.query || {}, }; } parseUrl(route, params) { const missingKeys = []; const resolvedUrl = route.replace(/{(\w+)}/g, (_, key) => { if (params[key] === 'undefined') { missingKeys.push(key); return `{${key}}`; } return params[key]; }).replace(/\/$/, ''); if (missingKeys.length > 0) { console.error(`Nicht ersetzte Platzhalter: ${missingKeys.join(', ')}`, `URL: ${route}`); return null; } return resolvedUrl; } _isLoaded() { return !!Object.entries( this._routes ).length; } async _loadList() { this._routes = await this.fetchList("/rest/routes"); } _resolveApiUrl(route, params = {}, prefix = true, hash = true) { const API_PREFIX = '/api/v1'; if (typeof prefix === 'string') { route = this._stripApiPrefix(route, API_PREFIX); route = route.startsWith(prefix) ? route : `${prefix}${route}`; } const mergedParams = params; const prefixedUrl = route.startsWith(API_PREFIX) ? route : `${API_PREFIX}${route}`; const resolvedUrl = this.parseUrl(prefixedUrl, params); if (!hash) { const withoutHash = resolvedUrl.replace(/#.*/, ''); return prefix ? withoutHash : this._stripApiPrefix(withoutHash, API_PREFIX); } return prefix ? resolvedUrl : this._stripApiPrefix(resolvedUrl, API_PREFIX); } _stripApiPrefix(route, api_prefix = '/api/v1') { if (route.startsWith(api_prefix)) { return route.replace(api_prefix, ''); } return route; } })(); ARUNOX.Router.history = null; /** Datei: plugins/rest/assets/js/core/registry.js **/ /** * /arunox/core/regestry.js * * Some Javascript codes without further description * * @author Sparky * @copyright 2025 ARUNOX * @version 2.0.0 * @since 2.0.0 Creation of new ctx methods */ window.ARUNOX = window.ARUNOX || {}; ARUNOX.Registry = { _store : {}, register : function( type, name, callback, priority = 10, once = false ) { if( !this._store[ type ] ) this._store[ type ] = {}; if( !this._store[ type ][ name ]) this._store[ type ][ name ] = []; // callback.name = name; callback.once = once; callback.priority = priority; this._store[ type ][ name ].unshift( callback ); this._store[ type ][ name ].sort( ( a, b ) => b.priority - a.priority ); }, once : function( type, name, callback, priority = 10 ) { return this.register( type, name, callback, priority, true ); }, remove : function( type, name, callback ) { const arr = this._store[ type ]?.[ name ]; if (!arr) return; this._store[type][name] = arr.filter(e => e !== callback); }, resolve : function( type, name, extendsSupportCallback ) { const resolved = this._store[ type ]?.[ name ]?.[0]; if( !resolved ) return null; if( resolved?.extends && typeof resolved?.extends === 'string' ) { return extendsSupportCallback.call( resolved, name ); } return resolved; }, exists : function( type, name, callback, originCallback ) { if( this._store[ type ]?.[ name ] ) { return !!( this._store[ type ]?.[ name ]?.filter( item => item == callback || item._originalCallback == callback ).length ); } return false; }, all : function( type, name ) { if( !type ) return this._store; if( !name ) return Object.keys(this._store[ type ] || {} ); return this._store[ type ]?.[ name ] || []; }, template : { _type : 'template', register : function( name, callback, priority ) { return ARUNOX.Registry.register( this._type, name, callback, priority); }, remove : function( name, callback ) { return ARUNOX.Registry.remove( this._type, name, callback ); }, resolve : function( name ) { return ARUNOX.Registry.resolve( this._type, name ); }, exists : function( name ) { return ARUNOX.Registry.exists( this._type, name ); }, all : function( name ) { return ARUNOX.Registry.all( this._type, name ); }, }, wrapper : { _type : 'wrapper', register : function( name, callback, priority ) { return ARUNOX.Registry.register( this._type, name, callback, priority); }, remove : function( name, callback ) { return ARUNOX.Registry.remove( this._type, name, callback ); }, resolve : function( name ) { return ARUNOX.Registry.resolve( this._type, name ); }, exists : function( name ) { return ARUNOX.Registry.exists( this._type, name ); }, all : function( name ) { return ARUNOX.Registry.all( this._type, name ); }, }, generator : { _type : 'generator', register : function( name, callback, priority ) { return ARUNOX.Registry.register( this._type, name, callback, priority); }, remove : function( name, callback ) { return ARUNOX.Registry.remove( this._type, name, callback ); }, resolve : function( name ) { return ARUNOX.Registry.resolve( this._type, name ); }, exists : function( name ) { return ARUNOX.Registry.exists( this._type, name ); }, all : function( name ) { return ARUNOX.Registry.all( this._type, name ); }, }, htmlTag : { _type : 'htmltag', register : function( name, callback, priority ) { return ARUNOX.Registry.register( this._type, name, callback, priority); }, remove : function( name, callback ) { return ARUNOX.Registry.remove( this._type, name, callback ); }, resolve : function( name ) { return ARUNOX.Registry.resolve( this._type, name ); }, exists : function( name ) { return ARUNOX.Registry.exists( this._type, name ); }, all : function( name ) { return ARUNOX.Registry.all( this._type, name ); }, }, _mergeHooks : function( baseHooks = {}, childHooks = {} ) { const merged = {...baseHooks}; for (const [name, hookFn] of Object.entries(childHooks)) { if (name.startsWith('!')) { // explizites Überschreiben: "!config" const realName = name.slice(1); merged[realName] = hookFn; } else { // beide behalten → Liste draus machen if (merged[name]) { merged[name] = [].concat(merged[name], hookFn); } else { merged[name] = hookFn; } } } return merged; } };