class TemplatesManagerView extends EICDomContent { constructor(privileges) { super('/templates', privileges) Object.assign(this, app.helpers.basicDialogs) Object.assign(this, app.helpers.activeAttributes) Object.assign(this, app.helpers.validators) this.selectedTemplate = null this.decodedHtml = null this.decodedAltText = null } DOMContentLoaded(options) { for (let model in options.models) this[model] = options.models[model] this.components = ui.eicfy(this.el) this.gridTemplates = new DataGrid(this.find('.library [eicdatagrid]'), { headers: [ { label: 'Name', filter: 'text', sortable: true }, { label: 'Author', type: 'markup', filter: 'text', sortable: true }, { label: 'Delete', type: 'button' }, { label: 'Status', type: 'markup' }, { label: 'Actions', type: 'markup' }, ], actions: [] }) this.gridTemplates.onRowClick = (row, event) => { const target = event.target const isButton = target.closest('.template-actions button') if (!isButton) { this.onTemplateSelect(row) } } this.preview = new Card(this.find('.preview')) this.previewContent = this.find('.preview .html').attachShadow({ mode: 'open' }) this.setupTriggers(this.components) this.setupRefs(this.components) this.setupStatusButtons() this.components.searchCriteria.value = [app.User.identity.uuid] } async refreshTpl() { this.gridTemplates.loading = true try { const result = await this.templates.getReadableFolders() this.authorizedPaths = result.authorizedPaths || [] if (this.authorizedPaths.length === 0) { this.output('searchResults', `
You're not authorized to access the templates dashboard
You're not authorized to view templates
An error occurred while loading templates. Please try again later
You are about to permanently delete the template "${tplName}".
Are you sure?
`, okLabel: 'Delete', severity: 'danger', okPromise: () => this.templates.delete(tplId) }) if (result) { ui.growl.append("Template deleted !", 'success') this.refreshTpl() } else { ui.growl.append("Error deleting Template!", 'error') } } onFolderSelect(path) { this.templates.ffs.changeDir(path) this.refreshTpl() } async onTemplateClone(tId, button) { if (!button) return button.classList.add('spin') button.disabled = true try { const originalTpl = await this.templates.get(tId) if (!originalTpl) { ui.growl.append('Original template not found', 'danger') return } const responseCreate = await this.templates.save({ status: 'created' }) if (responseCreate.success) { const newId = responseCreate.payload.id const responseClone = await this.createAndSaveNewTemplate(newId, originalTpl) if (responseClone.success) { app.Router.route(`/templates/${newId}`) } else { ui.growl.append('Error cloning template', 'error') } } else { ui.growl.append('Error creating template', 'error') } } catch (error) { console.error('Error cloning template:', error) ui.growl.append('Error cloning template', 'error') } finally { button.classList.remove('spin') button.disabled = false } } async onTemplateEdit(tId) { const button = this.find(`[data-id="${tId}"][data-trigger="onTemplateEdit"]`) if (!button) return button.classList.add('spin') button.disabled = true try { app.Router.route(`/templates/${tId}`, { mode: 'edit', ffsData: this.templates.ffs }) } catch (error) { console.error('Error opening template:', error) ui.growl.append('Error opening template', 'error') } finally { button.classList.remove('spin') button.disabled = false } } /*Manage template record for new template @status = created */ async onTemplateCreate() { const button = this.find('.tpl-createButton'); if (!button) return button.disabled = true; button.classList.add('loading') button.innerHTML = ' Creating...' const responseCreate = await this.templates.save({ status: 'created' }) if (responseCreate.success) { this.newId = responseCreate.payload.id const templateStatus = 'created' const tplDate = new Date().toISOString() const path = this.authorizedPaths?.[0] const tplName = 'New template' const html = btoa('new template
') const altText = '' const subject = 'New template' const tplData = { id: this.newId, type: 'templateInfo', name: tplName, path: path, html: html, status: templateStatus, meta: { subject: subject, type: '', altText: altText, }, statusHistory: { changedBy: { euLoginId: app.User.identity.uuid, mail: app.User.identity.mail, role: app.User.identity.role, }, dateTime: tplDate, value: templateStatus, }, startDate: tplDate, endDate: tplDate, } const response = await this.templates.save(tplData) if (response.success) { app.Router.route(`/templates/${this.newId}`) button.disabled = false button.classList.remove('loading') button.innerHTML = 'Create New Template' } else { ui.growl.append('Error creating template', 'error') } } } /*Manage template record for clone @tplData */ async createAndSaveNewTemplate(newId, tplData = {}) { const templateStatus = 'draft' const tplDate = new Date().toISOString() const path = tplData.path || this.authorizedPaths?.[0] const tplName = tplData.name || ' template' const html = tplData.html || btoa('CLONE template
') const altText = tplData.meta?.altText || '' const subject = tplData.meta?.subject || '' const data = { id: newId, type: 'templateInfo', name: 'CLONE-' + tplName, path: path, html: html, status: templateStatus, meta: { subject: 'CLONE-' + subject, type: '', altText: altText, }, statusHistory: { changedBy: { euLoginId: '', mail: '', role: '', }, dateTime: '', value: templateStatus, }, startDate: tplDate, endDate: tplDate, } const response = await this.templates.save(data) if (response.success) { return { success: true, id: response.payload.recId } } else { ui.growl.append('Error creating template', 'error') } } getEarliestDateTime(statusHistory) { if (!Array.isArray(statusHistory)) return null return statusHistory.reduce((earliest, entry) => { const entryDate = new Date(entry.dateTime) return !earliest || entryDate < earliest ? entryDate : earliest }, null) } onPreviewClose() { this.find('.preview').setAttribute('disabled', '') } onTemplatesDisplay(event) { let button = event._el switch (button.dataset.value) { case 'thumbs': this.find('.library').classList.add('thumbs') break default: this.find('.library').classList.remove('thumbs') } } /** * replaceMustaches : replaces {{mustache}} with a span with class tpl-token * @param {string} html : the html to process * @returns {string} : the processed html */ replaceMustaches(html) { return html.replace(/\{\{(.*?)\}\}/g, function (match, p1) { return `${p1.trim()}` }) } decodeBase64Utf8(str) { return decodeURIComponent( Array.from(atob(str)).map(c => '%' + c.charCodeAt(0).toString(16).padStart(2, '0') ).join('') ) } } app.registerClass('TemplatesManagerView', TemplatesManagerView)