kempo 4.2.15 → 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/app-public/account/split2.template.html +1 -0
- package/app-public/test.page.html +1 -0
- 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 -0
- package/dist/kempo/api/fragments/lock/PUT.js.map +1 -0
- 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 -0
- package/dist/kempo/api/globals/lock/PUT.js.map +1 -0
- 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 -0
- package/dist/kempo/api/pages/lock/PUT.js.map +1 -0
- package/dist/kempo/api/pages/search/GET.js +2 -0
- package/dist/kempo/api/pages/search/GET.js.map +1 -0
- 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 -0
- package/dist/kempo/api/templates/lock/PUT.js.map +1 -0
- 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 +3 -3
- package/server/sdk.js +5 -0
- package/server/utils/extensions/installExtension.js +19 -13
- package/server/utils/extensions/uninstallExtension.js +6 -10
- package/server/utils/fragments/createFragment.js +5 -1
- package/server/utils/fragments/deleteFragment.js +2 -3
- package/server/utils/fragments/disableFragment.js +0 -3
- package/server/utils/fragments/getFragmentMetadata.js +32 -0
- package/server/utils/fragments/moveFragment.js +0 -3
- package/server/utils/fragments/setFragmentLocked.js +48 -0
- package/server/utils/fragments/updateFragment.js +3 -3
- package/server/utils/fs/isResourceLocked.js +22 -0
- package/server/utils/global-content/createGlobalContent.js +3 -0
- package/server/utils/global-content/deleteGlobalContent.js +3 -3
- package/server/utils/global-content/getGlobalContentMetadata.js +37 -0
- package/server/utils/global-content/setGlobalContentLocked.js +44 -0
- package/server/utils/global-content/updateGlobalContent.js +3 -4
- package/server/utils/pages/createPage.js +19 -7
- package/server/utils/pages/deletePage.js +4 -13
- package/server/utils/pages/disablePage.js +0 -3
- package/server/utils/pages/getPage.js +7 -0
- package/server/utils/pages/getPageMetadata.js +32 -0
- package/server/utils/pages/listPages.js +11 -3
- package/server/utils/pages/movePage.js +0 -3
- package/server/utils/pages/searchByMetadata.js +61 -0
- package/server/utils/pages/setPageLocked.js +48 -0
- package/server/utils/pages/updatePage.js +19 -3
- package/server/utils/templates/createTemplate.js +5 -1
- package/server/utils/templates/deleteTemplate.js +2 -3
- package/server/utils/templates/disableTemplate.js +0 -3
- package/server/utils/templates/getTemplateMetadata.js +32 -0
- package/server/utils/templates/moveTemplate.js +0 -3
- package/server/utils/templates/setTemplateLocked.js +48 -0
- package/server/utils/templates/updateTemplate.js +3 -3
- 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
|
@@ -3,6 +3,7 @@ import { join } from 'path';
|
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import { GLOBAL_FILE, parseEntries } from './helpers.js';
|
|
6
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
6
7
|
|
|
7
8
|
const serializeEntries = entries => entries.map(e => {
|
|
8
9
|
const attrs = [
|
|
@@ -54,5 +55,7 @@ export default async ({ rootDir, name, location, priority, author }) => {
|
|
|
54
55
|
entries.push(entry);
|
|
55
56
|
await writeFile(filePath, serializeEntries(entries) + '\n', 'utf-8');
|
|
56
57
|
|
|
58
|
+
await triggerHook('globalContent:created', { id: entry.id, name, location });
|
|
59
|
+
|
|
57
60
|
return [null, entry];
|
|
58
61
|
};
|
|
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'fs/promises';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { GLOBAL_FILE, parseEntries } from './helpers.js';
|
|
5
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
5
6
|
|
|
6
7
|
const serializeEntries = entries => entries.map(e => {
|
|
7
8
|
const attrs = [
|
|
@@ -37,9 +38,6 @@ export default async ({ rootDir, ids }) => {
|
|
|
37
38
|
|
|
38
39
|
for(const entry of entries){
|
|
39
40
|
if(!idSet.has(entry.id)) continue;
|
|
40
|
-
if(entry.locked){
|
|
41
|
-
return [{ code: 403, msg: `Cannot delete locked global content: ${entry.name}` }, null];
|
|
42
|
-
}
|
|
43
41
|
if(entry.owner && entry.owner !== 'custom'){
|
|
44
42
|
return [{ code: 403, msg: `Cannot delete global content: ${entry.name}` }, null];
|
|
45
43
|
}
|
|
@@ -48,5 +46,7 @@ export default async ({ rootDir, ids }) => {
|
|
|
48
46
|
const remaining = entries.filter(e => !idSet.has(e.id));
|
|
49
47
|
await writeFile(filePath, remaining.length ? serializeEntries(remaining) + '\n' : '', 'utf-8');
|
|
50
48
|
|
|
49
|
+
for(const id of ids) await triggerHook('globalContent:deleted', { id });
|
|
50
|
+
|
|
51
51
|
return [null, { deleted: ids.length }];
|
|
52
52
|
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { GLOBAL_FILE, parseEntries } from './helpers.js';
|
|
5
|
+
|
|
6
|
+
export default async ({ rootDir, id }) => {
|
|
7
|
+
if(!rootDir){
|
|
8
|
+
return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
9
|
+
}
|
|
10
|
+
if(!id){
|
|
11
|
+
return [{ code: 400, msg: 'Global content ID is required' }, null];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const filePath = join(rootDir, GLOBAL_FILE);
|
|
15
|
+
if(!existsSync(filePath)){
|
|
16
|
+
return [{ code: 404, msg: 'Global content not found' }, null];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
20
|
+
const entries = parseEntries(raw);
|
|
21
|
+
const entry = entries.find(e => e.id === id);
|
|
22
|
+
|
|
23
|
+
if(!entry){
|
|
24
|
+
return [{ code: 404, msg: 'Global content not found' }, null];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return [null, {
|
|
28
|
+
name: entry.name,
|
|
29
|
+
owner: entry.owner,
|
|
30
|
+
locked: entry.locked,
|
|
31
|
+
author: entry.author,
|
|
32
|
+
createdAt: entry.createdAt,
|
|
33
|
+
updatedAt: entry.updatedAt,
|
|
34
|
+
location: entry.location,
|
|
35
|
+
priority: entry.priority
|
|
36
|
+
}];
|
|
37
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { GLOBAL_FILE, parseEntries } from './helpers.js';
|
|
5
|
+
|
|
6
|
+
const serializeEntries = entries => entries.map(e => {
|
|
7
|
+
const attrs = [
|
|
8
|
+
`id="${e.id}"`,
|
|
9
|
+
`name="${e.name}"`,
|
|
10
|
+
`owner="${e.owner}"`,
|
|
11
|
+
`author="${e.author}"`,
|
|
12
|
+
`created="${e.createdAt}"`,
|
|
13
|
+
`updated="${e.updatedAt}"`,
|
|
14
|
+
`locked="${e.locked}"`,
|
|
15
|
+
`location="${e.location}"`,
|
|
16
|
+
`priority="${e.priority}"`
|
|
17
|
+
].join(' ');
|
|
18
|
+
return `<content ${attrs}>\n${e.markup}\n</content>`;
|
|
19
|
+
}).join('\n');
|
|
20
|
+
|
|
21
|
+
export default async ({ rootDir, id, locked }) => {
|
|
22
|
+
if(!rootDir) return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
23
|
+
if(!id) return [{ code: 400, msg: 'Global content ID is required' }, null];
|
|
24
|
+
if(typeof locked !== 'boolean') return [{ code: 400, msg: 'locked must be a boolean' }, null];
|
|
25
|
+
|
|
26
|
+
const filePath = join(rootDir, GLOBAL_FILE);
|
|
27
|
+
if(!existsSync(filePath)){
|
|
28
|
+
return [{ code: 404, msg: 'Global content not found' }, null];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
32
|
+
const entries = parseEntries(raw);
|
|
33
|
+
const idx = entries.findIndex(e => e.id === id);
|
|
34
|
+
|
|
35
|
+
if(idx === -1){
|
|
36
|
+
return [{ code: 404, msg: 'Global content not found' }, null];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
entries[idx].locked = locked;
|
|
40
|
+
|
|
41
|
+
await writeFile(filePath, serializeEntries(entries) + '\n', 'utf-8');
|
|
42
|
+
|
|
43
|
+
return [null, { id, locked }];
|
|
44
|
+
};
|
|
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'fs/promises';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { GLOBAL_FILE, parseEntries } from './helpers.js';
|
|
5
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
5
6
|
|
|
6
7
|
const serializeEntries = entries => entries.map(e => {
|
|
7
8
|
const attrs = [
|
|
@@ -39,10 +40,6 @@ export default async ({ rootDir, id, name, location, priority, markup }) => {
|
|
|
39
40
|
return [{ code: 404, msg: 'Global content not found' }, null];
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
if(entries[idx].locked){
|
|
43
|
-
return [{ code: 403, msg: 'This global content is locked and cannot be edited' }, null];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
43
|
const now = new Date().toISOString();
|
|
47
44
|
if(name !== undefined) entries[idx].name = name;
|
|
48
45
|
if(location !== undefined) entries[idx].location = location;
|
|
@@ -52,5 +49,7 @@ export default async ({ rootDir, id, name, location, priority, markup }) => {
|
|
|
52
49
|
|
|
53
50
|
await writeFile(filePath, serializeEntries(entries) + '\n', 'utf-8');
|
|
54
51
|
|
|
52
|
+
await triggerHook('globalContent:updated', { id, updatedAt: now });
|
|
53
|
+
|
|
55
54
|
return [null, { id, updatedAt: now }];
|
|
56
55
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { writeFile, mkdir } from 'fs/promises';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
5
|
+
|
|
6
|
+
const RESERVED_KEYS = new Set(['owner', 'name', 'author', 'created', 'updated', 'locked', 'title', 'description']);
|
|
4
7
|
|
|
5
8
|
const slugify = name => name
|
|
6
9
|
.toLowerCase()
|
|
@@ -10,7 +13,7 @@ const slugify = name => name
|
|
|
10
13
|
.replace(/-+/g, '-')
|
|
11
14
|
.replace(/^-|-$/g, '');
|
|
12
15
|
|
|
13
|
-
export default async ({ rootDir, directory, name, template, author }) => {
|
|
16
|
+
export default async ({ rootDir, directory, name, template, author, locked = false, extraMetadata = {} }) => {
|
|
14
17
|
if(!rootDir){
|
|
15
18
|
return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
16
19
|
}
|
|
@@ -34,18 +37,24 @@ export default async ({ rootDir, directory, name, template, author }) => {
|
|
|
34
37
|
return [{ code: 409, msg: 'A page with this name already exists in this location' }, null];
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
const conflictingKeys = Object.keys(extraMetadata).filter(k => RESERVED_KEYS.has(k));
|
|
41
|
+
if(conflictingKeys.length){
|
|
42
|
+
return [{ code: 400, msg: `Cannot use reserved metadata keys: ${conflictingKeys.join(', ')}` }, null];
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
const templateName = template || 'default';
|
|
38
46
|
|
|
39
47
|
const now = new Date().toISOString();
|
|
40
|
-
const
|
|
41
|
-
'<!--',
|
|
48
|
+
const defaultLines = [
|
|
42
49
|
` owner: custom`,
|
|
43
50
|
` name: ${name}`,
|
|
44
51
|
` author: ${author || ''}`,
|
|
45
52
|
` created: ${now}`,
|
|
46
|
-
` updated: ${now}
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
` updated: ${now}`
|
|
54
|
+
];
|
|
55
|
+
if(locked) defaultLines.push(` locked: true`);
|
|
56
|
+
const extraLines = Object.entries(extraMetadata).map(([k, v]) => ` ${k}: ${v}`);
|
|
57
|
+
const frontmatter = ['<!--', ...defaultLines, ...extraLines, '-->'].join('\n');
|
|
49
58
|
|
|
50
59
|
const pageContent = `${frontmatter}\n<page template="${templateName}" title="${name}">\n</page>\n`;
|
|
51
60
|
|
|
@@ -55,6 +64,9 @@ export default async ({ rootDir, directory, name, template, author }) => {
|
|
|
55
64
|
|
|
56
65
|
const relPath = (dir ? `${dir}/${slug}` : slug);
|
|
57
66
|
const url = '/' + relPath;
|
|
67
|
+
const result = { file: `${relPath}.page.html`, url, title: name, slug };
|
|
68
|
+
|
|
69
|
+
await triggerHook('page:created', { file: result.file, url, name, author, extraMetadata });
|
|
58
70
|
|
|
59
|
-
return [null,
|
|
71
|
+
return [null, result];
|
|
60
72
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { unlink } from 'fs/promises';
|
|
2
2
|
import { join, normalize, sep } from 'path';
|
|
3
|
-
import
|
|
3
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
4
4
|
|
|
5
5
|
export default async ({ rootDir, files }) => {
|
|
6
6
|
if(!rootDir) return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
@@ -13,22 +13,13 @@ export default async ({ rootDir, files }) => {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const fullPath = join(rootDir, normalized);
|
|
16
|
-
let content;
|
|
17
16
|
try {
|
|
18
|
-
|
|
17
|
+
await unlink(fullPath);
|
|
19
18
|
} catch {
|
|
20
19
|
continue;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
if(meta.locked === 'true'){
|
|
25
|
-
return [{ code: 403, msg: `Cannot delete locked page: ${file}` }, null];
|
|
26
|
-
}
|
|
27
|
-
if(meta.owner && meta.owner !== 'custom'){
|
|
28
|
-
return [{ code: 403, msg: `Cannot delete page: ${file}` }, null];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
await unlink(fullPath);
|
|
22
|
+
await triggerHook('page:deleted', { file });
|
|
32
23
|
}
|
|
33
24
|
|
|
34
25
|
return [null, { deleted: files.length }];
|
|
@@ -23,9 +23,6 @@ export default async ({ rootDir, file }) => {
|
|
|
23
23
|
|
|
24
24
|
const content = await readFile(fullPath, 'utf-8');
|
|
25
25
|
const meta = parseFrontmatter(content);
|
|
26
|
-
if(meta.locked === 'true'){
|
|
27
|
-
return [{ code: 403, msg: 'Cannot disable a locked page' }, null];
|
|
28
|
-
}
|
|
29
26
|
|
|
30
27
|
const disabledPath = fullPath.replace(/\.page\.html$/, '.page-disabled.html');
|
|
31
28
|
if(existsSync(disabledPath)){
|
|
@@ -3,6 +3,8 @@ import { join } from 'path';
|
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import parseFrontmatter from '../fs/parseFrontmatter.js';
|
|
5
5
|
|
|
6
|
+
const RESERVED_KEYS = new Set(['owner', 'name', 'author', 'created', 'updated', 'locked', 'title', 'description']);
|
|
7
|
+
|
|
6
8
|
const parseContents = content => {
|
|
7
9
|
const blocks = [];
|
|
8
10
|
const regex = /<content(?:\s+location="([^"]*)")?\s*>([\s\S]*?)<\/content>/g;
|
|
@@ -37,6 +39,10 @@ export default async ({ rootDir, file }) => {
|
|
|
37
39
|
const templateMatch = raw.match(/<page\s[^>]*template="([^"]*)"/);
|
|
38
40
|
const contents = parseContents(raw);
|
|
39
41
|
|
|
42
|
+
const extraMetadata = Object.fromEntries(
|
|
43
|
+
Object.entries(meta).filter(([k]) => !RESERVED_KEYS.has(k))
|
|
44
|
+
);
|
|
45
|
+
|
|
40
46
|
return [null, {
|
|
41
47
|
file: safePath,
|
|
42
48
|
name: meta.name || '',
|
|
@@ -48,6 +54,7 @@ export default async ({ rootDir, file }) => {
|
|
|
48
54
|
description: meta.description || '',
|
|
49
55
|
createdAt: meta.created || '',
|
|
50
56
|
updatedAt: meta.updated || '',
|
|
57
|
+
extraMetadata,
|
|
51
58
|
contents
|
|
52
59
|
}];
|
|
53
60
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import parseFrontmatter from '../fs/parseFrontmatter.js';
|
|
5
|
+
|
|
6
|
+
export default async ({ rootDir, file }) => {
|
|
7
|
+
if(!rootDir){
|
|
8
|
+
return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
9
|
+
}
|
|
10
|
+
if(!file){
|
|
11
|
+
return [{ code: 400, msg: 'File path is required' }, null];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const safePath = file.replace(/\.\./g, '').replace(/^\//, '');
|
|
15
|
+
const fullPath = join(rootDir, safePath);
|
|
16
|
+
|
|
17
|
+
if(!existsSync(fullPath)){
|
|
18
|
+
return [{ code: 404, msg: 'Page not found' }, null];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const raw = await readFile(fullPath, 'utf-8');
|
|
22
|
+
const meta = parseFrontmatter(raw);
|
|
23
|
+
|
|
24
|
+
return [null, {
|
|
25
|
+
name: meta.name,
|
|
26
|
+
owner: meta.owner,
|
|
27
|
+
locked: meta.locked === 'true',
|
|
28
|
+
author: meta.author,
|
|
29
|
+
created: meta.created,
|
|
30
|
+
updated: meta.updated
|
|
31
|
+
}];
|
|
32
|
+
};
|
|
@@ -3,7 +3,9 @@ import { relative } from 'path';
|
|
|
3
3
|
import scanDir from '../fs/scanDir.js';
|
|
4
4
|
import parseFrontmatter from '../fs/parseFrontmatter.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const RESERVED_KEYS = new Set(['owner', 'name', 'author', 'created', 'updated', 'locked', 'title', 'description']);
|
|
7
|
+
|
|
8
|
+
export default async ({ rootDir, owner }) => {
|
|
7
9
|
if(!rootDir){
|
|
8
10
|
return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
9
11
|
}
|
|
@@ -20,6 +22,9 @@ export default async ({ rootDir }) => {
|
|
|
20
22
|
.replace(/\.page-disabled\.html$/, '')
|
|
21
23
|
.replace(/\.page\.html$/, '')
|
|
22
24
|
.replace(/(^|\/)index$/, '$1');
|
|
25
|
+
const extraMetadata = Object.fromEntries(
|
|
26
|
+
Object.entries(meta).filter(([k]) => !RESERVED_KEYS.has(k))
|
|
27
|
+
);
|
|
23
28
|
return {
|
|
24
29
|
file: relPath,
|
|
25
30
|
url: urlPath || '/',
|
|
@@ -31,10 +36,13 @@ export default async ({ rootDir }) => {
|
|
|
31
36
|
author: meta.author || '',
|
|
32
37
|
description: meta.description || '',
|
|
33
38
|
createdAt: meta.created || '',
|
|
34
|
-
updatedAt: meta.updated || ''
|
|
39
|
+
updatedAt: meta.updated || '',
|
|
40
|
+
extraMetadata
|
|
35
41
|
};
|
|
36
42
|
});
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
const filtered = owner ? pages.filter(page => page.owner === owner) : pages;
|
|
45
|
+
|
|
46
|
+
return [null, { pages: filtered, total: filtered.length }];
|
|
39
47
|
};
|
|
40
48
|
|
|
@@ -28,9 +28,6 @@ export default async ({ rootDir, file, newFile }) => {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const meta = parseFrontmatter(content);
|
|
31
|
-
if(meta.locked === 'true'){
|
|
32
|
-
return [{ code: 403, msg: 'Cannot move a locked page' }, null];
|
|
33
|
-
}
|
|
34
31
|
if(meta.owner && meta.owner !== 'custom'){
|
|
35
32
|
return [{ code: 403, msg: 'Cannot move system pages' }, null];
|
|
36
33
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { relative } from 'path';
|
|
3
|
+
import scanDir from '../fs/scanDir.js';
|
|
4
|
+
import parseFrontmatter from '../fs/parseFrontmatter.js';
|
|
5
|
+
|
|
6
|
+
const RESERVED_KEYS = new Set(['owner', 'name', 'author', 'created', 'updated', 'locked', 'title', 'description']);
|
|
7
|
+
|
|
8
|
+
export default async ({ rootDir, query = {} }) => {
|
|
9
|
+
if(!rootDir){
|
|
10
|
+
return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
11
|
+
}
|
|
12
|
+
if(!Object.keys(query).length){
|
|
13
|
+
return [{ code: 400, msg: 'Search query must have at least one key' }, null];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const queryKeys = Object.keys(query);
|
|
17
|
+
const reservedConflicts = queryKeys.filter(k => RESERVED_KEYS.has(k));
|
|
18
|
+
if(reservedConflicts.length){
|
|
19
|
+
return [{ code: 400, msg: `Cannot search reserved metadata keys: ${reservedConflicts.join(', ')}` }, null];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pages = await scanDir(rootDir, rootDir, async (fullPath, entry, root) => {
|
|
23
|
+
if(entry.isDirectory() || !entry.name.endsWith('.page.html')) return null;
|
|
24
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
25
|
+
const meta = parseFrontmatter(content);
|
|
26
|
+
|
|
27
|
+
const matches = queryKeys.every(k => {
|
|
28
|
+
const val = meta[k];
|
|
29
|
+
if(val === undefined) return false;
|
|
30
|
+
const expected = String(query[k]);
|
|
31
|
+
return val === expected;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if(!matches) return null;
|
|
35
|
+
|
|
36
|
+
const relPath = relative(root, fullPath).replace(/\\/g, '/');
|
|
37
|
+
const urlPath = '/' + relPath
|
|
38
|
+
.replace(/\.page\.html$/, '')
|
|
39
|
+
.replace(/(^|\/)index$/, '$1');
|
|
40
|
+
|
|
41
|
+
const extraMetadata = Object.fromEntries(
|
|
42
|
+
Object.entries(meta).filter(([k]) => !RESERVED_KEYS.has(k))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
file: relPath,
|
|
47
|
+
url: urlPath || '/',
|
|
48
|
+
name: meta.name || meta.title || '',
|
|
49
|
+
title: meta.title || '',
|
|
50
|
+
owner: meta.owner || 'custom',
|
|
51
|
+
locked: meta.locked === 'true',
|
|
52
|
+
author: meta.author || '',
|
|
53
|
+
description: meta.description || '',
|
|
54
|
+
createdAt: meta.created || '',
|
|
55
|
+
updatedAt: meta.updated || '',
|
|
56
|
+
extraMetadata
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return [null, { pages, total: pages.length }];
|
|
61
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { join, normalize, sep } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
export default async ({ rootDir, file, locked }) => {
|
|
6
|
+
if(!rootDir) return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
7
|
+
if(!file) return [{ code: 400, msg: 'File path is required' }, null];
|
|
8
|
+
if(typeof locked !== 'boolean') return [{ code: 400, msg: 'locked must be a boolean' }, null];
|
|
9
|
+
|
|
10
|
+
const normalized = normalize(file);
|
|
11
|
+
if(normalized.startsWith('..') || normalized.includes('..' + sep)){
|
|
12
|
+
return [{ code: 400, msg: 'Invalid file path' }, null];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const fullPath = join(rootDir, normalized);
|
|
16
|
+
if(!existsSync(fullPath)){
|
|
17
|
+
return [{ code: 404, msg: 'Page not found' }, null];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const raw = await readFile(fullPath, 'utf-8');
|
|
21
|
+
const frontmatterMatch = raw.match(/^<!--\s*\n([\s\S]*?)\n\s*-->/);
|
|
22
|
+
const existingMeta = {};
|
|
23
|
+
|
|
24
|
+
if(frontmatterMatch){
|
|
25
|
+
for(const line of frontmatterMatch[1].split('\n')){
|
|
26
|
+
const idx = line.indexOf(':');
|
|
27
|
+
if(idx === -1) continue;
|
|
28
|
+
const key = line.slice(0, idx).trim();
|
|
29
|
+
const value = line.slice(idx + 1).trim();
|
|
30
|
+
if(key) existingMeta[key] = value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if(locked){
|
|
35
|
+
existingMeta.locked = 'true';
|
|
36
|
+
} else {
|
|
37
|
+
delete existingMeta.locked;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const frontmatter = '<!--\n' +
|
|
41
|
+
Object.entries(existingMeta).map(([k, v]) => ` ${k}: ${v}`).join('\n') +
|
|
42
|
+
'\n-->';
|
|
43
|
+
|
|
44
|
+
const body = frontmatterMatch ? raw.slice(frontmatterMatch[0].length) : '\n' + raw;
|
|
45
|
+
await writeFile(fullPath, frontmatter + body, 'utf-8');
|
|
46
|
+
|
|
47
|
+
return [null, { file: normalized, locked }];
|
|
48
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { readFile, writeFile } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const RESERVED_KEYS = new Set(['owner', 'name', 'author', 'created', 'updated', 'locked', 'title', 'description']);
|
|
7
|
+
|
|
8
|
+
export default async ({ rootDir, file, name, title, description, author, template, contents, extraMetadata, force = false }) => {
|
|
6
9
|
if(!rootDir){
|
|
7
10
|
return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
8
11
|
}
|
|
@@ -20,8 +23,7 @@ export default async ({ rootDir, file, name, title, description, author, templat
|
|
|
20
23
|
const raw = await readFile(fullPath, 'utf-8');
|
|
21
24
|
|
|
22
25
|
const frontmatterMatch = raw.match(/^<!--\s*\n([\s\S]*?)\n\s*-->/);
|
|
23
|
-
|
|
24
|
-
if(lockedMatch) return [{ code: 403, msg: 'This page is locked and cannot be edited' }, null];
|
|
26
|
+
|
|
25
27
|
const existingMeta = {};
|
|
26
28
|
if(frontmatterMatch){
|
|
27
29
|
for(const line of frontmatterMatch[1].split('\n')){
|
|
@@ -33,6 +35,13 @@ export default async ({ rootDir, file, name, title, description, author, templat
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
if(extraMetadata !== undefined){
|
|
39
|
+
const conflictingKeys = Object.keys(extraMetadata).filter(k => RESERVED_KEYS.has(k));
|
|
40
|
+
if(conflictingKeys.length){
|
|
41
|
+
return [{ code: 400, msg: `Cannot use reserved metadata keys: ${conflictingKeys.join(', ')}` }, null];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
const now = new Date().toISOString();
|
|
37
46
|
const newMeta = {
|
|
38
47
|
...existingMeta,
|
|
@@ -42,6 +51,11 @@ export default async ({ rootDir, file, name, title, description, author, templat
|
|
|
42
51
|
if(title !== undefined) newMeta.title = title;
|
|
43
52
|
if(description !== undefined) newMeta.description = description;
|
|
44
53
|
if(author !== undefined) newMeta.author = author;
|
|
54
|
+
if(extraMetadata !== undefined){
|
|
55
|
+
for(const [k, v] of Object.entries(extraMetadata)){
|
|
56
|
+
newMeta[k] = v;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
45
59
|
|
|
46
60
|
const templateMatch = raw.match(/<page\s[^>]*template="([^"]*)"/)
|
|
47
61
|
const resolvedTemplate = template !== undefined ? template : (templateMatch ? templateMatch[1] : 'default');
|
|
@@ -70,5 +84,7 @@ export default async ({ rootDir, file, name, title, description, author, templat
|
|
|
70
84
|
|
|
71
85
|
await writeFile(fullPath, newPageContent, 'utf-8');
|
|
72
86
|
|
|
87
|
+
await triggerHook('page:updated', { file: safePath, updatedAt: now });
|
|
88
|
+
|
|
73
89
|
return [null, { file: safePath, updatedAt: now }];
|
|
74
90
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
4
5
|
|
|
5
6
|
const slugify = name => name
|
|
6
7
|
.toLowerCase()
|
|
@@ -10,7 +11,7 @@ const slugify = name => name
|
|
|
10
11
|
.replace(/-+/g, '-')
|
|
11
12
|
.replace(/^-|-$/g, '');
|
|
12
13
|
|
|
13
|
-
export default async ({ rootDir, directory, name, author, copyFrom }) => {
|
|
14
|
+
export default async ({ rootDir, directory, name, author, copyFrom, locked = false }) => {
|
|
14
15
|
if(!rootDir) return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
15
16
|
if(!name) return [{ code: 400, msg: 'Template name is required' }, null];
|
|
16
17
|
|
|
@@ -33,6 +34,7 @@ export default async ({ rootDir, directory, name, author, copyFrom }) => {
|
|
|
33
34
|
` author: ${author || ''}`,
|
|
34
35
|
` created: ${now}`,
|
|
35
36
|
` updated: ${now}`,
|
|
37
|
+
...(locked ? [' locked: true'] : []),
|
|
36
38
|
'-->'
|
|
37
39
|
].join('\n');
|
|
38
40
|
|
|
@@ -67,5 +69,7 @@ export default async ({ rootDir, directory, name, author, copyFrom }) => {
|
|
|
67
69
|
|
|
68
70
|
const relPath = dir ? `${dir}/${slug}.template.html` : `${slug}.template.html`;
|
|
69
71
|
|
|
72
|
+
await triggerHook('template:created', { file: relPath, name, slug });
|
|
73
|
+
|
|
70
74
|
return [null, { file: relPath, name, slug }];
|
|
71
75
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile, unlink } from 'fs/promises';
|
|
2
2
|
import { join, normalize, sep } from 'path';
|
|
3
3
|
import parseFrontmatter from '../fs/parseFrontmatter.js';
|
|
4
|
+
import triggerHook from '../hooks/triggerHook.js';
|
|
4
5
|
|
|
5
6
|
export default async ({ rootDir, files }) => {
|
|
6
7
|
if(!rootDir) return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
@@ -21,14 +22,12 @@ export default async ({ rootDir, files }) => {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const meta = parseFrontmatter(content);
|
|
24
|
-
if(meta.locked === 'true'){
|
|
25
|
-
return [{ code: 403, msg: `Cannot delete locked template: ${file}` }, null];
|
|
26
|
-
}
|
|
27
25
|
if(meta.owner && meta.owner !== 'custom'){
|
|
28
26
|
return [{ code: 403, msg: `Cannot delete template: ${file}` }, null];
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
await unlink(fullPath);
|
|
30
|
+
await triggerHook('template:deleted', { file });
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
return [null, { deleted: files.length }];
|
|
@@ -23,9 +23,6 @@ export default async ({ rootDir, file }) => {
|
|
|
23
23
|
|
|
24
24
|
const content = await readFile(fullPath, 'utf-8');
|
|
25
25
|
const meta = parseFrontmatter(content);
|
|
26
|
-
if(meta.locked === 'true'){
|
|
27
|
-
return [{ code: 403, msg: 'Cannot disable a locked template' }, null];
|
|
28
|
-
}
|
|
29
26
|
|
|
30
27
|
const disabledPath = fullPath.replace(/\.template\.html$/, '.template-disabled.html');
|
|
31
28
|
if(existsSync(disabledPath)){
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import parseFrontmatter from '../fs/parseFrontmatter.js';
|
|
5
|
+
|
|
6
|
+
export default async ({ rootDir, file }) => {
|
|
7
|
+
if(!rootDir){
|
|
8
|
+
return [{ code: 400, msg: 'Root directory is required' }, null];
|
|
9
|
+
}
|
|
10
|
+
if(!file){
|
|
11
|
+
return [{ code: 400, msg: 'File path is required' }, null];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const safePath = file.replace(/\.\./g, '').replace(/^\//, '');
|
|
15
|
+
const fullPath = join(rootDir, safePath);
|
|
16
|
+
|
|
17
|
+
if(!existsSync(fullPath)){
|
|
18
|
+
return [{ code: 404, msg: 'Template not found' }, null];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const raw = await readFile(fullPath, 'utf-8');
|
|
22
|
+
const meta = parseFrontmatter(raw);
|
|
23
|
+
|
|
24
|
+
return [null, {
|
|
25
|
+
name: meta.name,
|
|
26
|
+
owner: meta.owner,
|
|
27
|
+
locked: meta.locked === 'true',
|
|
28
|
+
author: meta.author,
|
|
29
|
+
created: meta.created,
|
|
30
|
+
updated: meta.updated
|
|
31
|
+
}];
|
|
32
|
+
};
|
|
@@ -28,9 +28,6 @@ export default async ({ rootDir, file, newFile }) => {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const meta = parseFrontmatter(content);
|
|
31
|
-
if(meta.locked === 'true'){
|
|
32
|
-
return [{ code: 403, msg: 'Cannot move a locked template' }, null];
|
|
33
|
-
}
|
|
34
31
|
if(meta.owner && meta.owner !== 'custom'){
|
|
35
32
|
return [{ code: 403, msg: 'Cannot move system templates' }, null];
|
|
36
33
|
}
|