@@ -43,12 +43,12 @@ function detail(label,value){return `<div><small>${t(label)}</small><strong>${es
function liveWriteAllowed ( ) { return state . settings ? . mock || state . settings ? . write _enabled ; }
function liveWriteAllowed ( ) { return state . settings ? . mock || state . settings ? . write _enabled ; }
function guardDeviceButtons ( ) { if ( ! liveWriteAllowed ( ) ) document . querySelectorAll ( '[data-show],' + ( state . tab === 'cloud' ? '.image-card .danger,' : '' ) + '#show-name,#display-template,#display-upload,#restore' ) . forEach ( b => { b . disabled = true ; b . title = t ( 'readOnlyWarning' ) ; } ) ; }
function guardDeviceButtons ( ) { if ( ! liveWriteAllowed ( ) ) document . querySelectorAll ( '[data-show],' + ( state . tab === 'cloud' ? '.image-card .danger,' : '' ) + '#show-name,#display-template,#display-upload,#restore' ) . forEach ( b => { b . disabled = true ; b . title = t ( 'readOnlyWarning' ) ; } ) ; }
async function templateEditor ( selected ) {
async function templateEditor ( selected ) {
const raw = $ ( '#template-data' ) ; const assets = await api ( '/api/v1/assets' ) ; let values = JSON . parse ( raw . value ) ;
const raw = $ ( '#template-data' ) ; const assets = await api ( '/api/v1/assets?source=uploaded&limit=200 ' ) ; let values = JSON . parse ( raw . value ) ;
const panel = document . createElement ( 'div' ) ; panel . id = 'template-fields' ; raw . before ( panel ) ;
const panel = document . createElement ( 'div' ) ; panel . id = 'template-fields' ; raw . before ( panel ) ;
const labels = { title : state . lang === 'zh' ? '标题' : 'Title' , message : state . lang === 'zh' ? '正文' : 'Message' , quote : state . lang === 'zh' ? '一句话' : 'Quote' , author : state . lang === 'zh' ? '作者' : 'Author' , events : t ( 'eventsHelp' ) , date : t ( 'date' ) , weekday : state . lang === 'zh' ? '星期(留空自动)' : 'Weekday (blank for automatic)' , weather : state . lang === 'zh' ? '天气数据(JSON,可留空使用 Provider) ' : 'Weather JSON (blank uses provider)' } ;
const labels = { title : state . lang === 'zh' ? '标题' : 'Title' , message : state . lang === 'zh' ? '正文' : 'Message' , quote : state . lang === 'zh' ? '一句话' : 'Quote' , author : state . lang === 'zh' ? '作者' : 'Author' , events : t ( 'eventsHelp' ) , date : t ( 'date' ) , weekday : state . lang === 'zh' ? '星期(留空自动)' : 'Weekday (blank for automatic)' , weather : state . lang === 'zh' ? '天气数据(JSON,可留空使用 Provider) ' : 'Weather JSON (blank uses provider)' } ;
const fields = [ ... new Set ( [ 'title' , ... selected . fields . filter ( k => Object . hasOwn ( selected . defaults , k ) ) , ... ( [ 'daily-dashboard' , 'calendar' ] . includes ( selected . id ) ? [ 'events' , 'date' , 'weekday' ] : selected . id === 'daily-poetry' ? [ 'date' ] : [ ] ) , ... ( selected . id === 'weather' || selected . id === 'daily-dashboard' ? [ 'weather' ] : [ ] ) , ... ( [ 'photo-text' , 'daily-dashboard' ] . includes ( selected . id ) ? [ 'photo_id' ] : [ ] ) ] ) ] ;
const fields = [ ... new Set ( [ 'title' , ... selected . fields . filter ( k => Object . hasOwn ( selected . defaults , k ) ) , ... ( [ 'daily-dashboard' , 'calendar' ] . includes ( selected . id ) ? [ 'events' , 'date' , 'weekday' ] : selected . id === 'daily-poetry' ? [ 'date' ] : [ ] ) , ... ( selected . id === 'weather' || selected . id === 'daily-dashboard' ? [ 'weather' ] : [ ] ) , ... ( [ 'photo-text' , 'daily-dashboard' ] . includes ( selected . id ) ? [ 'photo_id' ] : [ ] ) ] ) ] ;
function invalidate ( ) { state . templatePreview = null ; $ ( '#display-template' ) . disabled = true ; $ ( '#template-preview' ) . className = 'empty' ; $ ( '#template-preview' ) . textContent = t ( 'previewHint' ) ; }
function invalidate ( ) { state . templatePreview = null ; $ ( '#display-template' ) . disabled = true ; $ ( '#template-preview' ) . className = 'empty' ; $ ( '#template-preview' ) . textContent = t ( 'previewHint' ) ; }
function build ( ) { panel . innerHTML = ` <h3> ${ t ( 'formEditor' ) } </h3> ` + fields . map ( k => k === 'photo_id' ? ` <div class="field"><label for="field-photo_id"> ${ t ( 'photo' ) } </label><select id="field-photo_id" data-field="photo_id"><option value=""> ${ t ( 'noPhoto' ) } </option> ${ assets . map ( a => ` <option value=" ${ esc ( a . asset _id ) } " ${ a . asset _id === values . photo _id ? 'selected' : '' } > ${ esc ( a . name ) } </option> ` ) . join ( '' ) } </select ></div>` : ` <div class="field"><label for="field- ${ k } "> ${ esc ( labels [ k ] || t ( k ) ) } </label><textarea id="field- ${ k } " data-field=" ${ k } " rows=" ${ [ 'events' , 'message' , 'weather' , 'quote' ] . includes ( k ) ? 3 : 1 } "> ${ esc ( k === 'events' ? ( values [ k ] || [ ] ) . map ( v => typeof v === 'object' ? v . title : v ) . join ( '\n' ) : [ 'weather' , 'forecast' ] . includes ( k ) ? ( values [ k ] ? JSON . stringify ( values [ k ] , null , 2 ) : '' ) : values [ k ] || '' ) } </textarea></div> ` ) . join ( '' ) ; panel . querySelectorAll ( '[data-field]' ) . forEach ( el => el . oninput = ( ) => { try { const k = el . dataset . field ; let v = el . value ; if ( k === 'events' ) v = v . split ( '\n' ) . filter ( x => x . trim ( ) ) ; if ( [ 'weather' , 'forecast' ] . includes ( k ) ) v = v . trim ( ) ? JSON . parse ( v ) : null ; if ( v === null || v === '' ) delete values [ k ] ; else values [ k ] = v ; raw . value = JSON . stringify ( values , null , 2 ) ; el . setCustomValidity ( '' ) ; invalidate ( ) ; } catch { el . setCustomValidity ( t ( 'INVALID_REQUEST' ) ) ; el . reportValidity ( ) ; } } ) ; }
function build ( ) { panel . innerHTML = ` <h3> ${ t ( 'formEditor' ) } </h3> ` + fields . map ( k => k === 'photo_id' ? ` <div class="field"><label for="field-photo_id"> ${ t ( 'photo' ) } </label><select id="field-photo_id" data-field="photo_id"><option value=""> ${ t ( 'noPhoto' ) } </option> ${ values . photo _id && ! assets . some ( a => a . asset _id === values . photo _id ) ? ` <option value=" ${ esc ( values . photo _id ) } " selected disabled> ${ state . lang === 'zh' ? '已有选择不在上传照片列表中,请重新选择' : 'Previous selection is outside uploaded photos; choose again' } </option> ` : '' } ${ assets . map ( a => ` <option value=" ${ esc ( a . asset _id ) } " ${ a . asset _id === values . photo _id ? 'selected' : '' } > ${ esc ( a . name ) } </option> ` ) . join ( '' ) } </select><p class="hint"> ${ state . lang === 'zh' ? '只列出上传的图片,不包含模板生成图。没有照片时,请使用下方文件选择器上传。' : 'Uploaded images only; generated templates are excluded. Upload a photo below if needed.' } </p ></div>` : ` <div class="field"><label for="field- ${ k } "> ${ esc ( labels [ k ] || t ( k ) ) } </label><textarea id="field- ${ k } " data-field=" ${ k } " rows=" ${ [ 'events' , 'message' , 'weather' , 'quote' ] . includes ( k ) ? 3 : 1 } "> ${ esc ( k === 'events' ? ( values [ k ] || [ ] ) . map ( v => typeof v === 'object' ? v . title : v ) . join ( '\n' ) : [ 'weather' , 'forecast' ] . includes ( k ) ? ( values [ k ] ? JSON . stringify ( values [ k ] , null , 2 ) : '' ) : values [ k ] || '' ) } </textarea></div> ` ) . join ( '' ) ; panel . querySelectorAll ( '[data-field]' ) . forEach ( el => el . oninput = ( ) => { try { const k = el . dataset . field ; let v = el . value ; if ( k === 'events' ) v = v . split ( '\n' ) . filter ( x => x . trim ( ) ) ; if ( [ 'weather' , 'forecast' ] . includes ( k ) ) v = v . trim ( ) ? JSON . parse ( v ) : null ; if ( v === null || v === '' ) delete values [ k ] ; else values [ k ] = v ; raw . value = JSON . stringify ( values , null , 2 ) ; el . setCustomValidity ( '' ) ; invalidate ( ) ; } catch { el . setCustomValidity ( t ( 'INVALID_REQUEST' ) ) ; el . reportValidity ( ) ; } } ) ; }
build ( ) ; const advanced = document . createElement ( 'details' ) ; advanced . innerHTML = ` <summary> ${ t ( 'advanced' ) } </summary> ` ; raw . before ( advanced ) ; advanced . append ( raw ) ; raw . oninput = ( ) => { invalidate ( ) ; try { values = JSON . parse ( raw . value ) ; if ( ! values || typeof values !== 'object' || Array . isArray ( values ) ) throw Error ( ) ; raw . setCustomValidity ( '' ) ; build ( ) ; } catch { raw . setCustomValidity ( t ( 'INVALID_REQUEST' ) ) ; } } ;
build ( ) ; const advanced = document . createElement ( 'details' ) ; advanced . innerHTML = ` <summary> ${ t ( 'advanced' ) } </summary> ` ; raw . before ( advanced ) ; advanced . append ( raw ) ; raw . oninput = ( ) => { invalidate ( ) ; try { values = JSON . parse ( raw . value ) ; if ( ! values || typeof values !== 'object' || Array . isArray ( values ) ) throw Error ( ) ; raw . setCustomValidity ( '' ) ; build ( ) ; } catch { raw . setCustomValidity ( t ( 'INVALID_REQUEST' ) ) ; } } ;
const reset = document . createElement ( 'button' ) ; reset . id = 'reset-template' ; reset . textContent = t ( 'resetTemplate' ) ; $ ( '#save-template' ) . after ( reset ) ; bind ( 'reset-template' , ( ) => confirmDialog ( t ( 'resetConfirm' ) , async ( ) => { await api ( '/api/v1/templates/' + selected . id + '/config' , { method : 'DELETE' } ) ; await navigate ( ) ; } ) ) ;
const reset = document . createElement ( 'button' ) ; reset . id = 'reset-template' ; reset . textContent = t ( 'resetTemplate' ) ; $ ( '#save-template' ) . after ( reset ) ; bind ( 'reset-template' , ( ) => confirmDialog ( t ( 'resetConfirm' ) , async ( ) => { await api ( '/api/v1/templates/' + selected . id + '/config' , { method : 'DELETE' } ) ; await navigate ( ) ; } ) ) ;
const save = $ ( '#save-template' ) ; const oldSave = save . onclick ; save . onclick = async e => { if ( [ ... panel . querySelectorAll ( 'textarea' ) , raw ] . some ( x => ! x . checkValidity ( ) ) ) { toast ( t ( 'INVALID_REQUEST' ) ) ; return ; } await oldSave ( e ) ; } ;
const save = $ ( '#save-template' ) ; const oldSave = save . onclick ; save . onclick = async e => { if ( [ ... panel . querySelectorAll ( 'textarea' ) , raw ] . some ( x => ! x . checkValidity ( ) ) ) { toast ( t ( 'INVALID_REQUEST' ) ) ; return ; } await oldSave ( e ) ; } ;