kempo 4.2.16 → 4.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +21 -4
- package/dist/admin/components/AddGroupControl.js +2 -2
- package/dist/admin/components/AddGroupControl.js.map +1 -1
- package/dist/admin/components/ClearExpiredSessionsControl.js +2 -2
- package/dist/admin/components/ClearExpiredSessionsControl.js.map +1 -1
- package/dist/admin/components/FragmentEditor.js +15 -15
- package/dist/admin/components/FragmentEditor.js.map +1 -1
- package/dist/admin/components/GlobalContentEditor.js +12 -12
- package/dist/admin/components/GlobalContentEditor.js.map +1 -1
- package/dist/admin/components/GroupDeleteSelected.js +2 -2
- package/dist/admin/components/GroupDeleteSelected.js.map +1 -1
- package/dist/admin/components/GroupRemoveRecord.js +2 -2
- package/dist/admin/components/GroupRemoveRecord.js.map +1 -1
- package/dist/admin/components/Monaco.js +1 -1
- package/dist/admin/components/Monaco.js.map +1 -1
- package/dist/admin/components/PageEditor.js +42 -28
- package/dist/admin/components/PageEditor.js.map +1 -1
- package/dist/admin/components/TableRowDelete.js +2 -2
- package/dist/admin/components/TableRowDelete.js.map +1 -1
- package/dist/admin/components/TableRowLink.js +2 -2
- package/dist/admin/components/TableRowLink.js.map +1 -1
- package/dist/admin/components/TemplateEditor.js +18 -18
- package/dist/admin/components/TemplateEditor.js.map +1 -1
- package/dist/admin/components/ValueEditor.js +17 -17
- package/dist/admin/components/ValueEditor.js.map +1 -1
- package/dist/admin/components/tableControls/AddMember.js +2 -2
- package/dist/admin/components/tableControls/AddMember.js.map +1 -1
- package/dist/admin/components/tableControls/AddPermission.js +2 -2
- package/dist/admin/components/tableControls/AddPermission.js.map +1 -1
- package/dist/admin/components/tableControls/InstallExtension.js +2 -2
- package/dist/admin/components/tableControls/InstallExtension.js.map +1 -1
- package/dist/admin/components/tableControls/UninstallExtension.js +2 -2
- package/dist/admin/components/tableControls/UninstallExtension.js.map +1 -1
- package/dist/admin/extensions/index.page.html +49 -15
- package/dist/admin/icons/open_in_new.svg +1 -0
- package/dist/admin/nav.fragment.html +1 -1
- package/dist/kempo/api/admin-globals/DELETE.js +1 -1
- package/dist/kempo/api/admin-globals/DELETE.js.map +1 -1
- package/dist/kempo/api/admin-globals/GET.js +1 -1
- package/dist/kempo/api/admin-globals/GET.js.map +1 -1
- package/dist/kempo/api/admin-globals/POST.js +1 -1
- package/dist/kempo/api/admin-globals/POST.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/GET.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/GET.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/PUT.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/PUT.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/disable/PUT.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/disable/PUT.js.map +1 -1
- package/dist/kempo/api/admin-globals/[id]/enable/PUT.js +1 -1
- package/dist/kempo/api/admin-globals/[id]/enable/PUT.js.map +1 -1
- package/dist/kempo/api/auth/change-password/POST.js +1 -1
- package/dist/kempo/api/auth/change-password/POST.js.map +1 -1
- package/dist/kempo/api/auth/login/email/POST.js +1 -1
- package/dist/kempo/api/auth/login/email/POST.js.map +1 -1
- package/dist/kempo/api/auth/logout/POST.js +1 -1
- package/dist/kempo/api/auth/logout/POST.js.map +1 -1
- package/dist/kempo/api/auth/register/email/POST.js +1 -1
- package/dist/kempo/api/auth/register/email/POST.js.map +1 -1
- package/dist/kempo/api/auth/reset-password/POST.js +1 -1
- package/dist/kempo/api/auth/reset-password/POST.js.map +1 -1
- package/dist/kempo/api/auth/session/GET.js +1 -1
- package/dist/kempo/api/auth/session/GET.js.map +1 -1
- package/dist/kempo/api/auth/verify-email/POST.js +1 -1
- package/dist/kempo/api/auth/verify-email/POST.js.map +1 -1
- package/dist/kempo/api/extensions/DELETE.js +1 -1
- package/dist/kempo/api/extensions/DELETE.js.map +1 -1
- package/dist/kempo/api/extensions/GET.js +1 -1
- package/dist/kempo/api/extensions/GET.js.map +1 -1
- package/dist/kempo/api/extensions/POST.js +1 -1
- package/dist/kempo/api/extensions/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/disable/POST.js +1 -1
- package/dist/kempo/api/extensions/[name]/disable/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/enable/POST.js +1 -1
- package/dist/kempo/api/extensions/[name]/enable/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/update/POST.js +1 -1
- package/dist/kempo/api/extensions/[name]/update/POST.js.map +1 -1
- package/dist/kempo/api/extensions/[name]/update-check/GET.js +1 -1
- package/dist/kempo/api/extensions/[name]/update-check/GET.js.map +1 -1
- package/dist/kempo/api/extensions/available/GET.js +1 -1
- package/dist/kempo/api/extensions/available/GET.js.map +1 -1
- package/dist/kempo/api/extensions/known/GET.js +1 -1
- package/dist/kempo/api/extensions/known/GET.js.map +1 -1
- package/dist/kempo/api/forgot-password/POST.js +1 -1
- package/dist/kempo/api/forgot-password/POST.js.map +1 -1
- package/dist/kempo/api/fragments/DELETE.js +1 -1
- package/dist/kempo/api/fragments/DELETE.js.map +1 -1
- package/dist/kempo/api/fragments/GET.js +1 -1
- package/dist/kempo/api/fragments/GET.js.map +1 -1
- package/dist/kempo/api/fragments/POST.js +1 -1
- package/dist/kempo/api/fragments/POST.js.map +1 -1
- package/dist/kempo/api/fragments/disable/PUT.js +1 -1
- package/dist/kempo/api/fragments/disable/PUT.js.map +1 -1
- package/dist/kempo/api/fragments/enable/PUT.js +1 -1
- package/dist/kempo/api/fragments/enable/PUT.js.map +1 -1
- package/dist/kempo/api/fragments/file/GET.js +1 -1
- package/dist/kempo/api/fragments/file/GET.js.map +1 -1
- package/dist/kempo/api/fragments/file/PATCH.js +1 -1
- package/dist/kempo/api/fragments/file/PATCH.js.map +1 -1
- package/dist/kempo/api/fragments/file/PUT.js +1 -1
- package/dist/kempo/api/fragments/file/PUT.js.map +1 -1
- package/dist/kempo/api/fragments/lock/PUT.js +2 -2
- package/dist/kempo/api/fragments/lock/PUT.js.map +1 -1
- package/dist/kempo/api/globals/DELETE.js +1 -1
- package/dist/kempo/api/globals/DELETE.js.map +1 -1
- package/dist/kempo/api/globals/GET.js +1 -1
- package/dist/kempo/api/globals/GET.js.map +1 -1
- package/dist/kempo/api/globals/POST.js +1 -1
- package/dist/kempo/api/globals/POST.js.map +1 -1
- package/dist/kempo/api/globals/disable/PUT.js +1 -1
- package/dist/kempo/api/globals/disable/PUT.js.map +1 -1
- package/dist/kempo/api/globals/enable/PUT.js +1 -1
- package/dist/kempo/api/globals/enable/PUT.js.map +1 -1
- package/dist/kempo/api/globals/entry/GET.js +1 -1
- package/dist/kempo/api/globals/entry/GET.js.map +1 -1
- package/dist/kempo/api/globals/entry/PUT.js +1 -1
- package/dist/kempo/api/globals/entry/PUT.js.map +1 -1
- package/dist/kempo/api/globals/lock/PUT.js +2 -2
- package/dist/kempo/api/globals/lock/PUT.js.map +1 -1
- package/dist/kempo/api/groups/DELETE.js +1 -1
- package/dist/kempo/api/groups/DELETE.js.map +1 -1
- package/dist/kempo/api/groups/GET.js +1 -1
- package/dist/kempo/api/groups/GET.js.map +1 -1
- package/dist/kempo/api/groups/PATCH.js +1 -1
- package/dist/kempo/api/groups/PATCH.js.map +1 -1
- package/dist/kempo/api/groups/POST.js +1 -1
- package/dist/kempo/api/groups/POST.js.map +1 -1
- package/dist/kempo/api/groups/[name]/GET.js +1 -1
- package/dist/kempo/api/groups/[name]/GET.js.map +1 -1
- package/dist/kempo/api/groups/[name]/members/DELETE.js +1 -1
- package/dist/kempo/api/groups/[name]/members/DELETE.js.map +1 -1
- package/dist/kempo/api/groups/[name]/members/GET.js +1 -1
- package/dist/kempo/api/groups/[name]/members/GET.js.map +1 -1
- package/dist/kempo/api/groups/[name]/members/POST.js +1 -1
- package/dist/kempo/api/groups/[name]/members/POST.js.map +1 -1
- package/dist/kempo/api/groups/[name]/permissions/DELETE.js +1 -1
- package/dist/kempo/api/groups/[name]/permissions/DELETE.js.map +1 -1
- package/dist/kempo/api/groups/[name]/permissions/GET.js +1 -1
- package/dist/kempo/api/groups/[name]/permissions/GET.js.map +1 -1
- package/dist/kempo/api/groups/[name]/permissions/POST.js +1 -1
- package/dist/kempo/api/groups/[name]/permissions/POST.js.map +1 -1
- package/dist/kempo/api/pages/DELETE.js +1 -1
- package/dist/kempo/api/pages/DELETE.js.map +1 -1
- package/dist/kempo/api/pages/GET.js +1 -1
- package/dist/kempo/api/pages/GET.js.map +1 -1
- package/dist/kempo/api/pages/POST.js +1 -1
- package/dist/kempo/api/pages/POST.js.map +1 -1
- package/dist/kempo/api/pages/directories/GET.js +1 -1
- package/dist/kempo/api/pages/directories/GET.js.map +1 -1
- package/dist/kempo/api/pages/disable/PUT.js +1 -1
- package/dist/kempo/api/pages/disable/PUT.js.map +1 -1
- package/dist/kempo/api/pages/enable/PUT.js +1 -1
- package/dist/kempo/api/pages/enable/PUT.js.map +1 -1
- package/dist/kempo/api/pages/file/GET.js +1 -1
- package/dist/kempo/api/pages/file/GET.js.map +1 -1
- package/dist/kempo/api/pages/file/PATCH.js +1 -1
- package/dist/kempo/api/pages/file/PATCH.js.map +1 -1
- package/dist/kempo/api/pages/file/PUT.js +1 -1
- package/dist/kempo/api/pages/file/PUT.js.map +1 -1
- package/dist/kempo/api/pages/lock/PUT.js +2 -2
- package/dist/kempo/api/pages/lock/PUT.js.map +1 -1
- package/dist/kempo/api/pages/search/GET.js +2 -2
- package/dist/kempo/api/pages/search/GET.js.map +1 -1
- package/dist/kempo/api/pages/templates/GET.js +1 -1
- package/dist/kempo/api/pages/templates/GET.js.map +1 -1
- package/dist/kempo/api/permissions/DELETE.js +1 -1
- package/dist/kempo/api/permissions/DELETE.js.map +1 -1
- package/dist/kempo/api/permissions/GET.js +1 -1
- package/dist/kempo/api/permissions/GET.js.map +1 -1
- package/dist/kempo/api/permissions/PATCH.js +1 -1
- package/dist/kempo/api/permissions/PATCH.js.map +1 -1
- package/dist/kempo/api/permissions/POST.js +1 -1
- package/dist/kempo/api/permissions/POST.js.map +1 -1
- package/dist/kempo/api/settings/[owner]/GET.js +1 -1
- package/dist/kempo/api/settings/[owner]/GET.js.map +1 -1
- package/dist/kempo/api/settings/[owner]/[name]/DELETE.js +1 -1
- package/dist/kempo/api/settings/[owner]/[name]/DELETE.js.map +1 -1
- package/dist/kempo/api/settings/[owner]/[name]/GET.js +1 -1
- package/dist/kempo/api/settings/[owner]/[name]/GET.js.map +1 -1
- package/dist/kempo/api/settings/[owner]/[name]/POST.js +1 -1
- package/dist/kempo/api/settings/[owner]/[name]/POST.js.map +1 -1
- package/dist/kempo/api/settings/public/GET.js +1 -1
- package/dist/kempo/api/settings/public/GET.js.map +1 -1
- package/dist/kempo/api/templates/DELETE.js +1 -1
- package/dist/kempo/api/templates/DELETE.js.map +1 -1
- package/dist/kempo/api/templates/GET.js +1 -1
- package/dist/kempo/api/templates/GET.js.map +1 -1
- package/dist/kempo/api/templates/POST.js +1 -1
- package/dist/kempo/api/templates/POST.js.map +1 -1
- package/dist/kempo/api/templates/disable/PUT.js +1 -1
- package/dist/kempo/api/templates/disable/PUT.js.map +1 -1
- package/dist/kempo/api/templates/enable/PUT.js +1 -1
- package/dist/kempo/api/templates/enable/PUT.js.map +1 -1
- package/dist/kempo/api/templates/file/GET.js +1 -1
- package/dist/kempo/api/templates/file/GET.js.map +1 -1
- package/dist/kempo/api/templates/file/PATCH.js +1 -1
- package/dist/kempo/api/templates/file/PATCH.js.map +1 -1
- package/dist/kempo/api/templates/file/PUT.js +1 -1
- package/dist/kempo/api/templates/file/PUT.js.map +1 -1
- package/dist/kempo/api/templates/lock/PUT.js +2 -2
- package/dist/kempo/api/templates/lock/PUT.js.map +1 -1
- package/dist/kempo/api/user/DELETE.js +1 -1
- package/dist/kempo/api/user/DELETE.js.map +1 -1
- package/dist/kempo/api/user/GET.js +1 -1
- package/dist/kempo/api/user/GET.js.map +1 -1
- package/dist/kempo/api/user/PATCH.js +1 -1
- package/dist/kempo/api/user/PATCH.js.map +1 -1
- package/dist/kempo/api/user/[userid]/GET.js +1 -1
- package/dist/kempo/api/user/[userid]/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/groups/DELETE.js +1 -1
- package/dist/kempo/api/user/[userid]/groups/DELETE.js.map +1 -1
- package/dist/kempo/api/user/[userid]/groups/GET.js +1 -1
- package/dist/kempo/api/user/[userid]/groups/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/groups/POST.js +1 -1
- package/dist/kempo/api/user/[userid]/groups/POST.js.map +1 -1
- package/dist/kempo/api/user/[userid]/permissions/GET.js +1 -1
- package/dist/kempo/api/user/[userid]/permissions/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/permissions/all/GET.js +1 -1
- package/dist/kempo/api/user/[userid]/permissions/all/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/permissions/list/GET.js +1 -1
- package/dist/kempo/api/user/[userid]/permissions/list/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/permissions/some/GET.js +1 -1
- package/dist/kempo/api/user/[userid]/permissions/some/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/sessions/DELETE.js +1 -1
- package/dist/kempo/api/user/[userid]/sessions/DELETE.js.map +1 -1
- package/dist/kempo/api/user/[userid]/sessions/GET.js +1 -1
- package/dist/kempo/api/user/[userid]/sessions/GET.js.map +1 -1
- package/dist/kempo/api/user/[userid]/sessions/expired/DELETE.js +1 -1
- package/dist/kempo/api/user/[userid]/sessions/expired/DELETE.js.map +1 -1
- package/dist/kempo/api/user/create/POST.js +1 -1
- package/dist/kempo/api/user/create/POST.js.map +1 -1
- package/dist/kempo/api/user/search/GET.js +1 -1
- package/dist/kempo/api/user/search/GET.js.map +1 -1
- package/dist/kempo/components/LogoutBtn.js +1 -1
- package/dist/kempo/components/LogoutBtn.js.map +1 -1
- package/dist/kempo/components/Menu.js +9 -9
- package/dist/kempo/components/Menu.js.map +1 -1
- package/dist/kempo/components/Permission.js +1 -1
- package/dist/kempo/components/Permission.js.map +1 -1
- package/dist/kempo/components/Role.js +1 -1
- package/dist/kempo/components/Role.js.map +1 -1
- package/dist/kempo/components/UserCreatedAt.js +1 -1
- package/dist/kempo/components/UserCreatedAt.js.map +1 -1
- package/dist/kempo/components/UserEmail.js +1 -1
- package/dist/kempo/components/UserEmail.js.map +1 -1
- package/dist/kempo/components/UserEmailVerified.js +1 -1
- package/dist/kempo/components/UserEmailVerified.js.map +1 -1
- package/dist/kempo/components/UserName.js +1 -1
- package/dist/kempo/components/UserName.js.map +1 -1
- package/dist/kempo/fetch.js +1 -1
- package/dist/kempo/fetch.js.map +1 -1
- package/dist/kempo/sdk.js +1 -1
- package/dist/kempo/sdk.js.map +1 -1
- package/middleware/kempo.js +36 -6
- package/package.json +2 -2
- package/server/utils/extensions/installExtension.js +19 -13
- package/server/utils/extensions/uninstallExtension.js +6 -10
- package/server/utils/pages/deletePage.js +2 -10
- package/dist/admin/accounts/groups/[name]/index.html +0 -518
- package/dist/admin/accounts/groups/index.html +0 -378
- package/dist/admin/accounts/permissions/index.html +0 -397
- package/dist/admin/accounts/users/[id]/index.html +0 -541
- package/dist/admin/accounts/users/index.html +0 -396
- package/dist/admin/content/fragments/edit/index.html +0 -132
- package/dist/admin/content/fragments/index.html +0 -375
- package/dist/admin/content/globals/edit/index.html +0 -132
- package/dist/admin/content/globals/index.html +0 -358
- package/dist/admin/content/pages/edit/index.html +0 -132
- package/dist/admin/content/pages/index.html +0 -509
- package/dist/admin/content/templates/edit/index.html +0 -132
- package/dist/admin/content/templates/index.html +0 -429
- package/dist/admin/extensions/index.html +0 -222
- package/dist/admin/index.html +0 -130
- package/dist/admin/settings/[owner]/[name]/index.html +0 -449
- package/dist/admin/settings/index.html +0 -410
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"/kempo-ui/components/ShadowComponent.js";import{html as t,repeat as a}from"/kempo-ui/lit-all.min.js";import{getPage as n,updatePage as s,listTemplates as i,deletePages as l,listDirectories as o,movePage as r}from"/kempo/sdk.js";import c from"/kempo-ui/components/Dialog.js";import d from"/kempo-ui/components/Toast.js";import"/kempo-ui/components/Icon.js";import"/kempo-ui/components/Accordion.js";import"/kempo-ui/components/HtmlEditor.js";const m=new Set(["head","scripts"]),p=e=>e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"");export default class g extends e{static properties={loading:{state:!0},error:{state:!0},saving:{state:!0},page:{state:!0},templates:{state:!0},contents:{state:!0}};constructor(){super(),this.loading=!0,this.error=!1,this.saving=!1,this.page=null,this.templates=[],this.contents=[],this.file=""}async connectedCallback(){if(super.connectedCallback(),document.addEventListener("keydown",this.handleKeyDown),this.file=new URLSearchParams(window.location.search).get("path")+".page.html",!this.file||".page.html"===this.file)return this.loading=!1,void(this.error=!0);const[[e,t],[,a]]=await Promise.all([n(this.file),i()]);this.loading=!1,e?this.error=!0:(this.templates=a?.templates||[],this.page=t,this.contents=this.buildContents(t.template,t.contents),document.title=`Edit: ${t.name||t.title||this.file} - Admin`)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("keydown",this.handleKeyDown)}getPageUrl(){return"/"+this.file.replace(/\.page\.html$/,"").split("/").filter(Boolean).join("/")}buildContents(e,t){const a=this.templates.find(t=>t.name===e),n=(a?.locations||[{name:"default",label:null}]).map(e=>"string"==typeof e?{name:e,label:null}:e),s=n.map(({name:e,label:a})=>{const n=t.find(t=>t.location===e);return{location:e,label:a||null,content:n?.content||"",orphaned:!1}});for(const e of t)n.some(t=>t.name===e.location)||s.push({location:e.location,label:null,content:e.content,orphaned:!0});return s}getFormState(){const e=this.shadowRoot,t=new Map([...e.querySelectorAll("k-html-editor[data-location]")].map(e=>[e.dataset.location,e.getValue()]));return{name:e.querySelector("#metaName").value,title:e.querySelector("#metaTitle").value,description:e.querySelector("#metaDescription").value,template:e.querySelector("#metaTemplate").value,contents:this.contents.map(({location:e,content:a,orphaned:n})=>({location:e,content:n?t.get(e)?.trim()||a:t.get(e)??a}))}}handleKeyDown=e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),this.saving||this.page?.locked||this.handleSave())};handleTemplateChange=e=>{if(this.page?.locked)return;const t=e.target.value,a=new Map([...this.shadowRoot.querySelectorAll("k-html-editor[data-location]")].map(e=>[e.dataset.location,e.getValue()])),n=this.contents.map(({location:e,content:t})=>({location:e,content:a.get(e)?.trim()||t}));this.contents=this.buildContents(t,n),this.page={...this.page,template:t}};handleDelete=()=>{c.confirm("Delete this page? This action cannot be undone.",async e=>{if(!e)return;const[t]=await l([this.file]);t?d.error(t.msg||"Failed to delete page"):(d.success("Page deleted"),setTimeout(()=>{window.location.href="/admin/pages"},1e3))})};handleMove=async()=>{const[,e]=await o(),a=e?.directories||["."],n=this.file.replace(/\.page\.html$/,"").split("/"),i=n.pop(),l=n.join("/")||".",m=e=>/^\/[a-zA-Z0-9_\-\[\]\/]*$/.test(e)&&!e.includes("..")&&!e.includes("//"),g=e=>(e||"/").replace(/^\//,"").replace(/\/$/,"")||".",h="."===l?"/":"/"+l,u=c.create(t`
|
|
2
2
|
<div class="p">
|
|
3
3
|
<div class="mb">
|
|
4
4
|
<label class="d-b mb-sm"><strong>Location</strong></label>
|
|
5
|
-
<input type="text" id="dlg-move-dir" class="full" list="dlg-move-dir-list" .value="${
|
|
5
|
+
<input type="text" id="dlg-move-dir" class="full" list="dlg-move-dir-list" .value="${h}" placeholder="/">
|
|
6
6
|
<datalist id="dlg-move-dir-list">
|
|
7
|
-
${
|
|
7
|
+
${a.map(e=>t`<option value="${"."===e?"/":"/"+e}">`)}
|
|
8
8
|
</datalist>
|
|
9
9
|
<p id="dlg-move-dir-error" class="mt-sm tc-danger d-n"><small>Invalid path — use format: /path/to/directory</small></p>
|
|
10
|
-
<p class="mt-sm muted"><small>Supports dynamic segments e.g. <code>/blog/[slug]</code>. <button class="link" @click="${()=>
|
|
10
|
+
<p class="mt-sm muted"><small>Supports dynamic segments e.g. <code>/blog/[slug]</code>. <button class="link" @click="${()=>c.alert(t`
|
|
11
11
|
<div class="p">
|
|
12
12
|
<p>Directory paths define where a page lives in the URL structure.</p>
|
|
13
13
|
<p><strong>Examples:</strong></p>
|
|
@@ -23,19 +23,19 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
23
23
|
</div>
|
|
24
24
|
<div>
|
|
25
25
|
<label class="d-b mb-sm"><strong>Page Name</strong></label>
|
|
26
|
-
<input type="text" id="dlg-move-name" class="full" .value="${
|
|
26
|
+
<input type="text" id="dlg-move-name" class="full" .value="${i}">
|
|
27
27
|
<p class="mt-sm muted"><small id="dlg-move-preview"></small></p>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
|
-
`,{title:"Move Page",confirmText:"Save and Move Page",confirmAction:async()=>{const e=
|
|
30
|
+
`,{title:"Move Page",confirmText:"Save and Move Page",confirmAction:async()=>{const e=u.querySelector("#dlg-move-dir").value.trim()||"/",t=p(u.querySelector("#dlg-move-name").value.trim());if(!t)return void d.error("Page name is required");if(!m(e))return void d.error("Invalid directory path");const a=g(e),n="."===a?`${t}.page.html`:`${a}/${t}.page.html`;this.saving=!0;const[i]=await s({file:this.file,...this.getFormState()});if(this.saving=!1,i)return void d.error(i.msg||"Failed to save page");const[l,o]=await r({file:this.file,newFile:n});l?d.error(l.msg||"Failed to move page"):(d.success("Page saved and moved"),setTimeout(()=>{window.location.href=`/admin/pages/edit?path=${encodeURIComponent(o.file.replace(/\.page\.html$/,""))}`},1e3))},cancelText:"Cancel"}),v=()=>{const e=g(u.querySelector("#dlg-move-dir").value||"/"),t=p(u.querySelector("#dlg-move-name").value),a="."===e?"/":"/"+e+"/";u.querySelector("#dlg-move-preview").textContent=t?a+t:""};u.querySelector("#dlg-move-dir").addEventListener("input",e=>{u.querySelector("#dlg-move-dir-error").classList.toggle("d-n",m(e.target.value||"/")),v()}),u.querySelector("#dlg-move-name").addEventListener("input",v),v()};handleReset=()=>{c.confirm("Reset this page? Any unsaved changes will be lost.",e=>{e&&(this.contents=this.buildContents(this.page.template,this.page.contents),this.page={...this.page})})};handleSave=async()=>{this.saving=!0;const e=this.getFormState(),[t,a]=await s({file:this.file,...e});this.saving=!1,t?d.error(t.msg||"Failed to save page"):(this.contents=this.contents.map(t=>{const a=e.contents.find(e=>e.location===t.location);return a?{...t,content:a.content}:t}),this.page={...this.page,...e,contents:e.contents.filter(e=>e.content.trim()),updatedAt:a.updatedAt},document.title=`Edit: ${e.name||e.title||this.file} - Admin`,d.success("Page saved"))};render(){if(this.loading)return t`<div>Loading page...</div>`;if(this.error)return t`
|
|
31
31
|
<div>
|
|
32
32
|
<p>Page not found.</p>
|
|
33
33
|
<a href="/admin/pages">Back to Pages</a>
|
|
34
34
|
</div>
|
|
35
|
-
`;const{page:e,templates:
|
|
36
|
-
${
|
|
35
|
+
`;const{page:e,templates:n,contents:s}=this,i="system"===e.owner,l=e.locked;return t`
|
|
36
|
+
${l?t`
|
|
37
37
|
<div class="p r mb bc-warning tc-warning">
|
|
38
|
-
<k-icon name="lock"></k-icon> This page is locked and cannot be edited.
|
|
38
|
+
<k-icon name="lock"></k-icon> This page is locked and cannot be edited. ${"custom"===e.owner?"Locked by developer":`Managed by: ${e.owner||"external system"}`}
|
|
39
39
|
</div>
|
|
40
40
|
`:""}
|
|
41
41
|
<div class="d-f mb">
|
|
@@ -49,13 +49,13 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
51
|
<div class="flex"></div>
|
|
52
|
-
${l
|
|
52
|
+
${i||l?"":t`
|
|
53
53
|
<div class="btn-grp mrh mb">
|
|
54
54
|
<button class="danger" @click="${this.handleDelete}"><k-icon name="delete"></k-icon> Delete</button>
|
|
55
55
|
<button @click="${this.handleMove}"><k-icon name="drive_file_move"></k-icon> Move Page</button>
|
|
56
56
|
</div>
|
|
57
57
|
`}
|
|
58
|
-
${
|
|
58
|
+
${l?"":t`
|
|
59
59
|
<div class="btn-grp mrh mb">
|
|
60
60
|
<button @click="${this.handleReset}"><k-icon name="restart_alt"></k-icon> Reset</button>
|
|
61
61
|
<button id="saveBtn" class="primary" ?disabled="${this.saving}" @click="${this.handleSave}">
|
|
@@ -74,17 +74,17 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
74
74
|
<div class="p">
|
|
75
75
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
76
76
|
<label style="min-width: 120px;"><strong>Name</strong></label>
|
|
77
|
-
<input type="text" id="metaName" class="flex" .value="${e.name||""}" ?disabled="${l
|
|
77
|
+
<input type="text" id="metaName" class="flex" .value="${e.name||""}" ?disabled="${i||l}">
|
|
78
78
|
</div>
|
|
79
79
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
80
80
|
<label style="min-width: 120px;"><strong>Title</strong></label>
|
|
81
|
-
<input type="text" id="metaTitle" class="flex" .value="${e.title||""}" ?disabled="${
|
|
81
|
+
<input type="text" id="metaTitle" class="flex" .value="${e.title||""}" ?disabled="${l}">
|
|
82
82
|
</div>
|
|
83
83
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
84
84
|
<label style="min-width: 120px;"><strong>Description</strong></label>
|
|
85
|
-
<input type="text" id="metaDescription" class="flex" .value="${e.description||""}" ?disabled="${l
|
|
85
|
+
<input type="text" id="metaDescription" class="flex" .value="${e.description||""}" ?disabled="${i||l}">
|
|
86
86
|
</div>
|
|
87
|
-
${
|
|
87
|
+
${i?"":t`
|
|
88
88
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
89
89
|
<label style="min-width: 120px;"><strong>Author</strong></label>
|
|
90
90
|
<span class="muted">${e.author||""}</span>
|
|
@@ -92,11 +92,11 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
92
92
|
`}
|
|
93
93
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
94
94
|
<label style="min-width: 120px;"><strong>Template</strong></label>
|
|
95
|
-
${(()=>{const a=this.file.replace(/\.page\.html$/,"").split("/").slice(0,-1).join("/")||".",
|
|
96
|
-
<select id="metaTemplate" class="flex" @change="${this.handleTemplateChange}" ?disabled="${
|
|
97
|
-
${
|
|
98
|
-
${
|
|
99
|
-
<option value="${
|
|
95
|
+
${(()=>{const a=this.file.replace(/\.page\.html$/,"").split("/").slice(0,-1).join("/")||".",s=n.filter(e=>{const t="."===e.directory?"":e.directory,n="."===a?"":a;return""===t||n===t||n.startsWith(t+"/")}),i=s.some(t=>t.name===e.template);return t`
|
|
96
|
+
<select id="metaTemplate" class="flex" @change="${this.handleTemplateChange}" ?disabled="${l}">
|
|
97
|
+
${i?"":t`<option value="${e.template}" selected>${e.template} (Not Found, using default)</option>`}
|
|
98
|
+
${s.map(a=>t`
|
|
99
|
+
<option value="${a.name}" ?selected="${a.name===e.template}">${(e=>"."===e.directory?`/${e.name}`:`/${e.directory}/${e.name}`)(a)}</option>
|
|
100
100
|
`)}
|
|
101
101
|
</select>
|
|
102
102
|
`})()}
|
|
@@ -115,24 +115,38 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
115
115
|
</div>
|
|
116
116
|
</div>
|
|
117
117
|
</k-accordion-panel>
|
|
118
|
-
${
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
${Object.keys(e.extraMetadata||{}).length?t`
|
|
119
|
+
<k-accordion-header for-panel="extra-metadata">Extra Metadata</k-accordion-header>
|
|
120
|
+
<k-accordion-panel name="extra-metadata">
|
|
121
|
+
<div class="p">
|
|
122
|
+
${Object.entries(e.extraMetadata).map(([e,a])=>t`
|
|
123
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
124
|
+
<label style="min-width: 120px;" class="muted"><strong>${e}</strong></label>
|
|
125
|
+
<span>${a}</span>
|
|
126
|
+
</div>
|
|
127
|
+
`)}
|
|
128
|
+
<p class="muted mt0"><small>Extra metadata is managed by extensions and cannot be edited here.</small></p>
|
|
129
|
+
</div>
|
|
130
|
+
</k-accordion-panel>
|
|
131
|
+
`:""}
|
|
132
|
+
${a(s,e=>e.location,({location:e,label:a,content:n,orphaned:s})=>{const i="default"===e,o=a||(i?"Content":e.slice(0,1).toUpperCase()+e.slice(1));return t`
|
|
133
|
+
<k-accordion-header for-panel="content-${e}" ?active="${i}">
|
|
134
|
+
${o}${s?t` <span class="tc-warning"><small>(not in template)</small></span>`:""}
|
|
121
135
|
</k-accordion-header>
|
|
122
|
-
<k-accordion-panel name="content-${e}" ?active="${
|
|
136
|
+
<k-accordion-panel name="content-${e}" ?active="${i}">
|
|
123
137
|
<div class="p">
|
|
124
138
|
<k-html-editor
|
|
125
139
|
class="r b"
|
|
126
140
|
data-location="${e}"
|
|
127
141
|
controls="full"
|
|
128
|
-
.value="${
|
|
129
|
-
mode="${
|
|
142
|
+
.value="${n}"
|
|
143
|
+
mode="${m.has(e)?"code":"visual"}"
|
|
130
144
|
style="height: 400px;"
|
|
131
|
-
?disabled="${
|
|
145
|
+
?disabled="${l}"
|
|
132
146
|
></k-html-editor>
|
|
133
147
|
</div>
|
|
134
148
|
</k-accordion-panel>
|
|
135
149
|
`})}
|
|
136
150
|
</k-accordion>
|
|
137
|
-
`}}customElements.define("admin-page-editor",
|
|
151
|
+
`}}customElements.define("admin-page-editor",g);
|
|
138
152
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\PageEditor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"src\\admin\\components\\PageEditor.js","names":["ShadowComponent","html","repeat","getPage","updatePage","listTemplates","deletePages","listDirectories","movePage","Dialog","Toast","CODE_MODE_LOCATIONS","Set","slugify","name","toLowerCase","trim","replace","PageEditor","static","loading","state","error","saving","page","templates","contents","constructor","super","this","file","connectedCallback","document","addEventListener","handleKeyDown","URLSearchParams","window","location","search","get","err","tmplData","Promise","all","buildContents","template","title","disconnectedCallback","removeEventListener","getPageUrl","split","filter","Boolean","join","templateName","existingContents","tmpl","find","t","locations","label","map","l","result","block","b","content","orphaned","some","push","getFormState","root","shadowRoot","editorMap","Map","querySelectorAll","ed","dataset","getValue","querySelector","value","description","e","ctrlKey","metaKey","key","preventDefault","locked","handleSave","handleTemplateChange","newTemplate","target","currentContents","handleDelete","confirm","async","confirmed","msg","success","setTimeout","href","handleMove","dirData","directories","parts","currentName","pop","currentDir","isValidDir","p","test","includes","dirToInternal","currentDirDisplay","$dialog","create","d","alert","confirmText","confirmAction","dirInput","newName","newDir","newFile","saveError","moveError","moved","encodeURIComponent","cancelText","updatePreview","dir","prefix","textContent","classList","toggle","handleReset","saved","c","current","x","updatedAt","render","isSystem","owner","isLocked","author","pageDir","slice","reachable","tDir","directory","pDir","startsWith","currentFound","templateLabel","createdAt","Date","toLocaleString","isDefault","headerTitle","toUpperCase","has","customElements","define"],"sources":["0"],"sourcesContent":["import ShadowComponent from '/kempo-ui/components/ShadowComponent.js';\nimport { html, repeat } from '/kempo-ui/lit-all.min.js';\nimport { getPage, updatePage, listTemplates, deletePages, listDirectories, movePage } from '/kempo/sdk.js';\nimport Dialog from '/kempo-ui/components/Dialog.js';\nimport Toast from '/kempo-ui/components/Toast.js';\nimport '/kempo-ui/components/Icon.js';\nimport '/kempo-ui/components/Accordion.js';\nimport '/kempo-ui/components/HtmlEditor.js';\n\nconst CODE_MODE_LOCATIONS = new Set(['head', 'scripts']);\n\nconst slugify = name => name\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n\nexport default class PageEditor extends ShadowComponent {\n static properties = {\n loading: { state: true },\n error: { state: true },\n saving: { state: true },\n page: { state: true },\n templates: { state: true },\n contents: { state: true }\n };\n\n constructor() {\n super();\n this.loading = true;\n this.error = false;\n this.saving = false;\n this.page = null;\n this.templates = [];\n this.contents = [];\n this.file = '';\n }\n\n async connectedCallback() {\n super.connectedCallback();\n document.addEventListener('keydown', this.handleKeyDown);\n this.file = new URLSearchParams(window.location.search).get('path') + '.page.html';\n\n if (!this.file || this.file === '.page.html') {\n this.loading = false;\n this.error = true;\n return;\n }\n\n const [[err, page], [, tmplData]] = await Promise.all([\n getPage(this.file),\n listTemplates()\n ]);\n\n this.loading = false;\n\n if (err) {\n this.error = true;\n return;\n }\n\n this.templates = tmplData?.templates || [];\n this.page = page;\n this.contents = this.buildContents(page.template, page.contents);\n document.title = `Edit: ${page.name || page.title || this.file} - Admin`;\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('keydown', this.handleKeyDown);\n }\n\n /*\n Helpers\n */\n\n getPageUrl() {\n const path = this.file.replace(/\\.page\\.html$/, '');\n return '/' + path.split('/').filter(Boolean).join('/');\n }\n\n buildContents(templateName, existingContents) {\n const tmpl = this.templates.find(t => t.name === templateName);\n const raw = tmpl?.locations || [{ name: 'default', label: null }];\n const locations = raw.map(l => typeof l === 'string' ? { name: l, label: null } : l);\n const result = locations.map(({ name, label }) => {\n const block = existingContents.find(b => b.location === name);\n return { location: name, label: label || null, content: block?.content || '', orphaned: false };\n });\n for (const block of existingContents) {\n if (!locations.some(l => l.name === block.location))\n result.push({ location: block.location, label: null, content: block.content, orphaned: true });\n }\n return result;\n }\n\n getFormState() {\n const root = this.shadowRoot;\n const editorMap = new Map(\n [...root.querySelectorAll('k-html-editor[data-location]')].map(ed => [ed.dataset.location, ed.getValue()])\n );\n return {\n name: root.querySelector('#metaName').value,\n title: root.querySelector('#metaTitle').value,\n description: root.querySelector('#metaDescription').value,\n template: root.querySelector('#metaTemplate').value,\n contents: this.contents.map(({ location, content, orphaned }) => ({\n location,\n content: orphaned\n ? (editorMap.get(location)?.trim() || content)\n : (editorMap.get(location) ?? content)\n }))\n };\n }\n\n /*\n Event Handlers\n */\n\n handleKeyDown = e => {\n if((e.ctrlKey || e.metaKey) && e.key === 's'){\n e.preventDefault();\n if(!this.saving && !this.page?.locked) this.handleSave();\n }\n };\n\n handleTemplateChange = e => {\n const newTemplate = e.target.value;\n const editorMap = new Map(\n [...this.shadowRoot.querySelectorAll('k-html-editor[data-location]')].map(ed => [ed.dataset.location, ed.getValue()])\n );\n // Fall back to state content for any editor that hasn't initialized (Monaco lazy-loads)\n const currentContents = this.contents.map(({ location, content }) => ({\n location,\n content: editorMap.get(location)?.trim() || content\n }));\n this.contents = this.buildContents(newTemplate, currentContents);\n this.page = { ...this.page, template: newTemplate };\n };\n\n handleDelete = () => {\n Dialog.confirm('Delete this page? This action cannot be undone.', async confirmed => {\n if (!confirmed) return;\n const [error] = await deletePages([this.file]);\n if (error) {\n Toast.error(error.msg || 'Failed to delete page');\n return;\n }\n Toast.success('Page deleted');\n setTimeout(() => { window.location.href = '/admin/pages'; }, 1000);\n });\n };\n\n handleMove = async () => {\n const [, dirData] = await listDirectories();\n const directories = dirData?.directories || ['.'];\n\n const parts = this.file.replace(/\\.page\\.html$/, '').split('/');\n const currentName = parts.pop();\n const currentDir = parts.join('/') || '.';\n\n const isValidDir = p => /^\\/[a-zA-Z0-9_\\-\\[\\]\\/]*$/.test(p) && !p.includes('..') && !p.includes('//');\n const dirToInternal = p => { const t = (p || '/').replace(/^\\//, '').replace(/\\/$/, ''); return t || '.'; };\n const currentDirDisplay = currentDir === '.' ? '/' : '/' + currentDir;\n\n const openLearnMore = () => Dialog.alert(html`\n <div class=\"p\">\n <p>Directory paths define where a page lives in the URL structure.</p>\n <p><strong>Examples:</strong></p>\n <ul>\n <li><code>/</code> — root, e.g. <code>/about</code></li>\n <li><code>/blog</code> — e.g. <code>/blog/my-post</code></li>\n <li><code>/blog/[slug]</code> — dynamic segment; <code>[slug]</code> matches any value</li>\n </ul>\n <p>Dynamic segments like <code>[token]</code> allow a single page to handle many URLs. The segment name is available to the page as a URL parameter.</p>\n <p>You can nest segments: <code>/store/[category]/[id]</code></p>\n </div>\n `, null, { title: 'About Directory Paths' });\n\n const $dialog = Dialog.create(html`\n <div class=\"p\">\n <div class=\"mb\">\n <label class=\"d-b mb-sm\"><strong>Location</strong></label>\n <input type=\"text\" id=\"dlg-move-dir\" class=\"full\" list=\"dlg-move-dir-list\" .value=\"${currentDirDisplay}\" placeholder=\"/\">\n <datalist id=\"dlg-move-dir-list\">\n ${directories.map(d => html`<option value=\"${d === '.' ? '/' : '/' + d}\">`)} \n </datalist>\n <p id=\"dlg-move-dir-error\" class=\"mt-sm tc-danger d-n\"><small>Invalid path — use format: /path/to/directory</small></p>\n <p class=\"mt-sm muted\"><small>Supports dynamic segments e.g. <code>/blog/[slug]</code>. <button class=\"link\" @click=\"${openLearnMore}\">Learn more</button></small></p>\n </div>\n <div>\n <label class=\"d-b mb-sm\"><strong>Page Name</strong></label>\n <input type=\"text\" id=\"dlg-move-name\" class=\"full\" .value=\"${currentName}\">\n <p class=\"mt-sm muted\"><small id=\"dlg-move-preview\"></small></p>\n </div>\n </div>\n `, {\n title: 'Move Page',\n confirmText: 'Save and Move Page',\n confirmAction: async () => {\n const dirInput = $dialog.querySelector('#dlg-move-dir').value.trim() || '/';\n const newName = slugify($dialog.querySelector('#dlg-move-name').value.trim());\n if (!newName) {\n Toast.error('Page name is required');\n return;\n }\n if (!isValidDir(dirInput)) {\n Toast.error('Invalid directory path');\n return;\n }\n const newDir = dirToInternal(dirInput);\n const newFile = newDir === '.'\n ? `${newName}.page.html`\n : `${newDir}/${newName}.page.html`;\n\n this.saving = true;\n const [saveError] = await updatePage({ file: this.file, ...this.getFormState() });\n this.saving = false;\n if (saveError) {\n Toast.error(saveError.msg || 'Failed to save page');\n return;\n }\n\n const [moveError, moved] = await movePage({ file: this.file, newFile });\n if (moveError) {\n Toast.error(moveError.msg || 'Failed to move page');\n return;\n }\n\n Toast.success('Page saved and moved');\n setTimeout(() => {\n window.location.href = `/admin/pages/edit?path=${encodeURIComponent(moved.file.replace(/\\.page\\.html$/, ''))}`;\n }, 1000);\n },\n cancelText: 'Cancel'\n });\n\n const updatePreview = () => {\n const dir = dirToInternal($dialog.querySelector('#dlg-move-dir').value || '/');\n const name = slugify($dialog.querySelector('#dlg-move-name').value);\n const prefix = dir === '.' ? '/' : '/' + dir + '/';\n $dialog.querySelector('#dlg-move-preview').textContent = name ? prefix + name : '';\n };\n $dialog.querySelector('#dlg-move-dir').addEventListener('input', e => {\n $dialog.querySelector('#dlg-move-dir-error').classList.toggle('d-n', isValidDir(e.target.value || '/'));\n updatePreview();\n });\n $dialog.querySelector('#dlg-move-name').addEventListener('input', updatePreview);\n updatePreview();\n };\n\n handleReset = () => {\n Dialog.confirm('Reset this page? Any unsaved changes will be lost.', confirmed => {\n if (!confirmed) return;\n this.contents = this.buildContents(this.page.template, this.page.contents);\n this.page = { ...this.page };\n });\n };\n\n handleSave = async () => {\n this.saving = true;\n const state = this.getFormState();\n const [saveError, saved] = await updatePage({ file: this.file, ...state });\n this.saving = false;\n\n if (saveError) {\n Toast.error(saveError.msg || 'Failed to save page');\n return;\n }\n\n this.contents = this.contents.map(c => {\n const current = state.contents.find(x => x.location === c.location);\n return current ? { ...c, content: current.content } : c;\n });\n this.page = {\n ...this.page,\n ...state,\n contents: state.contents.filter(b => b.content.trim()),\n updatedAt: saved.updatedAt\n };\n\n document.title = `Edit: ${state.name || state.title || this.file} - Admin`;\n Toast.success('Page saved');\n };\n\n /*\n Render\n */\n\n render() {\n if (this.loading) return html`<div>Loading page...</div>`;\n\n if (this.error) return html`\n <div>\n <p>Page not found.</p>\n <a href=\"/admin/pages\">Back to Pages</a>\n </div>\n `;\n\n const { page, templates, contents } = this;\n const isSystem = page.owner === 'system';\n const isLocked = page.locked;\n\n return html`\n ${isLocked ? html`\n <div class=\"p r mb bc-warning tc-warning\">\n <k-icon name=\"lock\"></k-icon> This page is locked and cannot be edited. It is managed by an extension.\n </div>\n ` : ''}\n <div class=\"d-f mb\">\n <div class=\"btn-grp mrh mb\">\n <a href=\"/admin/content/pages\" class=\"btn\">\n <k-icon name=\"arrow\" direction=\"left\"></k-icon> Pages\n </a>\n <a href=\"${this.getPageUrl()}\" id=\"page-link\" class=\"btn\">\n <k-icon name=\"visibility\"></k-icon> View Page\n </a>\n </div>\n \n <div class=\"flex\"></div>\n ${!isSystem && !isLocked ? html`\n <div class=\"btn-grp mrh mb\">\n <button class=\"danger\" @click=\"${this.handleDelete}\"><k-icon name=\"delete\"></k-icon> Delete</button>\n <button @click=\"${this.handleMove}\"><k-icon name=\"drive_file_move\"></k-icon> Move Page</button>\n </div>\n ` : ''}\n ${!isLocked ? html`\n <div class=\"btn-grp mrh mb\">\n <button @click=\"${this.handleReset}\"><k-icon name=\"restart_alt\"></k-icon> Reset</button>\n <button id=\"saveBtn\" class=\"primary\" ?disabled=\"${this.saving}\" @click=\"${this.handleSave}\">\n <k-icon name=\"save\"></k-icon> Save\n </button>\n </div>\n ` : ''}\n </div>\n <h1 class=\"mb0\">Page: ${page.name || page.title || this.file}</h1>\n\n <a href=\"${this.getPageUrl()}\" class=\"d-b mb\"><code>${this.getPageUrl()}</code></a>\n\n <k-accordion>\n <k-accordion-header for-panel=\"metadata\">Metadata</k-accordion-header>\n <k-accordion-panel name=\"metadata\">\n <div class=\"p\">\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Name</strong></label>\n <input type=\"text\" id=\"metaName\" class=\"flex\" .value=\"${page.name || ''}\" ?disabled=\"${isSystem || isLocked}\">\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Title</strong></label>\n <input type=\"text\" id=\"metaTitle\" class=\"flex\" .value=\"${page.title || ''}\" ?disabled=\"${isLocked}\">\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Description</strong></label>\n <input type=\"text\" id=\"metaDescription\" class=\"flex\" .value=\"${page.description || ''}\" ?disabled=\"${isSystem || isLocked}\">\n </div>\n ${!isSystem ? html`\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Author</strong></label>\n <span class=\"muted\">${page.author || ''}</span>\n </div>\n ` : ''}\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Template</strong></label>\n ${(() => {\n const pageDir = this.file.replace(/\\.page\\.html$/, '').split('/').slice(0, -1).join('/') || '.';\n const reachable = templates.filter(t => {\n const tDir = t.directory === '.' ? '' : t.directory;\n const pDir = pageDir === '.' ? '' : pageDir;\n return tDir === '' || pDir === tDir || pDir.startsWith(tDir + '/');\n });\n const templateLabel = t => t.directory === '.' ? `/${t.name}` : `/${t.directory}/${t.name}`;\n const currentFound = reachable.some(t => t.name === page.template);\n return html`\n <select id=\"metaTemplate\" class=\"flex\" @change=\"${this.handleTemplateChange}\" ?disabled=\"${isLocked}\">\n ${!currentFound ? html`<option value=\"${page.template}\" selected>${page.template} (Not Found, using default)</option>` : ''}\n ${reachable.map(t => html`\n <option value=\"${t.name}\" ?selected=\"${t.name === page.template}\">${templateLabel(t)}</option>\n `)}\n </select>\n `;\n })()}\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Owner</strong></label>\n <span class=\"muted\">${page.owner || ''}</span>\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Created</strong></label>\n <span class=\"muted\">${page.createdAt ? new Date(page.createdAt).toLocaleString() : ''}</span>\n </div>\n <div class=\"d-f\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Updated</strong></label>\n <span class=\"muted\">${page.updatedAt ? new Date(page.updatedAt).toLocaleString() : ''}</span>\n </div>\n </div>\n </k-accordion-panel>\n ${repeat(contents, c => c.location, ({ location, label, content, orphaned }) => {\n const isDefault = location === 'default';\n const headerTitle = label || (isDefault ? 'Content' : location.slice(0, 1).toUpperCase() + location.slice(1));\n return html`\n <k-accordion-header for-panel=\"content-${location}\" ?active=\"${isDefault}\">\n ${headerTitle}${orphaned ? html` <span class=\"tc-warning\"><small>(not in template)</small></span>` : ''}\n </k-accordion-header>\n <k-accordion-panel name=\"content-${location}\" ?active=\"${isDefault}\">\n <div class=\"p\">\n <k-html-editor\n class=\"r b\"\n data-location=\"${location}\"\n controls=\"full\"\n .value=\"${content}\"\n mode=\"${CODE_MODE_LOCATIONS.has(location) ? 'code' : 'visual'}\"\n style=\"height: 400px;\"\n ?disabled=\"${isLocked}\"\n ></k-html-editor>\n </div>\n </k-accordion-panel>\n `;\n })}\n </k-accordion>\n `;\n }\n}\n\ncustomElements.define('admin-page-editor', PageEditor);\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"src\\admin\\components\\PageEditor.js","names":["ShadowComponent","html","repeat","getPage","updatePage","listTemplates","deletePages","listDirectories","movePage","Dialog","Toast","CODE_MODE_LOCATIONS","Set","slugify","name","toLowerCase","trim","replace","PageEditor","static","loading","state","error","saving","page","templates","contents","constructor","super","this","file","connectedCallback","document","addEventListener","handleKeyDown","URLSearchParams","window","location","search","get","err","tmplData","Promise","all","buildContents","template","title","disconnectedCallback","removeEventListener","getPageUrl","split","filter","Boolean","join","templateName","existingContents","tmpl","find","t","locations","label","map","l","result","block","b","content","orphaned","some","push","getFormState","root","shadowRoot","editorMap","Map","querySelectorAll","ed","dataset","getValue","querySelector","value","description","e","ctrlKey","metaKey","key","preventDefault","locked","handleSave","handleTemplateChange","newTemplate","target","currentContents","handleDelete","confirm","async","confirmed","msg","success","setTimeout","href","handleMove","dirData","directories","parts","currentName","pop","currentDir","isValidDir","p","test","includes","dirToInternal","currentDirDisplay","$dialog","create","d","alert","confirmText","confirmAction","dirInput","newName","newDir","newFile","saveError","moveError","moved","encodeURIComponent","cancelText","updatePreview","dir","prefix","textContent","classList","toggle","handleReset","saved","c","current","x","updatedAt","render","isSystem","owner","isLocked","author","pageDir","slice","reachable","tDir","directory","pDir","startsWith","currentFound","templateLabel","createdAt","Date","toLocaleString","Object","keys","extraMetadata","length","entries","isDefault","headerTitle","toUpperCase","has","customElements","define"],"sources":["0"],"sourcesContent":["import ShadowComponent from '/kempo-ui/components/ShadowComponent.js';\nimport { html, repeat } from '/kempo-ui/lit-all.min.js';\nimport { getPage, updatePage, listTemplates, deletePages, listDirectories, movePage } from '/kempo/sdk.js';\nimport Dialog from '/kempo-ui/components/Dialog.js';\nimport Toast from '/kempo-ui/components/Toast.js';\nimport '/kempo-ui/components/Icon.js';\nimport '/kempo-ui/components/Accordion.js';\nimport '/kempo-ui/components/HtmlEditor.js';\n\nconst CODE_MODE_LOCATIONS = new Set(['head', 'scripts']);\n\nconst slugify = name => name\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n\nexport default class PageEditor extends ShadowComponent {\n static properties = {\n loading: { state: true },\n error: { state: true },\n saving: { state: true },\n page: { state: true },\n templates: { state: true },\n contents: { state: true }\n };\n\n constructor() {\n super();\n this.loading = true;\n this.error = false;\n this.saving = false;\n this.page = null;\n this.templates = [];\n this.contents = [];\n this.file = '';\n }\n\n async connectedCallback() {\n super.connectedCallback();\n document.addEventListener('keydown', this.handleKeyDown);\n this.file = new URLSearchParams(window.location.search).get('path') + '.page.html';\n\n if (!this.file || this.file === '.page.html') {\n this.loading = false;\n this.error = true;\n return;\n }\n\n const [[err, page], [, tmplData]] = await Promise.all([\n getPage(this.file),\n listTemplates()\n ]);\n\n this.loading = false;\n\n if (err) {\n this.error = true;\n return;\n }\n\n this.templates = tmplData?.templates || [];\n this.page = page;\n this.contents = this.buildContents(page.template, page.contents);\n document.title = `Edit: ${page.name || page.title || this.file} - Admin`;\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n document.removeEventListener('keydown', this.handleKeyDown);\n }\n\n /*\n Helpers\n */\n\n getPageUrl() {\n const path = this.file.replace(/\\.page\\.html$/, '');\n return '/' + path.split('/').filter(Boolean).join('/');\n }\n\n buildContents(templateName, existingContents) {\n const tmpl = this.templates.find(t => t.name === templateName);\n const raw = tmpl?.locations || [{ name: 'default', label: null }];\n const locations = raw.map(l => typeof l === 'string' ? { name: l, label: null } : l);\n const result = locations.map(({ name, label }) => {\n const block = existingContents.find(b => b.location === name);\n return { location: name, label: label || null, content: block?.content || '', orphaned: false };\n });\n for (const block of existingContents) {\n if (!locations.some(l => l.name === block.location))\n result.push({ location: block.location, label: null, content: block.content, orphaned: true });\n }\n return result;\n }\n\n getFormState() {\n const root = this.shadowRoot;\n const editorMap = new Map(\n [...root.querySelectorAll('k-html-editor[data-location]')].map(ed => [ed.dataset.location, ed.getValue()])\n );\n return {\n name: root.querySelector('#metaName').value,\n title: root.querySelector('#metaTitle').value,\n description: root.querySelector('#metaDescription').value,\n template: root.querySelector('#metaTemplate').value,\n contents: this.contents.map(({ location, content, orphaned }) => ({\n location,\n content: orphaned\n ? (editorMap.get(location)?.trim() || content)\n : (editorMap.get(location) ?? content)\n }))\n };\n }\n\n /*\n Event Handlers\n */\n\n handleKeyDown = e => {\n if((e.ctrlKey || e.metaKey) && e.key === 's'){\n e.preventDefault();\n if(!this.saving && !this.page?.locked) this.handleSave();\n }\n };\n\n handleTemplateChange = e => {\n if(this.page?.locked) return;\n const newTemplate = e.target.value;\n const editorMap = new Map(\n [...this.shadowRoot.querySelectorAll('k-html-editor[data-location]')].map(ed => [ed.dataset.location, ed.getValue()])\n );\n // Fall back to state content for any editor that hasn't initialized (Monaco lazy-loads)\n const currentContents = this.contents.map(({ location, content }) => ({\n location,\n content: editorMap.get(location)?.trim() || content\n }));\n this.contents = this.buildContents(newTemplate, currentContents);\n this.page = { ...this.page, template: newTemplate };\n };\n\n handleDelete = () => {\n Dialog.confirm('Delete this page? This action cannot be undone.', async confirmed => {\n if (!confirmed) return;\n const [error] = await deletePages([this.file]);\n if (error) {\n Toast.error(error.msg || 'Failed to delete page');\n return;\n }\n Toast.success('Page deleted');\n setTimeout(() => { window.location.href = '/admin/pages'; }, 1000);\n });\n };\n\n handleMove = async () => {\n const [, dirData] = await listDirectories();\n const directories = dirData?.directories || ['.'];\n\n const parts = this.file.replace(/\\.page\\.html$/, '').split('/');\n const currentName = parts.pop();\n const currentDir = parts.join('/') || '.';\n\n const isValidDir = p => /^\\/[a-zA-Z0-9_\\-\\[\\]\\/]*$/.test(p) && !p.includes('..') && !p.includes('//');\n const dirToInternal = p => { const t = (p || '/').replace(/^\\//, '').replace(/\\/$/, ''); return t || '.'; };\n const currentDirDisplay = currentDir === '.' ? '/' : '/' + currentDir;\n\n const openLearnMore = () => Dialog.alert(html`\n <div class=\"p\">\n <p>Directory paths define where a page lives in the URL structure.</p>\n <p><strong>Examples:</strong></p>\n <ul>\n <li><code>/</code> — root, e.g. <code>/about</code></li>\n <li><code>/blog</code> — e.g. <code>/blog/my-post</code></li>\n <li><code>/blog/[slug]</code> — dynamic segment; <code>[slug]</code> matches any value</li>\n </ul>\n <p>Dynamic segments like <code>[token]</code> allow a single page to handle many URLs. The segment name is available to the page as a URL parameter.</p>\n <p>You can nest segments: <code>/store/[category]/[id]</code></p>\n </div>\n `, null, { title: 'About Directory Paths' });\n\n const $dialog = Dialog.create(html`\n <div class=\"p\">\n <div class=\"mb\">\n <label class=\"d-b mb-sm\"><strong>Location</strong></label>\n <input type=\"text\" id=\"dlg-move-dir\" class=\"full\" list=\"dlg-move-dir-list\" .value=\"${currentDirDisplay}\" placeholder=\"/\">\n <datalist id=\"dlg-move-dir-list\">\n ${directories.map(d => html`<option value=\"${d === '.' ? '/' : '/' + d}\">`)} \n </datalist>\n <p id=\"dlg-move-dir-error\" class=\"mt-sm tc-danger d-n\"><small>Invalid path — use format: /path/to/directory</small></p>\n <p class=\"mt-sm muted\"><small>Supports dynamic segments e.g. <code>/blog/[slug]</code>. <button class=\"link\" @click=\"${openLearnMore}\">Learn more</button></small></p>\n </div>\n <div>\n <label class=\"d-b mb-sm\"><strong>Page Name</strong></label>\n <input type=\"text\" id=\"dlg-move-name\" class=\"full\" .value=\"${currentName}\">\n <p class=\"mt-sm muted\"><small id=\"dlg-move-preview\"></small></p>\n </div>\n </div>\n `, {\n title: 'Move Page',\n confirmText: 'Save and Move Page',\n confirmAction: async () => {\n const dirInput = $dialog.querySelector('#dlg-move-dir').value.trim() || '/';\n const newName = slugify($dialog.querySelector('#dlg-move-name').value.trim());\n if (!newName) {\n Toast.error('Page name is required');\n return;\n }\n if (!isValidDir(dirInput)) {\n Toast.error('Invalid directory path');\n return;\n }\n const newDir = dirToInternal(dirInput);\n const newFile = newDir === '.'\n ? `${newName}.page.html`\n : `${newDir}/${newName}.page.html`;\n\n this.saving = true;\n const [saveError] = await updatePage({ file: this.file, ...this.getFormState() });\n this.saving = false;\n if (saveError) {\n Toast.error(saveError.msg || 'Failed to save page');\n return;\n }\n\n const [moveError, moved] = await movePage({ file: this.file, newFile });\n if (moveError) {\n Toast.error(moveError.msg || 'Failed to move page');\n return;\n }\n\n Toast.success('Page saved and moved');\n setTimeout(() => {\n window.location.href = `/admin/pages/edit?path=${encodeURIComponent(moved.file.replace(/\\.page\\.html$/, ''))}`;\n }, 1000);\n },\n cancelText: 'Cancel'\n });\n\n const updatePreview = () => {\n const dir = dirToInternal($dialog.querySelector('#dlg-move-dir').value || '/');\n const name = slugify($dialog.querySelector('#dlg-move-name').value);\n const prefix = dir === '.' ? '/' : '/' + dir + '/';\n $dialog.querySelector('#dlg-move-preview').textContent = name ? prefix + name : '';\n };\n $dialog.querySelector('#dlg-move-dir').addEventListener('input', e => {\n $dialog.querySelector('#dlg-move-dir-error').classList.toggle('d-n', isValidDir(e.target.value || '/'));\n updatePreview();\n });\n $dialog.querySelector('#dlg-move-name').addEventListener('input', updatePreview);\n updatePreview();\n };\n\n handleReset = () => {\n Dialog.confirm('Reset this page? Any unsaved changes will be lost.', confirmed => {\n if (!confirmed) return;\n this.contents = this.buildContents(this.page.template, this.page.contents);\n this.page = { ...this.page };\n });\n };\n\n handleSave = async () => {\n this.saving = true;\n const state = this.getFormState();\n const [saveError, saved] = await updatePage({ file: this.file, ...state });\n this.saving = false;\n\n if (saveError) {\n Toast.error(saveError.msg || 'Failed to save page');\n return;\n }\n\n this.contents = this.contents.map(c => {\n const current = state.contents.find(x => x.location === c.location);\n return current ? { ...c, content: current.content } : c;\n });\n this.page = {\n ...this.page,\n ...state,\n contents: state.contents.filter(b => b.content.trim()),\n updatedAt: saved.updatedAt\n };\n\n document.title = `Edit: ${state.name || state.title || this.file} - Admin`;\n Toast.success('Page saved');\n };\n\n /*\n Render\n */\n\n render() {\n if (this.loading) return html`<div>Loading page...</div>`;\n\n if (this.error) return html`\n <div>\n <p>Page not found.</p>\n <a href=\"/admin/pages\">Back to Pages</a>\n </div>\n `;\n\n const { page, templates, contents } = this;\n const isSystem = page.owner === 'system';\n const isLocked = page.locked;\n\n return html`\n ${isLocked ? html`\n <div class=\"p r mb bc-warning tc-warning\">\n <k-icon name=\"lock\"></k-icon> This page is locked and cannot be edited. ${page.owner === 'custom' ? 'Locked by developer' : `Managed by: ${page.owner || 'external system'}`}\n </div>\n ` : ''}\n <div class=\"d-f mb\">\n <div class=\"btn-grp mrh mb\">\n <a href=\"/admin/content/pages\" class=\"btn\">\n <k-icon name=\"arrow\" direction=\"left\"></k-icon> Pages\n </a>\n <a href=\"${this.getPageUrl()}\" id=\"page-link\" class=\"btn\">\n <k-icon name=\"visibility\"></k-icon> View Page\n </a>\n </div>\n \n <div class=\"flex\"></div>\n ${!isSystem && !isLocked ? html`\n <div class=\"btn-grp mrh mb\">\n <button class=\"danger\" @click=\"${this.handleDelete}\"><k-icon name=\"delete\"></k-icon> Delete</button>\n <button @click=\"${this.handleMove}\"><k-icon name=\"drive_file_move\"></k-icon> Move Page</button>\n </div>\n ` : ''}\n ${!isLocked ? html`\n <div class=\"btn-grp mrh mb\">\n <button @click=\"${this.handleReset}\"><k-icon name=\"restart_alt\"></k-icon> Reset</button>\n <button id=\"saveBtn\" class=\"primary\" ?disabled=\"${this.saving}\" @click=\"${this.handleSave}\">\n <k-icon name=\"save\"></k-icon> Save\n </button>\n </div>\n ` : ''}\n </div>\n <h1 class=\"mb0\">Page: ${page.name || page.title || this.file}</h1>\n\n <a href=\"${this.getPageUrl()}\" class=\"d-b mb\"><code>${this.getPageUrl()}</code></a>\n\n <k-accordion>\n <k-accordion-header for-panel=\"metadata\">Metadata</k-accordion-header>\n <k-accordion-panel name=\"metadata\">\n <div class=\"p\">\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Name</strong></label>\n <input type=\"text\" id=\"metaName\" class=\"flex\" .value=\"${page.name || ''}\" ?disabled=\"${isSystem || isLocked}\">\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Title</strong></label>\n <input type=\"text\" id=\"metaTitle\" class=\"flex\" .value=\"${page.title || ''}\" ?disabled=\"${isLocked}\">\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Description</strong></label>\n <input type=\"text\" id=\"metaDescription\" class=\"flex\" .value=\"${page.description || ''}\" ?disabled=\"${isSystem || isLocked}\">\n </div>\n ${!isSystem ? html`\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Author</strong></label>\n <span class=\"muted\">${page.author || ''}</span>\n </div>\n ` : ''}\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Template</strong></label>\n ${(() => {\n const pageDir = this.file.replace(/\\.page\\.html$/, '').split('/').slice(0, -1).join('/') || '.';\n const reachable = templates.filter(t => {\n const tDir = t.directory === '.' ? '' : t.directory;\n const pDir = pageDir === '.' ? '' : pageDir;\n return tDir === '' || pDir === tDir || pDir.startsWith(tDir + '/');\n });\n const templateLabel = t => t.directory === '.' ? `/${t.name}` : `/${t.directory}/${t.name}`;\n const currentFound = reachable.some(t => t.name === page.template);\n return html`\n <select id=\"metaTemplate\" class=\"flex\" @change=\"${this.handleTemplateChange}\" ?disabled=\"${isLocked}\">\n ${!currentFound ? html`<option value=\"${page.template}\" selected>${page.template} (Not Found, using default)</option>` : ''}\n ${reachable.map(t => html`\n <option value=\"${t.name}\" ?selected=\"${t.name === page.template}\">${templateLabel(t)}</option>\n `)}\n </select>\n `;\n })()}\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Owner</strong></label>\n <span class=\"muted\">${page.owner || ''}</span>\n </div>\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Created</strong></label>\n <span class=\"muted\">${page.createdAt ? new Date(page.createdAt).toLocaleString() : ''}</span>\n </div>\n <div class=\"d-f\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\"><strong>Updated</strong></label>\n <span class=\"muted\">${page.updatedAt ? new Date(page.updatedAt).toLocaleString() : ''}</span>\n </div>\n </div>\n </k-accordion-panel>\n ${Object.keys(page.extraMetadata || {}).length ? html`\n <k-accordion-header for-panel=\"extra-metadata\">Extra Metadata</k-accordion-header>\n <k-accordion-panel name=\"extra-metadata\">\n <div class=\"p\">\n ${Object.entries(page.extraMetadata).map(([key, value]) => html`\n <div class=\"d-f mb\" style=\"align-items: center; gap: var(--spacer);\">\n <label style=\"min-width: 120px;\" class=\"muted\"><strong>${key}</strong></label>\n <span>${value}</span>\n </div>\n `)}\n <p class=\"muted mt0\"><small>Extra metadata is managed by extensions and cannot be edited here.</small></p>\n </div>\n </k-accordion-panel>\n ` : ''}\n ${repeat(contents, c => c.location, ({ location, label, content, orphaned }) => {\n const isDefault = location === 'default';\n const headerTitle = label || (isDefault ? 'Content' : location.slice(0, 1).toUpperCase() + location.slice(1));\n return html`\n <k-accordion-header for-panel=\"content-${location}\" ?active=\"${isDefault}\">\n ${headerTitle}${orphaned ? html` <span class=\"tc-warning\"><small>(not in template)</small></span>` : ''}\n </k-accordion-header>\n <k-accordion-panel name=\"content-${location}\" ?active=\"${isDefault}\">\n <div class=\"p\">\n <k-html-editor\n class=\"r b\"\n data-location=\"${location}\"\n controls=\"full\"\n .value=\"${content}\"\n mode=\"${CODE_MODE_LOCATIONS.has(location) ? 'code' : 'visual'}\"\n style=\"height: 400px;\"\n ?disabled=\"${isLocked}\"\n ></k-html-editor>\n </div>\n </k-accordion-panel>\n `;\n })}\n </k-accordion>\n `;\n }\n}\n\ncustomElements.define('admin-page-editor', PageEditor);\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{html}from"/kempo-ui/lit-all.min.js";import
|
|
1
|
+
import{html as e}from"/kempo-ui/lit-all.min.js";import t from"/kempo-ui/components/tableControls/TableControl.js";import o from"/kempo-ui/components/Dialog.js";import r from"/kempo-ui/components/Toast.js";import"/kempo-ui/components/Icon.js";export default class i extends t{static properties={maxWidth:{type:Number,attribute:"max-width"},sdkMethod:{type:String,attribute:"sdk-method"},primaryKey:{type:String,attribute:"primary-key"}};constructor(){super(),this.maxWidth=40,this.sdkMethod="deleteUsers",this.idField="id"}delete=()=>{this.record&&o.confirm("Are you sure you want to delete this record? This action cannot be undone.",async e=>{if(!e)return;const t=(await import("../../kempo/sdk.js"))[this.sdkMethod];if(!t)return void r.error("Delete method not found");const[o,i]=await t([this.record[this.primaryKey]]);o?r.error(o.msg||"Failed to delete record"):(this.table.deleteRecord(this.record),r.success("Record deleted successfully"))})};render(){return e`<button class="no-btn icon-btn" @click="${this.delete}">
|
|
2
2
|
<slot><k-icon name="delete"></k-icon></slot>
|
|
3
|
-
</button>`}}customElements.define("admin-table-row-delete",
|
|
3
|
+
</button>`}}customElements.define("admin-table-row-delete",i);
|
|
4
4
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\TableRowDelete.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"src\\admin\\components\\TableRowDelete.js","names":["html","TableControl","Dialog","Toast","TableRowDelete","static","maxWidth","type","Number","attribute","sdkMethod","String","primaryKey","constructor","super","this","idField","delete","record","confirm","async","confirmed","deleteMethod","import","error","result","msg","table","deleteRecord","success","render","customElements","define"],"sources":["0"],"sourcesContent":["import { html } from '/kempo-ui/lit-all.min.js';\r\nimport TableControl from '/kempo-ui/components/tableControls/TableControl.js';\r\nimport Dialog from '/kempo-ui/components/Dialog.js';\r\nimport Toast from '/kempo-ui/components/Toast.js';\r\nimport '/kempo-ui/components/Icon.js';\r\n\r\nexport default class TableRowDelete extends TableControl {\r\n static properties = {\r\n maxWidth: { type: Number, attribute: 'max-width' },\r\n sdkMethod: { type: String, attribute: 'sdk-method' },\r\n primaryKey: { type: String, attribute: 'primary-key' }\r\n };\r\n\r\n constructor(){\r\n super();\r\n this.maxWidth = 40;\r\n this.sdkMethod = 'deleteUsers';\r\n this.idField = 'id';\r\n }\r\n\r\n delete = () => {\r\n if(!this.record) return;\r\n\r\n Dialog.confirm('Are you sure you want to delete this record? This action cannot be undone.', async (confirmed) => {\r\n if(!confirmed) return;\r\n\r\n const module = await import('../../kempo/sdk.js');\r\n const deleteMethod = module[this.sdkMethod];\r\n\r\n if(!deleteMethod){\r\n Toast.error('Delete method not found');\r\n return;\r\n }\r\n\r\n const [error, result] = await deleteMethod([this.record[this.primaryKey]]);\r\n\r\n if(error){\r\n Toast.error(error.msg || 'Failed to delete record');\r\n return;\r\n }\r\n\r\n this.table.deleteRecord(this.record);\r\n Toast.success('Record deleted successfully');\r\n });\r\n };\r\n\r\n render(){\r\n return html`<button class=\"no-btn icon-btn\" @click=\"${this.delete}\">\r\n <slot><k-icon name=\"delete\"></k-icon></slot>\r\n </button>`;\r\n }\r\n}\r\n\r\ncustomElements.define('admin-table-row-delete', TableRowDelete);\r\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"src\\admin\\components\\TableRowDelete.js","names":["html","TableControl","Dialog","Toast","TableRowDelete","static","maxWidth","type","Number","attribute","sdkMethod","String","primaryKey","constructor","super","this","idField","delete","record","confirm","async","confirmed","deleteMethod","import","error","result","msg","table","deleteRecord","success","render","customElements","define"],"sources":["0"],"sourcesContent":["import { html } from '/kempo-ui/lit-all.min.js';\r\nimport TableControl from '/kempo-ui/components/tableControls/TableControl.js';\r\nimport Dialog from '/kempo-ui/components/Dialog.js';\r\nimport Toast from '/kempo-ui/components/Toast.js';\r\nimport '/kempo-ui/components/Icon.js';\r\n\r\nexport default class TableRowDelete extends TableControl {\r\n static properties = {\r\n maxWidth: { type: Number, attribute: 'max-width' },\r\n sdkMethod: { type: String, attribute: 'sdk-method' },\r\n primaryKey: { type: String, attribute: 'primary-key' }\r\n };\r\n\r\n constructor(){\r\n super();\r\n this.maxWidth = 40;\r\n this.sdkMethod = 'deleteUsers';\r\n this.idField = 'id';\r\n }\r\n\r\n delete = () => {\r\n if(!this.record) return;\r\n\r\n Dialog.confirm('Are you sure you want to delete this record? This action cannot be undone.', async (confirmed) => {\r\n if(!confirmed) return;\r\n\r\n const module = await import('../../kempo/sdk.js');\r\n const deleteMethod = module[this.sdkMethod];\r\n\r\n if(!deleteMethod){\r\n Toast.error('Delete method not found');\r\n return;\r\n }\r\n\r\n const [error, result] = await deleteMethod([this.record[this.primaryKey]]);\r\n\r\n if(error){\r\n Toast.error(error.msg || 'Failed to delete record');\r\n return;\r\n }\r\n\r\n this.table.deleteRecord(this.record);\r\n Toast.success('Record deleted successfully');\r\n });\r\n };\r\n\r\n render(){\r\n return html`<button class=\"no-btn icon-btn\" @click=\"${this.delete}\">\r\n <slot><k-icon name=\"delete\"></k-icon></slot>\r\n </button>`;\r\n }\r\n}\r\n\r\ncustomElements.define('admin-table-row-delete', TableRowDelete);\r\n"],"mappings":"eAASA,MAAY,kCACdC,MAAkB,4DAClBC,MAAY,wCACZC,MAAW,sCACX,8CAEQ,MAAMC,UAAuBH,EAC1CI,kBAAoB,CAClBC,SAAU,CAAEC,KAAMC,OAAQC,UAAW,aACrCC,UAAW,CAAEH,KAAMI,OAAQF,UAAW,cACtCG,WAAY,CAAEL,KAAMI,OAAQF,UAAW,gBAGzC,WAAAI,GACEC,QACAC,KAAKT,SAAW,GAChBS,KAAKL,UAAY,cACjBK,KAAKC,QAAU,IACjB,CAEAC,OAAS,KACHF,KAAKG,QAEThB,EAAOiB,QAAQ,6EAA8EC,MAAOC,IAClG,IAAIA,EAAW,OAEf,MACMC,SADeC,OAAO,uBACAR,KAAKL,WAEjC,IAAIY,EAEF,YADAnB,EAAMqB,MAAM,2BAId,MAAOA,EAAOC,SAAgBH,EAAa,CAACP,KAAKG,OAAOH,KAAKH,cAE1DY,EACDrB,EAAMqB,MAAMA,EAAME,KAAO,4BAI3BX,KAAKY,MAAMC,aAAab,KAAKG,QAC7Bf,EAAM0B,QAAQ,mCAIlB,MAAAC,GACE,OAAO9B,CAAI,2CAA2Ce,KAAKE;;cAG7D,EAGFc,eAAeC,OAAO,yBAA0B5B","ignoreList":[]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import t from"/kempo-ui/components/tableControls/TableControl.js";import{html as e}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Icon.js";export default class i extends t{static get properties(){return{urlPattern:{type:String,attribute:"url-pattern"},fieldName:{type:String,attribute:"field-name"}}}constructor(){super(),this.fieldName="id",this.maxWidth=40}navigate=()=>{if(!this.record)return;const t=this.record[this.fieldName];if(!t)return;const e=this.urlPattern.replace(`{${this.fieldName}}`,t);window.location.href=e};render(){return e`
|
|
2
2
|
<button class="no-btn icon-btn" @click="${this.navigate}">
|
|
3
3
|
<slot>
|
|
4
4
|
<k-icon name="visibility"></k-icon>
|
|
5
5
|
</slot>
|
|
6
6
|
</button>
|
|
7
|
-
`}}customElements.define("admin-table-row-link",
|
|
7
|
+
`}}customElements.define("admin-table-row-link",i);
|
|
8
8
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\TableRowLink.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"src\\admin\\components\\TableRowLink.js","names":["TableControl","html","TableRowLink","properties","urlPattern","type","String","attribute","fieldName","constructor","super","this","maxWidth","navigate","record","fieldValue","url","replace","window","location","href","render","customElements","define"],"sources":["0"],"sourcesContent":["import TableControl from '/kempo-ui/components/tableControls/TableControl.js';\r\nimport { html } from '/kempo-ui/lit-all.min.js';\r\nimport '/kempo-ui/components/Icon.js';\r\n\r\nexport default class TableRowLink extends TableControl {\r\n static get properties() {\r\n return {\r\n urlPattern: { type: String, attribute: 'url-pattern' },\r\n fieldName: { type: String, attribute: 'field-name' }\r\n };\r\n }\r\n\r\n constructor() {\r\n super();\r\n this.fieldName = 'id';\r\n this.maxWidth = 40;\r\n }\r\n\r\n navigate = () => {\r\n if(!this.record) return;\r\n\r\n const fieldValue = this.record[this.fieldName];\r\n if(!fieldValue) return;\r\n\r\n const url = this.urlPattern.replace(`{${this.fieldName}}`, fieldValue);\r\n window.location.href = url;\r\n };\r\n\r\n render() {\r\n return html`\r\n <button class=\"no-btn icon-btn\" @click=\"${this.navigate}\">\r\n <slot>\r\n <k-icon name=\"visibility\"></k-icon>\r\n </slot>\r\n </button>\r\n `;\r\n }\r\n}\r\n\r\ncustomElements.define('admin-table-row-link', TableRowLink);\r\n\r\n"],"mappings":"OAAOA,
|
|
1
|
+
{"version":3,"file":"src\\admin\\components\\TableRowLink.js","names":["TableControl","html","TableRowLink","properties","urlPattern","type","String","attribute","fieldName","constructor","super","this","maxWidth","navigate","record","fieldValue","url","replace","window","location","href","render","customElements","define"],"sources":["0"],"sourcesContent":["import TableControl from '/kempo-ui/components/tableControls/TableControl.js';\r\nimport { html } from '/kempo-ui/lit-all.min.js';\r\nimport '/kempo-ui/components/Icon.js';\r\n\r\nexport default class TableRowLink extends TableControl {\r\n static get properties() {\r\n return {\r\n urlPattern: { type: String, attribute: 'url-pattern' },\r\n fieldName: { type: String, attribute: 'field-name' }\r\n };\r\n }\r\n\r\n constructor() {\r\n super();\r\n this.fieldName = 'id';\r\n this.maxWidth = 40;\r\n }\r\n\r\n navigate = () => {\r\n if(!this.record) return;\r\n\r\n const fieldValue = this.record[this.fieldName];\r\n if(!fieldValue) return;\r\n\r\n const url = this.urlPattern.replace(`{${this.fieldName}}`, fieldValue);\r\n window.location.href = url;\r\n };\r\n\r\n render() {\r\n return html`\r\n <button class=\"no-btn icon-btn\" @click=\"${this.navigate}\">\r\n <slot>\r\n <k-icon name=\"visibility\"></k-icon>\r\n </slot>\r\n </button>\r\n `;\r\n }\r\n}\r\n\r\ncustomElements.define('admin-table-row-link', TableRowLink);\r\n\r\n"],"mappings":"OAAOA,MAAkB,oEAChBC,MAAY,iCACd,8CAEQ,MAAMC,UAAqBF,EACxC,qBAAWG,GACT,MAAO,CACLC,WAAY,CAAEC,KAAMC,OAAQC,UAAW,eACvCC,UAAW,CAAEH,KAAMC,OAAQC,UAAW,cAE1C,CAEA,WAAAE,GACEC,QACAC,KAAKH,UAAY,KACjBG,KAAKC,SAAW,EAClB,CAEAC,SAAW,KACT,IAAIF,KAAKG,OAAQ,OAEjB,MAAMC,EAAaJ,KAAKG,OAAOH,KAAKH,WACpC,IAAIO,EAAY,OAEhB,MAAMC,EAAML,KAAKP,WAAWa,QAAQ,IAAIN,KAAKH,aAAcO,GAC3DG,OAAOC,SAASC,KAAOJ,GAGzB,MAAAK,GACE,OAAOpB,CAAI;gDACiCU,KAAKE;;;;;KAMnD,EAGFS,eAAeC,OAAO,uBAAwBrB","ignoreList":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"/kempo-ui/components/ShadowComponent.js";import{html as t}from"/kempo-ui/lit-all.min.js";import{getTemplate as a,updateTemplate as i,deleteTemplates as l,moveTemplate as n,createTemplate as o,listDirectories as s}from"/kempo/sdk.js";import r from"/kempo-ui/components/Dialog.js";import d from"/kempo-ui/components/Toast.js";import"/kempo-ui/components/Icon.js";import"/kempo-ui/components/Accordion.js";import"/kempo-ui/components/CodeEditor.js";export default class c extends e{static properties={loading:{state:!0},error:{state:!0},saving:{state:!0},template:{state:!0}};constructor(){super(),this.loading=!0,this.error=!1,this.saving=!1,this.template=null,this.file=""}async connectedCallback(){super.connectedCallback(),document.addEventListener("keydown",this.handleKeyDown);const e=new URLSearchParams(window.location.search).get("template");if(this.file=e+".template.html",!this.file||".template.html"===this.file)return this.loading=!1,void(this.error=!0);const[t,i]=await a(this.file);this.loading=!1,t?this.error=!0:(this.template=i,document.title=`Edit: ${i.name} - Admin`)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener("keydown",this.handleKeyDown)}getFormState(){const e=this.shadowRoot;return{name:e.querySelector("#metaName")?.value??this.template.name,markup:e.querySelector("#templateEditor")?.getValue()??this.template.markup}}handleKeyDown=e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),this.saving||this.template?.locked||this.handleSave())};handleDelete=()=>{r.confirm("Delete this template? This action cannot be undone.",async e=>{if(!e)return;const[t]=await l([this.file]);t?d.error(t.msg||"Failed to delete template"):(d.success("Template deleted"),setTimeout(()=>{window.location.href="/admin/content/templates"},1e3))})};handleMove=async()=>{const[,e]=await s(),a=e?.directories||["."],l=this.file.replace(/\.template\.html$/,"").split("/"),o=l.pop(),c=l.join("/")||".",m=e=>(e||"/").replace(/^\//,"").replace(/\/$/,"")||".",p="."===c?"/":"/"+c,v=r.create(t`
|
|
2
2
|
<div class="p">
|
|
3
3
|
<div class="p r mb bc-warning tc-warning">
|
|
4
4
|
<k-icon name="warning"></k-icon>
|
|
@@ -6,24 +6,24 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
6
6
|
</div>
|
|
7
7
|
<div class="mb">
|
|
8
8
|
<label class="d-b mb-sm"><strong>Location</strong></label>
|
|
9
|
-
<input type="text" id="dlg-move-dir" class="full" list="dlg-move-dir-list" .value="${
|
|
9
|
+
<input type="text" id="dlg-move-dir" class="full" list="dlg-move-dir-list" .value="${p}" placeholder="/">
|
|
10
10
|
<datalist id="dlg-move-dir-list">
|
|
11
|
-
${
|
|
11
|
+
${a.map(e=>t`<option value="${"."===e?"/":"/"+e}">`)}
|
|
12
12
|
</datalist>
|
|
13
13
|
</div>
|
|
14
14
|
<div>
|
|
15
15
|
<label class="d-b mb-sm"><strong>Template Name</strong></label>
|
|
16
|
-
<input type="text" id="dlg-move-name" class="full" .value="${
|
|
16
|
+
<input type="text" id="dlg-move-name" class="full" .value="${o}">
|
|
17
17
|
<p class="mt-sm muted"><small id="dlg-move-preview"></small></p>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
|
-
`,{title:"Move Template",confirmText:"Save and Move Template",confirmAction:async()=>{const e=
|
|
20
|
+
`,{title:"Move Template",confirmText:"Save and Move Template",confirmAction:async()=>{const e=v.querySelector("#dlg-move-dir").value.trim()||"/",t=v.querySelector("#dlg-move-name").value.trim();if(!t)return void d.error("Template name is required");if(!/^\/[a-zA-Z0-9_\-\[\]\/]*$/.test(a=e)||a.includes("..")||a.includes("//"))return void d.error("Invalid directory path");var a;const l=m(e),o="."===l?`${t}.template.html`:`${l}/${t}.template.html`;this.saving=!0;const[s]=await i({file:this.file,...this.getFormState()});if(this.saving=!1,s)return void d.error(s.msg||"Failed to save template");const[r,c]=await n({file:this.file,newFile:o});if(r)return void d.error(r.msg||"Failed to move template");d.success("Template saved and moved");const p=c.file.replace(/\.template\.html$/,"");setTimeout(()=>{window.location.href=`/admin/content/templates/edit?template=${encodeURIComponent(p)}`},1e3)},cancelText:"Cancel"}),u=()=>{const e=m(v.querySelector("#dlg-move-dir").value||"/"),t=v.querySelector("#dlg-move-name").value,a="."===e?"/":"/"+e+"/";v.querySelector("#dlg-move-preview").textContent=t?a+t:""};v.querySelector("#dlg-move-dir").addEventListener("input",u),v.querySelector("#dlg-move-name").addEventListener("input",u),u()};handleCopy=async()=>{const[,e]=await s(),a=e?.directories||["."],i=this.file.replace(/\.template\.html$/,"").split("/"),l=i.pop(),n=i.join("/")||".",c=e=>(e||"/").replace(/^\//,"").replace(/\/$/,"")||".",m="."===n?"/":"/"+n,p=r.create(t`
|
|
21
21
|
<div class="p">
|
|
22
22
|
<div class="mb">
|
|
23
23
|
<label class="d-b mb-sm"><strong>Location</strong></label>
|
|
24
|
-
<input type="text" id="dlg-copy-dir" class="full" list="dlg-copy-dir-list" .value="${
|
|
24
|
+
<input type="text" id="dlg-copy-dir" class="full" list="dlg-copy-dir-list" .value="${m}" placeholder="/">
|
|
25
25
|
<datalist id="dlg-copy-dir-list">
|
|
26
|
-
${
|
|
26
|
+
${a.map(e=>t`<option value="${"."===e?"/":"/"+e}">`)}
|
|
27
27
|
</datalist>
|
|
28
28
|
</div>
|
|
29
29
|
<div>
|
|
@@ -32,15 +32,15 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
32
32
|
<p class="mt-sm muted"><small id="dlg-copy-preview"></small></p>
|
|
33
33
|
</div>
|
|
34
34
|
</div>
|
|
35
|
-
`,{title:"Copy Template",confirmText:"Copy Template",confirmAction:async()=>{const e=
|
|
35
|
+
`,{title:"Copy Template",confirmText:"Copy Template",confirmAction:async()=>{const e=p.querySelector("#dlg-copy-dir").value.trim()||"/",t=p.querySelector("#dlg-copy-name").value.trim();if(!t)return void d.error("Template name is required");if(!/^\/[a-zA-Z0-9_\-\[\]\/]*$/.test(a=e)||a.includes("..")||a.includes("//"))return void d.error("Invalid directory path");var a;const i=c(e),[l,n]=await o({directory:"."===i?"":i,name:t,copyFrom:this.file});l?d.error(l.msg||"Failed to copy template"):(d.success("Template copied"),setTimeout(()=>{window.location.href=`/admin/content/templates/edit?template=${encodeURIComponent(n.file.replace(/\.template\.html$/,""))}`},1e3))},cancelText:"Cancel"}),v=()=>{const e=c(p.querySelector("#dlg-copy-dir").value||"/"),t=p.querySelector("#dlg-copy-name").value,a="."===e?"/":"/"+e+"/";p.querySelector("#dlg-copy-preview").textContent=t?a+t:""};p.querySelector("#dlg-copy-dir").addEventListener("input",v),p.querySelector("#dlg-copy-name").addEventListener("input",v),v()};handleReset=()=>{r.confirm("Reset this template? Any unsaved changes will be lost.",e=>{e&&(this.template={...this.template})})};handleSave=async()=>{this.saving=!0;const e=this.getFormState(),[t,a]=await i({file:this.file,markup:e.markup});if(this.saving=!1,t)return void d.error(t.msg||"Failed to save template");this.template={...this.template,markup:e.markup,updatedAt:a.updatedAt};const l=this.file.replace(/\.template\.html$/,"").split("/"),o=l.pop(),s=l.join("/"),r=e.name.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"");if(r&&r!==o){const e=s?`${s}/${r}.template.html`:`${r}.template.html`,[t,a]=await n({file:this.file,newFile:e});if(t)return void d.error(t.msg||"Failed to rename template file");d.success("Template saved and renamed");const i=a.file.replace(/\.template\.html$/,"");return void setTimeout(()=>{window.location.href=`/admin/content/templates/edit?template=${encodeURIComponent(i)}`},1e3)}document.title=`Edit: ${template.name} - Admin`,d.success("Template saved")};render(){if(this.loading)return t`<div>Loading template...</div>`;if(this.error)return t`
|
|
36
36
|
<div>
|
|
37
37
|
<p>Template not found.</p>
|
|
38
38
|
<a href="/admin/content/templates">Back to Templates</a>
|
|
39
39
|
</div>
|
|
40
|
-
`;const{template:e}=this,
|
|
41
|
-
${
|
|
40
|
+
`;const{template:e}=this,a="custom"===e.owner,i=e.locked;return t`
|
|
41
|
+
${i?t`
|
|
42
42
|
<div class="p r mb bc-warning tc-warning">
|
|
43
|
-
<k-icon name="lock"></k-icon> This template is locked and cannot be edited.
|
|
43
|
+
<k-icon name="lock"></k-icon> This template is locked and cannot be edited. ${"custom"===e.owner?"Locked by developer":`Managed by: ${e.owner||"external system"}`}
|
|
44
44
|
</div>
|
|
45
45
|
`:""}
|
|
46
46
|
<div class="d-f mb">
|
|
@@ -50,7 +50,7 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
50
50
|
</a>
|
|
51
51
|
</div>
|
|
52
52
|
<div class="flex"></div>
|
|
53
|
-
${
|
|
53
|
+
${a&&!i?t`
|
|
54
54
|
<div class="btn-grp mrh mb">
|
|
55
55
|
<button class="danger" @click="${this.handleDelete}"><k-icon name="delete"></k-icon> Delete</button>
|
|
56
56
|
<button @click="${this.handleMove}"><k-icon name="drive_file_move"></k-icon> Move Template</button>
|
|
@@ -59,7 +59,7 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
59
59
|
<div class="btn-grp mrh mb">
|
|
60
60
|
<button @click="${this.handleCopy}"><k-icon name="content_copy"></k-icon> Copy Template</button>
|
|
61
61
|
</div>
|
|
62
|
-
${
|
|
62
|
+
${i?"":t`
|
|
63
63
|
<div class="btn-grp mrh mb">
|
|
64
64
|
<button @click="${this.handleReset}"><k-icon name="restart_alt"></k-icon> Reset</button>
|
|
65
65
|
<button id="saveBtn" class="primary" ?disabled="${this.saving}" @click="${this.handleSave}">
|
|
@@ -77,16 +77,16 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
77
77
|
<div class="p">
|
|
78
78
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
79
79
|
<label style="min-width: 120px;"><strong>Name</strong></label>
|
|
80
|
-
<input type="text" id="metaName" class="flex" .value="${e.name||""}" ?disabled="${!
|
|
80
|
+
<input type="text" id="metaName" class="flex" .value="${e.name||""}" ?disabled="${!a||i}">
|
|
81
81
|
</div>
|
|
82
82
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
83
83
|
<label style="min-width: 120px;"><strong>Directory</strong></label>
|
|
84
84
|
<span class="muted">${(()=>{const e=this.file.replace(/\.template\.html$/,"").split("/");return e.pop(),e.length?e.join("/"):"/"})()}</span>
|
|
85
|
-
${
|
|
85
|
+
${a&&!i?t`
|
|
86
86
|
<button class="icon-btn no-btn ph" title="Move Template" @click="${this.handleMove}"><k-icon name="drive_file_move"></k-icon></button>
|
|
87
87
|
`:""}
|
|
88
88
|
</div>
|
|
89
|
-
${e.author?
|
|
89
|
+
${e.author?t`
|
|
90
90
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
91
91
|
<label style="min-width: 120px;"><strong>Author</strong></label>
|
|
92
92
|
<span class="muted">${e.author}</span>
|
|
@@ -116,10 +116,10 @@ import ShadowComponent from"/kempo-ui/components/ShadowComponent.js";import{html
|
|
|
116
116
|
language="html"
|
|
117
117
|
.value="${e.markup||""}"
|
|
118
118
|
style="height: 70vh; min-height: 400px;"
|
|
119
|
-
?disabled="${
|
|
119
|
+
?disabled="${i}"
|
|
120
120
|
></k-code-editor>
|
|
121
121
|
</div>
|
|
122
122
|
</k-accordion-panel>
|
|
123
123
|
</k-accordion>
|
|
124
|
-
`}}customElements.define("admin-template-editor",
|
|
124
|
+
`}}customElements.define("admin-template-editor",c);
|
|
125
125
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\TemplateEditor.js.map
|