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.
Files changed (306) hide show
  1. package/app-public/account/split2.template.html +1 -0
  2. package/app-public/test.page.html +1 -0
  3. package/bin/cli.js +21 -4
  4. package/dist/admin/components/AddGroupControl.js +2 -2
  5. package/dist/admin/components/AddGroupControl.js.map +1 -1
  6. package/dist/admin/components/ClearExpiredSessionsControl.js +2 -2
  7. package/dist/admin/components/ClearExpiredSessionsControl.js.map +1 -1
  8. package/dist/admin/components/FragmentEditor.js +15 -15
  9. package/dist/admin/components/FragmentEditor.js.map +1 -1
  10. package/dist/admin/components/GlobalContentEditor.js +12 -12
  11. package/dist/admin/components/GlobalContentEditor.js.map +1 -1
  12. package/dist/admin/components/GroupDeleteSelected.js +2 -2
  13. package/dist/admin/components/GroupDeleteSelected.js.map +1 -1
  14. package/dist/admin/components/GroupRemoveRecord.js +2 -2
  15. package/dist/admin/components/GroupRemoveRecord.js.map +1 -1
  16. package/dist/admin/components/Monaco.js +1 -1
  17. package/dist/admin/components/Monaco.js.map +1 -1
  18. package/dist/admin/components/PageEditor.js +42 -28
  19. package/dist/admin/components/PageEditor.js.map +1 -1
  20. package/dist/admin/components/TableRowDelete.js +2 -2
  21. package/dist/admin/components/TableRowDelete.js.map +1 -1
  22. package/dist/admin/components/TableRowLink.js +2 -2
  23. package/dist/admin/components/TableRowLink.js.map +1 -1
  24. package/dist/admin/components/TemplateEditor.js +18 -18
  25. package/dist/admin/components/TemplateEditor.js.map +1 -1
  26. package/dist/admin/components/ValueEditor.js +17 -17
  27. package/dist/admin/components/ValueEditor.js.map +1 -1
  28. package/dist/admin/components/tableControls/AddMember.js +2 -2
  29. package/dist/admin/components/tableControls/AddMember.js.map +1 -1
  30. package/dist/admin/components/tableControls/AddPermission.js +2 -2
  31. package/dist/admin/components/tableControls/AddPermission.js.map +1 -1
  32. package/dist/admin/components/tableControls/InstallExtension.js +2 -2
  33. package/dist/admin/components/tableControls/InstallExtension.js.map +1 -1
  34. package/dist/admin/components/tableControls/UninstallExtension.js +2 -2
  35. package/dist/admin/components/tableControls/UninstallExtension.js.map +1 -1
  36. package/dist/admin/extensions/index.page.html +49 -15
  37. package/dist/admin/icons/open_in_new.svg +1 -0
  38. package/dist/admin/nav.fragment.html +1 -1
  39. package/dist/kempo/api/admin-globals/DELETE.js +1 -1
  40. package/dist/kempo/api/admin-globals/DELETE.js.map +1 -1
  41. package/dist/kempo/api/admin-globals/GET.js +1 -1
  42. package/dist/kempo/api/admin-globals/GET.js.map +1 -1
  43. package/dist/kempo/api/admin-globals/POST.js +1 -1
  44. package/dist/kempo/api/admin-globals/POST.js.map +1 -1
  45. package/dist/kempo/api/admin-globals/[id]/GET.js +1 -1
  46. package/dist/kempo/api/admin-globals/[id]/GET.js.map +1 -1
  47. package/dist/kempo/api/admin-globals/[id]/PUT.js +1 -1
  48. package/dist/kempo/api/admin-globals/[id]/PUT.js.map +1 -1
  49. package/dist/kempo/api/admin-globals/[id]/disable/PUT.js +1 -1
  50. package/dist/kempo/api/admin-globals/[id]/disable/PUT.js.map +1 -1
  51. package/dist/kempo/api/admin-globals/[id]/enable/PUT.js +1 -1
  52. package/dist/kempo/api/admin-globals/[id]/enable/PUT.js.map +1 -1
  53. package/dist/kempo/api/auth/change-password/POST.js +1 -1
  54. package/dist/kempo/api/auth/change-password/POST.js.map +1 -1
  55. package/dist/kempo/api/auth/login/email/POST.js +1 -1
  56. package/dist/kempo/api/auth/login/email/POST.js.map +1 -1
  57. package/dist/kempo/api/auth/logout/POST.js +1 -1
  58. package/dist/kempo/api/auth/logout/POST.js.map +1 -1
  59. package/dist/kempo/api/auth/register/email/POST.js +1 -1
  60. package/dist/kempo/api/auth/register/email/POST.js.map +1 -1
  61. package/dist/kempo/api/auth/reset-password/POST.js +1 -1
  62. package/dist/kempo/api/auth/reset-password/POST.js.map +1 -1
  63. package/dist/kempo/api/auth/session/GET.js +1 -1
  64. package/dist/kempo/api/auth/session/GET.js.map +1 -1
  65. package/dist/kempo/api/auth/verify-email/POST.js +1 -1
  66. package/dist/kempo/api/auth/verify-email/POST.js.map +1 -1
  67. package/dist/kempo/api/extensions/DELETE.js +1 -1
  68. package/dist/kempo/api/extensions/DELETE.js.map +1 -1
  69. package/dist/kempo/api/extensions/GET.js +1 -1
  70. package/dist/kempo/api/extensions/GET.js.map +1 -1
  71. package/dist/kempo/api/extensions/POST.js +1 -1
  72. package/dist/kempo/api/extensions/POST.js.map +1 -1
  73. package/dist/kempo/api/extensions/[name]/disable/POST.js +1 -1
  74. package/dist/kempo/api/extensions/[name]/disable/POST.js.map +1 -1
  75. package/dist/kempo/api/extensions/[name]/enable/POST.js +1 -1
  76. package/dist/kempo/api/extensions/[name]/enable/POST.js.map +1 -1
  77. package/dist/kempo/api/extensions/[name]/update/POST.js +1 -1
  78. package/dist/kempo/api/extensions/[name]/update/POST.js.map +1 -1
  79. package/dist/kempo/api/extensions/[name]/update-check/GET.js +1 -1
  80. package/dist/kempo/api/extensions/[name]/update-check/GET.js.map +1 -1
  81. package/dist/kempo/api/extensions/available/GET.js +1 -1
  82. package/dist/kempo/api/extensions/available/GET.js.map +1 -1
  83. package/dist/kempo/api/extensions/known/GET.js +1 -1
  84. package/dist/kempo/api/extensions/known/GET.js.map +1 -1
  85. package/dist/kempo/api/forgot-password/POST.js +1 -1
  86. package/dist/kempo/api/forgot-password/POST.js.map +1 -1
  87. package/dist/kempo/api/fragments/DELETE.js +1 -1
  88. package/dist/kempo/api/fragments/DELETE.js.map +1 -1
  89. package/dist/kempo/api/fragments/GET.js +1 -1
  90. package/dist/kempo/api/fragments/GET.js.map +1 -1
  91. package/dist/kempo/api/fragments/POST.js +1 -1
  92. package/dist/kempo/api/fragments/POST.js.map +1 -1
  93. package/dist/kempo/api/fragments/disable/PUT.js +1 -1
  94. package/dist/kempo/api/fragments/disable/PUT.js.map +1 -1
  95. package/dist/kempo/api/fragments/enable/PUT.js +1 -1
  96. package/dist/kempo/api/fragments/enable/PUT.js.map +1 -1
  97. package/dist/kempo/api/fragments/file/GET.js +1 -1
  98. package/dist/kempo/api/fragments/file/GET.js.map +1 -1
  99. package/dist/kempo/api/fragments/file/PATCH.js +1 -1
  100. package/dist/kempo/api/fragments/file/PATCH.js.map +1 -1
  101. package/dist/kempo/api/fragments/file/PUT.js +1 -1
  102. package/dist/kempo/api/fragments/file/PUT.js.map +1 -1
  103. package/dist/kempo/api/fragments/lock/PUT.js +2 -0
  104. package/dist/kempo/api/fragments/lock/PUT.js.map +1 -0
  105. package/dist/kempo/api/globals/DELETE.js +1 -1
  106. package/dist/kempo/api/globals/DELETE.js.map +1 -1
  107. package/dist/kempo/api/globals/GET.js +1 -1
  108. package/dist/kempo/api/globals/GET.js.map +1 -1
  109. package/dist/kempo/api/globals/POST.js +1 -1
  110. package/dist/kempo/api/globals/POST.js.map +1 -1
  111. package/dist/kempo/api/globals/disable/PUT.js +1 -1
  112. package/dist/kempo/api/globals/disable/PUT.js.map +1 -1
  113. package/dist/kempo/api/globals/enable/PUT.js +1 -1
  114. package/dist/kempo/api/globals/enable/PUT.js.map +1 -1
  115. package/dist/kempo/api/globals/entry/GET.js +1 -1
  116. package/dist/kempo/api/globals/entry/GET.js.map +1 -1
  117. package/dist/kempo/api/globals/entry/PUT.js +1 -1
  118. package/dist/kempo/api/globals/entry/PUT.js.map +1 -1
  119. package/dist/kempo/api/globals/lock/PUT.js +2 -0
  120. package/dist/kempo/api/globals/lock/PUT.js.map +1 -0
  121. package/dist/kempo/api/groups/DELETE.js +1 -1
  122. package/dist/kempo/api/groups/DELETE.js.map +1 -1
  123. package/dist/kempo/api/groups/GET.js +1 -1
  124. package/dist/kempo/api/groups/GET.js.map +1 -1
  125. package/dist/kempo/api/groups/PATCH.js +1 -1
  126. package/dist/kempo/api/groups/PATCH.js.map +1 -1
  127. package/dist/kempo/api/groups/POST.js +1 -1
  128. package/dist/kempo/api/groups/POST.js.map +1 -1
  129. package/dist/kempo/api/groups/[name]/GET.js +1 -1
  130. package/dist/kempo/api/groups/[name]/GET.js.map +1 -1
  131. package/dist/kempo/api/groups/[name]/members/DELETE.js +1 -1
  132. package/dist/kempo/api/groups/[name]/members/DELETE.js.map +1 -1
  133. package/dist/kempo/api/groups/[name]/members/GET.js +1 -1
  134. package/dist/kempo/api/groups/[name]/members/GET.js.map +1 -1
  135. package/dist/kempo/api/groups/[name]/members/POST.js +1 -1
  136. package/dist/kempo/api/groups/[name]/members/POST.js.map +1 -1
  137. package/dist/kempo/api/groups/[name]/permissions/DELETE.js +1 -1
  138. package/dist/kempo/api/groups/[name]/permissions/DELETE.js.map +1 -1
  139. package/dist/kempo/api/groups/[name]/permissions/GET.js +1 -1
  140. package/dist/kempo/api/groups/[name]/permissions/GET.js.map +1 -1
  141. package/dist/kempo/api/groups/[name]/permissions/POST.js +1 -1
  142. package/dist/kempo/api/groups/[name]/permissions/POST.js.map +1 -1
  143. package/dist/kempo/api/pages/DELETE.js +1 -1
  144. package/dist/kempo/api/pages/DELETE.js.map +1 -1
  145. package/dist/kempo/api/pages/GET.js +1 -1
  146. package/dist/kempo/api/pages/GET.js.map +1 -1
  147. package/dist/kempo/api/pages/POST.js +1 -1
  148. package/dist/kempo/api/pages/POST.js.map +1 -1
  149. package/dist/kempo/api/pages/directories/GET.js +1 -1
  150. package/dist/kempo/api/pages/directories/GET.js.map +1 -1
  151. package/dist/kempo/api/pages/disable/PUT.js +1 -1
  152. package/dist/kempo/api/pages/disable/PUT.js.map +1 -1
  153. package/dist/kempo/api/pages/enable/PUT.js +1 -1
  154. package/dist/kempo/api/pages/enable/PUT.js.map +1 -1
  155. package/dist/kempo/api/pages/file/GET.js +1 -1
  156. package/dist/kempo/api/pages/file/GET.js.map +1 -1
  157. package/dist/kempo/api/pages/file/PATCH.js +1 -1
  158. package/dist/kempo/api/pages/file/PATCH.js.map +1 -1
  159. package/dist/kempo/api/pages/file/PUT.js +1 -1
  160. package/dist/kempo/api/pages/file/PUT.js.map +1 -1
  161. package/dist/kempo/api/pages/lock/PUT.js +2 -0
  162. package/dist/kempo/api/pages/lock/PUT.js.map +1 -0
  163. package/dist/kempo/api/pages/search/GET.js +2 -0
  164. package/dist/kempo/api/pages/search/GET.js.map +1 -0
  165. package/dist/kempo/api/pages/templates/GET.js +1 -1
  166. package/dist/kempo/api/pages/templates/GET.js.map +1 -1
  167. package/dist/kempo/api/permissions/DELETE.js +1 -1
  168. package/dist/kempo/api/permissions/DELETE.js.map +1 -1
  169. package/dist/kempo/api/permissions/GET.js +1 -1
  170. package/dist/kempo/api/permissions/GET.js.map +1 -1
  171. package/dist/kempo/api/permissions/PATCH.js +1 -1
  172. package/dist/kempo/api/permissions/PATCH.js.map +1 -1
  173. package/dist/kempo/api/permissions/POST.js +1 -1
  174. package/dist/kempo/api/permissions/POST.js.map +1 -1
  175. package/dist/kempo/api/settings/[owner]/GET.js +1 -1
  176. package/dist/kempo/api/settings/[owner]/GET.js.map +1 -1
  177. package/dist/kempo/api/settings/[owner]/[name]/DELETE.js +1 -1
  178. package/dist/kempo/api/settings/[owner]/[name]/DELETE.js.map +1 -1
  179. package/dist/kempo/api/settings/[owner]/[name]/GET.js +1 -1
  180. package/dist/kempo/api/settings/[owner]/[name]/GET.js.map +1 -1
  181. package/dist/kempo/api/settings/[owner]/[name]/POST.js +1 -1
  182. package/dist/kempo/api/settings/[owner]/[name]/POST.js.map +1 -1
  183. package/dist/kempo/api/settings/public/GET.js +1 -1
  184. package/dist/kempo/api/settings/public/GET.js.map +1 -1
  185. package/dist/kempo/api/templates/DELETE.js +1 -1
  186. package/dist/kempo/api/templates/DELETE.js.map +1 -1
  187. package/dist/kempo/api/templates/GET.js +1 -1
  188. package/dist/kempo/api/templates/GET.js.map +1 -1
  189. package/dist/kempo/api/templates/POST.js +1 -1
  190. package/dist/kempo/api/templates/POST.js.map +1 -1
  191. package/dist/kempo/api/templates/disable/PUT.js +1 -1
  192. package/dist/kempo/api/templates/disable/PUT.js.map +1 -1
  193. package/dist/kempo/api/templates/enable/PUT.js +1 -1
  194. package/dist/kempo/api/templates/enable/PUT.js.map +1 -1
  195. package/dist/kempo/api/templates/file/GET.js +1 -1
  196. package/dist/kempo/api/templates/file/GET.js.map +1 -1
  197. package/dist/kempo/api/templates/file/PATCH.js +1 -1
  198. package/dist/kempo/api/templates/file/PATCH.js.map +1 -1
  199. package/dist/kempo/api/templates/file/PUT.js +1 -1
  200. package/dist/kempo/api/templates/file/PUT.js.map +1 -1
  201. package/dist/kempo/api/templates/lock/PUT.js +2 -0
  202. package/dist/kempo/api/templates/lock/PUT.js.map +1 -0
  203. package/dist/kempo/api/user/DELETE.js +1 -1
  204. package/dist/kempo/api/user/DELETE.js.map +1 -1
  205. package/dist/kempo/api/user/GET.js +1 -1
  206. package/dist/kempo/api/user/GET.js.map +1 -1
  207. package/dist/kempo/api/user/PATCH.js +1 -1
  208. package/dist/kempo/api/user/PATCH.js.map +1 -1
  209. package/dist/kempo/api/user/[userid]/GET.js +1 -1
  210. package/dist/kempo/api/user/[userid]/GET.js.map +1 -1
  211. package/dist/kempo/api/user/[userid]/groups/DELETE.js +1 -1
  212. package/dist/kempo/api/user/[userid]/groups/DELETE.js.map +1 -1
  213. package/dist/kempo/api/user/[userid]/groups/GET.js +1 -1
  214. package/dist/kempo/api/user/[userid]/groups/GET.js.map +1 -1
  215. package/dist/kempo/api/user/[userid]/groups/POST.js +1 -1
  216. package/dist/kempo/api/user/[userid]/groups/POST.js.map +1 -1
  217. package/dist/kempo/api/user/[userid]/permissions/GET.js +1 -1
  218. package/dist/kempo/api/user/[userid]/permissions/GET.js.map +1 -1
  219. package/dist/kempo/api/user/[userid]/permissions/all/GET.js +1 -1
  220. package/dist/kempo/api/user/[userid]/permissions/all/GET.js.map +1 -1
  221. package/dist/kempo/api/user/[userid]/permissions/list/GET.js +1 -1
  222. package/dist/kempo/api/user/[userid]/permissions/list/GET.js.map +1 -1
  223. package/dist/kempo/api/user/[userid]/permissions/some/GET.js +1 -1
  224. package/dist/kempo/api/user/[userid]/permissions/some/GET.js.map +1 -1
  225. package/dist/kempo/api/user/[userid]/sessions/DELETE.js +1 -1
  226. package/dist/kempo/api/user/[userid]/sessions/DELETE.js.map +1 -1
  227. package/dist/kempo/api/user/[userid]/sessions/GET.js +1 -1
  228. package/dist/kempo/api/user/[userid]/sessions/GET.js.map +1 -1
  229. package/dist/kempo/api/user/[userid]/sessions/expired/DELETE.js +1 -1
  230. package/dist/kempo/api/user/[userid]/sessions/expired/DELETE.js.map +1 -1
  231. package/dist/kempo/api/user/create/POST.js +1 -1
  232. package/dist/kempo/api/user/create/POST.js.map +1 -1
  233. package/dist/kempo/api/user/search/GET.js +1 -1
  234. package/dist/kempo/api/user/search/GET.js.map +1 -1
  235. package/dist/kempo/components/LogoutBtn.js +1 -1
  236. package/dist/kempo/components/LogoutBtn.js.map +1 -1
  237. package/dist/kempo/components/Menu.js +9 -9
  238. package/dist/kempo/components/Menu.js.map +1 -1
  239. package/dist/kempo/components/Permission.js +1 -1
  240. package/dist/kempo/components/Permission.js.map +1 -1
  241. package/dist/kempo/components/Role.js +1 -1
  242. package/dist/kempo/components/Role.js.map +1 -1
  243. package/dist/kempo/components/UserCreatedAt.js +1 -1
  244. package/dist/kempo/components/UserCreatedAt.js.map +1 -1
  245. package/dist/kempo/components/UserEmail.js +1 -1
  246. package/dist/kempo/components/UserEmail.js.map +1 -1
  247. package/dist/kempo/components/UserEmailVerified.js +1 -1
  248. package/dist/kempo/components/UserEmailVerified.js.map +1 -1
  249. package/dist/kempo/components/UserName.js +1 -1
  250. package/dist/kempo/components/UserName.js.map +1 -1
  251. package/dist/kempo/fetch.js +1 -1
  252. package/dist/kempo/fetch.js.map +1 -1
  253. package/dist/kempo/sdk.js +1 -1
  254. package/dist/kempo/sdk.js.map +1 -1
  255. package/middleware/kempo.js +36 -6
  256. package/package.json +3 -3
  257. package/server/sdk.js +5 -0
  258. package/server/utils/extensions/installExtension.js +19 -13
  259. package/server/utils/extensions/uninstallExtension.js +6 -10
  260. package/server/utils/fragments/createFragment.js +5 -1
  261. package/server/utils/fragments/deleteFragment.js +2 -3
  262. package/server/utils/fragments/disableFragment.js +0 -3
  263. package/server/utils/fragments/getFragmentMetadata.js +32 -0
  264. package/server/utils/fragments/moveFragment.js +0 -3
  265. package/server/utils/fragments/setFragmentLocked.js +48 -0
  266. package/server/utils/fragments/updateFragment.js +3 -3
  267. package/server/utils/fs/isResourceLocked.js +22 -0
  268. package/server/utils/global-content/createGlobalContent.js +3 -0
  269. package/server/utils/global-content/deleteGlobalContent.js +3 -3
  270. package/server/utils/global-content/getGlobalContentMetadata.js +37 -0
  271. package/server/utils/global-content/setGlobalContentLocked.js +44 -0
  272. package/server/utils/global-content/updateGlobalContent.js +3 -4
  273. package/server/utils/pages/createPage.js +19 -7
  274. package/server/utils/pages/deletePage.js +4 -13
  275. package/server/utils/pages/disablePage.js +0 -3
  276. package/server/utils/pages/getPage.js +7 -0
  277. package/server/utils/pages/getPageMetadata.js +32 -0
  278. package/server/utils/pages/listPages.js +11 -3
  279. package/server/utils/pages/movePage.js +0 -3
  280. package/server/utils/pages/searchByMetadata.js +61 -0
  281. package/server/utils/pages/setPageLocked.js +48 -0
  282. package/server/utils/pages/updatePage.js +19 -3
  283. package/server/utils/templates/createTemplate.js +5 -1
  284. package/server/utils/templates/deleteTemplate.js +2 -3
  285. package/server/utils/templates/disableTemplate.js +0 -3
  286. package/server/utils/templates/getTemplateMetadata.js +32 -0
  287. package/server/utils/templates/moveTemplate.js +0 -3
  288. package/server/utils/templates/setTemplateLocked.js +48 -0
  289. package/server/utils/templates/updateTemplate.js +3 -3
  290. package/dist/admin/accounts/groups/[name]/index.html +0 -518
  291. package/dist/admin/accounts/groups/index.html +0 -378
  292. package/dist/admin/accounts/permissions/index.html +0 -397
  293. package/dist/admin/accounts/users/[id]/index.html +0 -541
  294. package/dist/admin/accounts/users/index.html +0 -396
  295. package/dist/admin/content/fragments/edit/index.html +0 -132
  296. package/dist/admin/content/fragments/index.html +0 -375
  297. package/dist/admin/content/globals/edit/index.html +0 -132
  298. package/dist/admin/content/globals/index.html +0 -358
  299. package/dist/admin/content/pages/edit/index.html +0 -132
  300. package/dist/admin/content/pages/index.html +0 -509
  301. package/dist/admin/content/templates/edit/index.html +0 -132
  302. package/dist/admin/content/templates/index.html +0 -429
  303. package/dist/admin/extensions/index.html +0 -222
  304. package/dist/admin/index.html +0 -130
  305. package/dist/admin/settings/[owner]/[name]/index.html +0 -449
  306. 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 frontmatter = [
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
- ].join('\n');
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, { file: `${relPath}.page.html`, url, title: name, slug }];
71
+ return [null, result];
60
72
  };
@@ -1,6 +1,6 @@
1
- import { readFile, unlink } from 'fs/promises';
1
+ import { unlink } from 'fs/promises';
2
2
  import { join, normalize, sep } from 'path';
3
- import parseFrontmatter from '../fs/parseFrontmatter.js';
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
- content = await readFile(fullPath, 'utf-8');
17
+ await unlink(fullPath);
19
18
  } catch {
20
19
  continue;
21
20
  }
22
21
 
23
- const meta = parseFrontmatter(content);
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
- export default async ({ rootDir }) => {
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
- return [null, { pages, total: pages.length }];
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
- export default async ({ rootDir, file, name, title, description, author, template, contents }) => {
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
- const lockedMatch = raw.match(/^<!--[\s\S]*?locked:\s*true[\s\S]*?-->/);
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
  }