@taon-dev/cms 21.0.25 → 21.0.27

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 (852) hide show
  1. package/VERIFIED-BUILD-DATA.jsonc +3 -3
  2. package/assets/.gitkeep +0 -0
  3. package/assets/css/primeicons/fonts/primeicons.ttf +0 -0
  4. package/assets/css/primeicons/fonts/primeicons.woff +0 -0
  5. package/assets/css/primeicons/fonts/primeicons.woff2 +0 -0
  6. package/assets/generated/pwa/android-chrome-144x144.png +0 -0
  7. package/assets/generated/pwa/android-chrome-192x192.png +0 -0
  8. package/assets/generated/pwa/android-chrome-256x256.png +0 -0
  9. package/assets/generated/pwa/android-chrome-36x36.png +0 -0
  10. package/assets/generated/pwa/android-chrome-384x384.png +0 -0
  11. package/assets/generated/pwa/android-chrome-48x48.png +0 -0
  12. package/assets/generated/pwa/android-chrome-512x512.png +0 -0
  13. package/assets/generated/pwa/android-chrome-72x72.png +0 -0
  14. package/assets/generated/pwa/android-chrome-96x96.png +0 -0
  15. package/assets/generated/pwa/apple-touch-icon-1024x1024.png +0 -0
  16. package/assets/generated/pwa/apple-touch-icon-114x114.png +0 -0
  17. package/assets/generated/pwa/apple-touch-icon-120x120.png +0 -0
  18. package/assets/generated/pwa/apple-touch-icon-144x144.png +0 -0
  19. package/assets/generated/pwa/apple-touch-icon-152x152.png +0 -0
  20. package/assets/generated/pwa/apple-touch-icon-167x167.png +0 -0
  21. package/assets/generated/pwa/apple-touch-icon-180x180.png +0 -0
  22. package/assets/generated/pwa/apple-touch-icon-57x57.png +0 -0
  23. package/assets/generated/pwa/apple-touch-icon-60x60.png +0 -0
  24. package/assets/generated/pwa/apple-touch-icon-72x72.png +0 -0
  25. package/assets/generated/pwa/apple-touch-icon-76x76.png +0 -0
  26. package/assets/generated/pwa/apple-touch-icon-precomposed.png +0 -0
  27. package/assets/generated/pwa/apple-touch-icon.png +0 -0
  28. package/assets/generated/pwa/apple-touch-startup-image-1125x2436.png +0 -0
  29. package/assets/generated/pwa/apple-touch-startup-image-1136x640.png +0 -0
  30. package/assets/generated/pwa/apple-touch-startup-image-1170x2532.png +0 -0
  31. package/assets/generated/pwa/apple-touch-startup-image-1179x2556.png +0 -0
  32. package/assets/generated/pwa/apple-touch-startup-image-1242x2208.png +0 -0
  33. package/assets/generated/pwa/apple-touch-startup-image-1242x2688.png +0 -0
  34. package/assets/generated/pwa/apple-touch-startup-image-1284x2778.png +0 -0
  35. package/assets/generated/pwa/apple-touch-startup-image-1290x2796.png +0 -0
  36. package/assets/generated/pwa/apple-touch-startup-image-1334x750.png +0 -0
  37. package/assets/generated/pwa/apple-touch-startup-image-1488x2266.png +0 -0
  38. package/assets/generated/pwa/apple-touch-startup-image-1536x2048.png +0 -0
  39. package/assets/generated/pwa/apple-touch-startup-image-1620x2160.png +0 -0
  40. package/assets/generated/pwa/apple-touch-startup-image-1640x2160.png +0 -0
  41. package/assets/generated/pwa/apple-touch-startup-image-1668x2224.png +0 -0
  42. package/assets/generated/pwa/apple-touch-startup-image-1668x2388.png +0 -0
  43. package/assets/generated/pwa/apple-touch-startup-image-1792x828.png +0 -0
  44. package/assets/generated/pwa/apple-touch-startup-image-2048x1536.png +0 -0
  45. package/assets/generated/pwa/apple-touch-startup-image-2048x2732.png +0 -0
  46. package/assets/generated/pwa/apple-touch-startup-image-2160x1620.png +0 -0
  47. package/assets/generated/pwa/apple-touch-startup-image-2160x1640.png +0 -0
  48. package/assets/generated/pwa/apple-touch-startup-image-2208x1242.png +0 -0
  49. package/assets/generated/pwa/apple-touch-startup-image-2224x1668.png +0 -0
  50. package/assets/generated/pwa/apple-touch-startup-image-2266x1488.png +0 -0
  51. package/assets/generated/pwa/apple-touch-startup-image-2388x1668.png +0 -0
  52. package/assets/generated/pwa/apple-touch-startup-image-2436x1125.png +0 -0
  53. package/assets/generated/pwa/apple-touch-startup-image-2532x1170.png +0 -0
  54. package/assets/generated/pwa/apple-touch-startup-image-2556x1179.png +0 -0
  55. package/assets/generated/pwa/apple-touch-startup-image-2688x1242.png +0 -0
  56. package/assets/generated/pwa/apple-touch-startup-image-2732x2048.png +0 -0
  57. package/assets/generated/pwa/apple-touch-startup-image-2778x1284.png +0 -0
  58. package/assets/generated/pwa/apple-touch-startup-image-2796x1290.png +0 -0
  59. package/assets/generated/pwa/apple-touch-startup-image-640x1136.png +0 -0
  60. package/assets/generated/pwa/apple-touch-startup-image-750x1334.png +0 -0
  61. package/assets/generated/pwa/apple-touch-startup-image-828x1792.png +0 -0
  62. package/assets/generated/pwa/browserconfig.xml +12 -0
  63. package/assets/generated/pwa/favicon-16x16.png +0 -0
  64. package/assets/generated/pwa/favicon-32x32.png +0 -0
  65. package/assets/generated/pwa/favicon-48x48.png +0 -0
  66. package/assets/generated/pwa/favicon.ico +0 -0
  67. package/assets/generated/pwa/manifest.webmanifest +69 -0
  68. package/assets/generated/pwa/mstile-144x144.png +0 -0
  69. package/assets/generated/pwa/mstile-150x150.png +0 -0
  70. package/assets/generated/pwa/mstile-310x150.png +0 -0
  71. package/assets/generated/pwa/mstile-310x310.png +0 -0
  72. package/assets/generated/pwa/mstile-70x70.png +0 -0
  73. package/assets/generated/pwa/yandex-browser-50x50.png +0 -0
  74. package/assets/generated/pwa/yandex-browser-manifest.json +9 -0
  75. package/browser/fesm2022/{cms-browser-taon-cms-post.component.html.de-DE.i18n.data-Bv4175Nc.mjs → cms-browser-taon-cms-post.component.html.de-DE.i18n.data-GLOdlJtK.mjs} +152 -137
  76. package/browser/fesm2022/cms-browser-taon-cms-post.component.html.de-DE.i18n.data-GLOdlJtK.mjs.map +1 -0
  77. package/browser/fesm2022/{cms-browser-taon-cms-post.component.html.en-US.i18n.data-CY1znnmK.mjs → cms-browser-taon-cms-post.component.html.en-US.i18n.data-Sti5WK0f.mjs} +149 -137
  78. package/browser/fesm2022/cms-browser-taon-cms-post.component.html.en-US.i18n.data-Sti5WK0f.mjs.map +1 -0
  79. package/browser/fesm2022/{cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-BcbuzOtn.mjs → cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-D4hUUPRh.mjs} +152 -137
  80. package/browser/fesm2022/cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-D4hUUPRh.mjs.map +1 -0
  81. package/browser/fesm2022/{cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DzuYOTSn.mjs → cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DFs0vM5m.mjs} +89 -70
  82. package/browser/fesm2022/cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DFs0vM5m.mjs.map +1 -0
  83. package/browser/fesm2022/{cms-browser-taon-cms-post.component.ts.en-US.i18n.data-MgewtvKr.mjs → cms-browser-taon-cms-post.component.ts.en-US.i18n.data-C2yhA0CQ.mjs} +87 -70
  84. package/browser/fesm2022/cms-browser-taon-cms-post.component.ts.en-US.i18n.data-C2yhA0CQ.mjs.map +1 -0
  85. package/browser/fesm2022/{cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-B1ILfsGK.mjs → cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-BSX4auNB.mjs} +89 -70
  86. package/browser/fesm2022/cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-BSX4auNB.mjs.map +1 -0
  87. package/browser/fesm2022/cms-browser.mjs +1463 -157
  88. package/browser/fesm2022/cms-browser.mjs.map +1 -1
  89. package/browser/package.json +1 -1
  90. package/browser/types/cms-browser.d.ts +124 -9
  91. package/browser-prod/fesm2022/{cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DpFy5ZSO.mjs → cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DCh3tUVC.mjs} +152 -137
  92. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DCh3tUVC.mjs.map +1 -0
  93. package/browser-prod/fesm2022/{cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-DLaPQZZj.mjs → cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-CmuKeBoj.mjs} +149 -137
  94. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-CmuKeBoj.mjs.map +1 -0
  95. package/browser-prod/fesm2022/{cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-alQxEnOY.mjs → cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-DlP8ezIr.mjs} +152 -137
  96. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-DlP8ezIr.mjs.map +1 -0
  97. package/browser-prod/fesm2022/{cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-4KAdwPda.mjs → cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-BnlwPUxT.mjs} +89 -70
  98. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-BnlwPUxT.mjs.map +1 -0
  99. package/browser-prod/fesm2022/{cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CyuTYn77.mjs → cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CX4JzE60.mjs} +87 -70
  100. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CX4JzE60.mjs.map +1 -0
  101. package/browser-prod/fesm2022/{cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-CgmdVwt_.mjs → cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-C4xmbOD4.mjs} +89 -70
  102. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-C4xmbOD4.mjs.map +1 -0
  103. package/browser-prod/fesm2022/cms-browser-prod.mjs +1463 -157
  104. package/browser-prod/fesm2022/cms-browser-prod.mjs.map +1 -1
  105. package/browser-prod/package.json +1 -1
  106. package/browser-prod/types/cms-browser-prod.d.ts +124 -9
  107. package/full-dts/build-info._auto-generated_.d.ts +1 -1
  108. package/full-dts/index._auto-generated_.d.ts +3 -0
  109. package/full-dts/index.d.ts +1 -0
  110. package/full-dts/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.d.ts +2 -0
  111. package/full-dts/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.d.ts +2 -0
  112. package/full-dts/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.d.ts +2 -0
  113. package/full-dts/taon-cms-content/taon-cms-content-api.service.d.ts +4 -2
  114. package/full-dts/taon-cms-content/taon-cms-content-storage.controller.d.ts +12 -1
  115. package/full-dts/taon-cms-content/taon-cms-content.config.service.d.ts +1 -0
  116. package/full-dts/taon-cms-content/taon-cms-content.constants.d.ts +5 -1
  117. package/full-dts/taon-cms-content/taon-cms-content.controller.d.ts +4 -0
  118. package/full-dts/taon-cms-content/taon-cms-content.models.d.ts +2 -1
  119. package/full-dts/taon-cms-content/taon-cms-content.provider.d.ts +4 -0
  120. package/full-dts/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.d.ts +2 -0
  121. package/full-dts/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.d.ts +2 -0
  122. package/full-dts/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.d.ts +2 -0
  123. package/full-dts/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.d.ts +10 -2
  124. package/full-dts/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.d.ts +4 -0
  125. package/full-dts/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.d.ts +10 -2
  126. package/full-dts/taon-posts/taon-cms-post/taon-cms-post.component.d.ts +18 -2
  127. package/full-dts/taon-spreadsheet-table/index.d.ts +4 -0
  128. package/full-dts/taon-spreadsheet-table/taon-spreadsheet-table.component.d.ts +51 -0
  129. package/full-dts/taon-spreadsheet-table/taon-spreadsheet-table.models.d.ts +13 -0
  130. package/full-dts/taon-spreadsheet-table/taon-spreadsheet-table.utils.d.ts +10 -0
  131. package/full-dts/taon-spreadsheet-table/taon-spreadsheet-table.utils.spec.d.ts +2 -0
  132. package/html/lib/taon-posts/taon-cms-post/taon-cms-post.component.html +50 -5
  133. package/html/lib/taon-spreadsheet-table/taon-spreadsheet-table.component.html +364 -0
  134. package/i18n/app.ts.de-DE.po +42 -0
  135. package/i18n/app.ts.en-US.po +42 -0
  136. package/i18n/app.ts.pl-PL.po +42 -0
  137. package/lib/build-info._auto-generated_.d.ts +1 -1
  138. package/lib/build-info._auto-generated_.js +1 -1
  139. package/lib/index._auto-generated_.d.ts +2 -0
  140. package/lib/index._auto-generated_.js +3 -0
  141. package/lib/index._auto-generated_.js.map +1 -1
  142. package/lib/index.d.ts +1 -0
  143. package/lib/index.js +1 -0
  144. package/lib/index.js.map +1 -1
  145. package/lib/lib-info.md +8 -0
  146. package/lib/migrations/migrations-info.md +6 -0
  147. package/lib/package.json +1 -1
  148. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-asset-preview.component.html.de-DE.po +21 -0
  149. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-asset-preview.component.html.en-US.po +21 -0
  150. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-asset-preview.component.html.pl-PL.po +21 -0
  151. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-asset-preview.component.ts.de-DE.po +13 -0
  152. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-asset-preview.component.ts.en-US.po +13 -0
  153. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-asset-preview.component.ts.pl-PL.po +13 -0
  154. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.component.html.de-DE.po +17 -0
  155. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.component.html.en-US.po +17 -0
  156. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.component.html.pl-PL.po +17 -0
  157. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.component.ts.de-DE.po +17 -0
  158. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.component.ts.en-US.po +17 -0
  159. package/lib/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.component.ts.pl-PL.po +17 -0
  160. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.component.html.de-DE.po +10 -0
  161. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.component.html.en-US.po +10 -0
  162. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.component.html.pl-PL.po +10 -0
  163. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.component.ts.de-DE.po +5 -0
  164. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.component.ts.en-US.po +5 -0
  165. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.component.ts.pl-PL.po +5 -0
  166. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.routes.ts.de-DE.po +33 -0
  167. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.routes.ts.en-US.po +33 -0
  168. package/lib/taon-cms-backoffice/i18n/taon-cms-backoffice.routes.ts.pl-PL.po +33 -0
  169. package/lib/taon-cms-backoffice/index.d.ts +1 -1
  170. package/lib/taon-cms-backoffice/index.js +2 -2
  171. package/lib/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.component.html.de-DE.po +30 -0
  172. package/lib/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.component.html.en-US.po +30 -0
  173. package/lib/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.component.html.pl-PL.po +30 -0
  174. package/lib/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.component.ts.de-DE.po +41 -0
  175. package/lib/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.component.ts.en-US.po +41 -0
  176. package/lib/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.component.ts.pl-PL.po +41 -0
  177. package/lib/taon-cms-backoffice/taon-cms-website-features/index.d.ts +1 -1
  178. package/lib/taon-cms-backoffice/taon-cms-website-features/index.js +2 -2
  179. package/lib/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.constants.ts.de-DE.po +9 -0
  180. package/lib/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.constants.ts.en-US.po +9 -0
  181. package/lib/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.constants.ts.pl-PL.po +9 -0
  182. package/lib/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.controller.ts.de-DE.po +9 -0
  183. package/lib/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.controller.ts.en-US.po +9 -0
  184. package/lib/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.controller.ts.pl-PL.po +9 -0
  185. package/lib/taon-cms-category/i18n/taon-cms-category.controller.ts.de-DE.po +9 -0
  186. package/lib/taon-cms-category/i18n/taon-cms-category.controller.ts.en-US.po +9 -0
  187. package/lib/taon-cms-category/i18n/taon-cms-category.controller.ts.pl-PL.po +9 -0
  188. package/lib/taon-cms-category/i18n/taon-cms-category.repository.ts.de-DE.po +17 -0
  189. package/lib/taon-cms-category/i18n/taon-cms-category.repository.ts.en-US.po +17 -0
  190. package/lib/taon-cms-category/i18n/taon-cms-category.repository.ts.pl-PL.po +17 -0
  191. package/lib/taon-cms-category/i18n/taon-cms-taxonomy.validation.ts.de-DE.po +17 -0
  192. package/lib/taon-cms-category/i18n/taon-cms-taxonomy.validation.ts.en-US.po +17 -0
  193. package/lib/taon-cms-category/i18n/taon-cms-taxonomy.validation.ts.pl-PL.po +17 -0
  194. package/lib/taon-cms-comment/i18n/taon-cms-comment.controller.ts.de-DE.po +77 -0
  195. package/lib/taon-cms-comment/i18n/taon-cms-comment.controller.ts.en-US.po +77 -0
  196. package/lib/taon-cms-comment/i18n/taon-cms-comment.controller.ts.pl-PL.po +77 -0
  197. package/lib/taon-cms-comment/i18n/taon-cms-comments.component.html.de-DE.po +64 -0
  198. package/lib/taon-cms-comment/i18n/taon-cms-comments.component.html.en-US.po +64 -0
  199. package/lib/taon-cms-comment/i18n/taon-cms-comments.component.html.pl-PL.po +64 -0
  200. package/lib/taon-cms-comment/i18n/taon-cms-comments.component.ts.de-DE.po +25 -0
  201. package/lib/taon-cms-comment/i18n/taon-cms-comments.component.ts.en-US.po +25 -0
  202. package/lib/taon-cms-comment/i18n/taon-cms-comments.component.ts.pl-PL.po +25 -0
  203. package/lib/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.component.ts.de-DE.po +5 -0
  204. package/lib/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.component.ts.en-US.po +5 -0
  205. package/lib/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.component.ts.pl-PL.po +5 -0
  206. package/lib/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.routes.ts.de-DE.po +17 -0
  207. package/lib/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.routes.ts.en-US.po +17 -0
  208. package/lib/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.routes.ts.pl-PL.po +17 -0
  209. package/lib/taon-cms-comment/taon-cms-comments-backoffice/index.d.ts +1 -1
  210. package/lib/taon-cms-comment/taon-cms-comments-backoffice/index.js +2 -2
  211. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.component.html.de-DE.po +66 -0
  212. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.component.html.en-US.po +66 -0
  213. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.component.html.pl-PL.po +66 -0
  214. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.component.ts.de-DE.po +41 -0
  215. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.component.ts.en-US.po +41 -0
  216. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.component.ts.pl-PL.po +41 -0
  217. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/index.d.ts +1 -1
  218. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/index.js +2 -2
  219. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.component.html.de-DE.po +50 -0
  220. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.component.html.en-US.po +50 -0
  221. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.component.html.pl-PL.po +50 -0
  222. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.component.ts.de-DE.po +17 -0
  223. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.component.ts.en-US.po +17 -0
  224. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.component.ts.pl-PL.po +17 -0
  225. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/index.d.ts +1 -1
  226. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/index.js +2 -2
  227. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.component.html.de-DE.po +33 -0
  228. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.component.html.en-US.po +33 -0
  229. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.component.html.pl-PL.po +33 -0
  230. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.component.ts.de-DE.po +17 -0
  231. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.component.ts.en-US.po +17 -0
  232. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.component.ts.pl-PL.po +17 -0
  233. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/index.d.ts +1 -1
  234. package/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/index.js +2 -2
  235. package/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.i18n.data.js +1 -1
  236. package/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.po +9 -0
  237. package/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.i18n.data.js +1 -1
  238. package/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.po +9 -0
  239. package/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.i18n.data.js +1 -1
  240. package/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.po +9 -0
  241. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.d.ts +2 -0
  242. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.js +30 -10
  243. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.js.map +1 -1
  244. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.po +49 -0
  245. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.d.ts +2 -0
  246. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.js +26 -10
  247. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.js.map +1 -1
  248. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.po +49 -0
  249. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.d.ts +2 -0
  250. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.js +30 -10
  251. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.js.map +1 -1
  252. package/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.po +49 -0
  253. package/lib/taon-cms-content/taon-cms-content-storage.controller.d.ts +12 -1
  254. package/lib/taon-cms-content/taon-cms-content-storage.controller.js +44 -0
  255. package/lib/taon-cms-content/taon-cms-content-storage.controller.js.map +1 -1
  256. package/lib/taon-cms-content/taon-cms-content.constants.d.ts +4 -0
  257. package/lib/taon-cms-content/taon-cms-content.constants.js +5 -1
  258. package/lib/taon-cms-content/taon-cms-content.constants.js.map +1 -1
  259. package/lib/taon-cms-content/taon-cms-content.controller.d.ts +4 -0
  260. package/lib/taon-cms-content/taon-cms-content.controller.js +71 -0
  261. package/lib/taon-cms-content/taon-cms-content.controller.js.map +1 -1
  262. package/lib/taon-cms-content/taon-cms-content.models.d.ts +2 -1
  263. package/lib/taon-cms-content/taon-cms-content.models.js +1 -0
  264. package/lib/taon-cms-content/taon-cms-content.models.js.map +1 -1
  265. package/lib/taon-cms-content/taon-cms-content.provider.d.ts +4 -0
  266. package/lib/taon-cms-content/taon-cms-content.provider.js +4 -0
  267. package/lib/taon-cms-content/taon-cms-content.provider.js.map +1 -1
  268. package/lib/taon-cms-content/taon-cms-content.repository.js +17 -1
  269. package/lib/taon-cms-content/taon-cms-content.repository.js.map +1 -1
  270. package/lib/taon-cms-content/taon-cms-content.validation.js +1 -1
  271. package/lib/taon-cms-content/taon-cms-content.validation.js.map +1 -1
  272. package/lib/taon-cms-tag/i18n/taon-cms-tag.controller.ts.de-DE.po +9 -0
  273. package/lib/taon-cms-tag/i18n/taon-cms-tag.controller.ts.en-US.po +9 -0
  274. package/lib/taon-cms-tag/i18n/taon-cms-tag.controller.ts.pl-PL.po +9 -0
  275. package/lib/taon-cms-tag/i18n/taon-cms-tag.repository.ts.de-DE.po +13 -0
  276. package/lib/taon-cms-tag/i18n/taon-cms-tag.repository.ts.en-US.po +13 -0
  277. package/lib/taon-cms-tag/i18n/taon-cms-tag.repository.ts.pl-PL.po +13 -0
  278. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.component.html.de-DE.po +69 -0
  279. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.component.html.en-US.po +69 -0
  280. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.component.html.pl-PL.po +69 -0
  281. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.component.ts.de-DE.po +54 -0
  282. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.component.ts.en-US.po +54 -0
  283. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.component.ts.pl-PL.po +54 -0
  284. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-quick-create-dialog.component.ts.de-DE.po +33 -0
  285. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-quick-create-dialog.component.ts.en-US.po +33 -0
  286. package/lib/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-quick-create-dialog.component.ts.pl-PL.po +33 -0
  287. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.component.html.de-DE.po +43 -0
  288. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.component.html.en-US.po +43 -0
  289. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.component.html.pl-PL.po +43 -0
  290. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.component.ts.de-DE.po +21 -0
  291. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.component.ts.en-US.po +21 -0
  292. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.component.ts.pl-PL.po +21 -0
  293. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-contents.component.html.de-DE.po +30 -0
  294. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-contents.component.html.en-US.po +30 -0
  295. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-contents.component.html.pl-PL.po +30 -0
  296. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-contents.component.ts.de-DE.po +29 -0
  297. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-contents.component.ts.en-US.po +29 -0
  298. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-contents.component.ts.pl-PL.po +29 -0
  299. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-cover.component.ts.de-DE.po +9 -0
  300. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-cover.component.ts.en-US.po +9 -0
  301. package/lib/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-cover.component.ts.pl-PL.po +9 -0
  302. package/lib/taon-posts/taon-cms-course-backoffice/index.d.ts +1 -1
  303. package/lib/taon-posts/taon-cms-course-backoffice/index.js +2 -2
  304. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-choice-dialog.component.html.de-DE.po +13 -0
  305. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-choice-dialog.component.html.en-US.po +13 -0
  306. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-choice-dialog.component.html.pl-PL.po +13 -0
  307. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-choice-dialog.component.ts.de-DE.po +5 -0
  308. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-choice-dialog.component.ts.en-US.po +5 -0
  309. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-choice-dialog.component.ts.pl-PL.po +5 -0
  310. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-thumbnail.component.ts.de-DE.po +9 -0
  311. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-thumbnail.component.ts.en-US.po +9 -0
  312. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post-thumbnail.component.ts.pl-PL.po +9 -0
  313. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.d.ts +2 -0
  314. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.js +151 -136
  315. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.js.map +1 -1
  316. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.po +505 -0
  317. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.d.ts +2 -0
  318. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.js +148 -136
  319. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.js.map +1 -1
  320. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.po +505 -0
  321. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.d.ts +2 -0
  322. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.js +151 -136
  323. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.js.map +1 -1
  324. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.po +505 -0
  325. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.d.ts +10 -2
  326. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.js +88 -69
  327. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.js.map +1 -1
  328. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.po +291 -0
  329. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.d.ts +4 -0
  330. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.js +86 -69
  331. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.js.map +1 -1
  332. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.po +291 -0
  333. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.d.ts +10 -2
  334. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.js +88 -69
  335. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.js.map +1 -1
  336. package/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.po +291 -0
  337. package/lib/taon-posts/taon-cms-post/index.d.ts +1 -1
  338. package/lib/taon-posts/taon-cms-post/index.js +2 -2
  339. package/lib/taon-posts/taon-cms-post/taon-cms-thumbnail.js +1 -0
  340. package/lib/taon-posts/taon-cms-post/taon-cms-thumbnail.js.map +1 -1
  341. package/lib/taon-posts/taon-cms-post-details-page/i18n/taon-cms-post-details-page.component.ts.de-DE.po +13 -0
  342. package/lib/taon-posts/taon-cms-post-details-page/i18n/taon-cms-post-details-page.component.ts.en-US.po +13 -0
  343. package/lib/taon-posts/taon-cms-post-details-page/i18n/taon-cms-post-details-page.component.ts.pl-PL.po +13 -0
  344. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-post-table-actions.directive.ts.de-DE.po +69 -0
  345. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-post-table-actions.directive.ts.en-US.po +69 -0
  346. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-post-table-actions.directive.ts.pl-PL.po +69 -0
  347. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.component.html.de-DE.po +41 -0
  348. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.component.html.en-US.po +41 -0
  349. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.component.html.pl-PL.po +41 -0
  350. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.component.ts.de-DE.po +17 -0
  351. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.component.ts.en-US.po +17 -0
  352. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.component.ts.pl-PL.po +17 -0
  353. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.routes.ts.de-DE.po +13 -0
  354. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.routes.ts.en-US.po +13 -0
  355. package/lib/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.routes.ts.pl-PL.po +13 -0
  356. package/lib/taon-posts/taon-cms-posts-backoffice/index.d.ts +1 -1
  357. package/lib/taon-posts/taon-cms-posts-backoffice/index.js +2 -2
  358. package/lib/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.component.html.de-DE.po +56 -0
  359. package/lib/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.component.html.en-US.po +56 -0
  360. package/lib/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.component.html.pl-PL.po +56 -0
  361. package/lib/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.component.ts.de-DE.po +17 -0
  362. package/lib/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.component.ts.en-US.po +17 -0
  363. package/lib/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.component.ts.pl-PL.po +17 -0
  364. package/lib/taon-posts/taon-cms-revision-chooser/index.d.ts +1 -1
  365. package/lib/taon-posts/taon-cms-revision-chooser/index.js +2 -2
  366. package/lib/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.component.html.de-DE.po +38 -0
  367. package/lib/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.component.html.en-US.po +38 -0
  368. package/lib/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.component.html.pl-PL.po +38 -0
  369. package/lib/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.component.ts.de-DE.po +13 -0
  370. package/lib/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.component.ts.en-US.po +13 -0
  371. package/lib/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.component.ts.pl-PL.po +13 -0
  372. package/lib/taon-posts/taon-related-post-chooser/index.d.ts +1 -1
  373. package/lib/taon-posts/taon-related-post-chooser/index.js +2 -2
  374. package/lib/taon-spreadsheet-table/index.d.ts +2 -0
  375. package/lib/taon-spreadsheet-table/index.js +20 -0
  376. package/lib/taon-spreadsheet-table/index.js.map +1 -0
  377. package/lib/taon-spreadsheet-table/taon-spreadsheet-table.models.d.ts +12 -0
  378. package/lib/taon-spreadsheet-table/taon-spreadsheet-table.models.js +3 -0
  379. package/lib/taon-spreadsheet-table/taon-spreadsheet-table.models.js.map +1 -0
  380. package/lib/taon-spreadsheet-table/taon-spreadsheet-table.utils.d.ts +9 -0
  381. package/lib/taon-spreadsheet-table/taon-spreadsheet-table.utils.js +393 -0
  382. package/lib/taon-spreadsheet-table/taon-spreadsheet-table.utils.js.map +1 -0
  383. package/lib-esm/lib/build-info._auto-generated_.d.ts +1 -1
  384. package/lib-esm/lib/build-info._auto-generated_.js +1 -1
  385. package/lib-esm/lib/index._auto-generated_.d.ts +2 -0
  386. package/lib-esm/lib/index._auto-generated_.js +3 -0
  387. package/lib-esm/lib/index._auto-generated_.js.map +1 -1
  388. package/lib-esm/lib/index.d.ts +1 -0
  389. package/lib-esm/lib/index.js +1 -0
  390. package/lib-esm/lib/index.js.map +1 -1
  391. package/lib-esm/lib/taon-cms-backoffice/index.d.ts +1 -1
  392. package/lib-esm/lib/taon-cms-backoffice/index.js +1 -1
  393. package/lib-esm/lib/taon-cms-backoffice/taon-cms-website-features/index.d.ts +1 -1
  394. package/lib-esm/lib/taon-cms-backoffice/taon-cms-website-features/index.js +1 -1
  395. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/index.d.ts +1 -1
  396. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/index.js +1 -1
  397. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/index.d.ts +1 -1
  398. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/index.js +1 -1
  399. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/index.d.ts +1 -1
  400. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/index.js +1 -1
  401. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/index.d.ts +1 -1
  402. package/lib-esm/lib/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/index.js +1 -1
  403. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.i18n.data.js +1 -1
  404. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.i18n.data.js +1 -1
  405. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.i18n.data.js +1 -1
  406. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.d.ts +2 -0
  407. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.js +30 -10
  408. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.js.map +1 -1
  409. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.d.ts +2 -0
  410. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.js +26 -10
  411. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.js.map +1 -1
  412. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.d.ts +2 -0
  413. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.js +30 -10
  414. package/lib-esm/lib/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.js.map +1 -1
  415. package/lib-esm/lib/taon-cms-content/taon-cms-content-storage.controller.d.ts +12 -1
  416. package/lib-esm/lib/taon-cms-content/taon-cms-content-storage.controller.js +44 -0
  417. package/lib-esm/lib/taon-cms-content/taon-cms-content-storage.controller.js.map +1 -1
  418. package/lib-esm/lib/taon-cms-content/taon-cms-content.constants.d.ts +4 -0
  419. package/lib-esm/lib/taon-cms-content/taon-cms-content.constants.js +4 -0
  420. package/lib-esm/lib/taon-cms-content/taon-cms-content.constants.js.map +1 -1
  421. package/lib-esm/lib/taon-cms-content/taon-cms-content.controller.d.ts +4 -0
  422. package/lib-esm/lib/taon-cms-content/taon-cms-content.controller.js +71 -0
  423. package/lib-esm/lib/taon-cms-content/taon-cms-content.controller.js.map +1 -1
  424. package/lib-esm/lib/taon-cms-content/taon-cms-content.models.d.ts +2 -1
  425. package/lib-esm/lib/taon-cms-content/taon-cms-content.models.js +1 -0
  426. package/lib-esm/lib/taon-cms-content/taon-cms-content.models.js.map +1 -1
  427. package/lib-esm/lib/taon-cms-content/taon-cms-content.provider.d.ts +4 -0
  428. package/lib-esm/lib/taon-cms-content/taon-cms-content.provider.js +4 -0
  429. package/lib-esm/lib/taon-cms-content/taon-cms-content.provider.js.map +1 -1
  430. package/lib-esm/lib/taon-cms-content/taon-cms-content.repository.js +17 -1
  431. package/lib-esm/lib/taon-cms-content/taon-cms-content.repository.js.map +1 -1
  432. package/lib-esm/lib/taon-cms-content/taon-cms-content.validation.js +1 -1
  433. package/lib-esm/lib/taon-cms-content/taon-cms-content.validation.js.map +1 -1
  434. package/lib-esm/lib/taon-posts/taon-cms-course-backoffice/index.d.ts +1 -1
  435. package/lib-esm/lib/taon-posts/taon-cms-course-backoffice/index.js +1 -1
  436. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.d.ts +2 -0
  437. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.js +151 -136
  438. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.js.map +1 -1
  439. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.d.ts +2 -0
  440. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.js +148 -136
  441. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.js.map +1 -1
  442. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.d.ts +2 -0
  443. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.js +151 -136
  444. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.js.map +1 -1
  445. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.d.ts +10 -2
  446. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.js +88 -69
  447. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.js.map +1 -1
  448. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.d.ts +4 -0
  449. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.js +86 -69
  450. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.js.map +1 -1
  451. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.d.ts +10 -2
  452. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.js +88 -69
  453. package/lib-esm/lib/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.js.map +1 -1
  454. package/lib-esm/lib/taon-posts/taon-cms-post/index.d.ts +1 -1
  455. package/lib-esm/lib/taon-posts/taon-cms-post/index.js +1 -1
  456. package/lib-esm/lib/taon-posts/taon-cms-post/taon-cms-thumbnail.js +1 -0
  457. package/lib-esm/lib/taon-posts/taon-cms-post/taon-cms-thumbnail.js.map +1 -1
  458. package/lib-esm/lib/taon-posts/taon-cms-posts-backoffice/index.d.ts +1 -1
  459. package/lib-esm/lib/taon-posts/taon-cms-posts-backoffice/index.js +1 -1
  460. package/lib-esm/lib/taon-posts/taon-cms-revision-chooser/index.d.ts +1 -1
  461. package/lib-esm/lib/taon-posts/taon-cms-revision-chooser/index.js +1 -1
  462. package/lib-esm/lib/taon-posts/taon-related-post-chooser/index.d.ts +1 -1
  463. package/lib-esm/lib/taon-posts/taon-related-post-chooser/index.js +1 -1
  464. package/lib-esm/lib/taon-spreadsheet-table/index.d.ts +2 -0
  465. package/lib-esm/lib/taon-spreadsheet-table/index.js +4 -0
  466. package/lib-esm/lib/taon-spreadsheet-table/index.js.map +1 -0
  467. package/lib-esm/lib/taon-spreadsheet-table/taon-spreadsheet-table.models.d.ts +12 -0
  468. package/lib-esm/lib/taon-spreadsheet-table/taon-spreadsheet-table.models.js +2 -0
  469. package/lib-esm/lib/taon-spreadsheet-table/taon-spreadsheet-table.models.js.map +1 -0
  470. package/lib-esm/lib/taon-spreadsheet-table/taon-spreadsheet-table.utils.d.ts +9 -0
  471. package/lib-esm/lib/taon-spreadsheet-table/taon-spreadsheet-table.utils.js +383 -0
  472. package/lib-esm/lib/taon-spreadsheet-table/taon-spreadsheet-table.utils.js.map +1 -0
  473. package/lib-prod/build-info._auto-generated_.d.ts +1 -1
  474. package/lib-prod/build-info._auto-generated_.js +1 -1
  475. package/lib-prod/index._auto-generated_.d.ts +2 -0
  476. package/lib-prod/index._auto-generated_.js +3 -0
  477. package/lib-prod/index._auto-generated_.js.map +1 -1
  478. package/lib-prod/index.d.ts +1 -0
  479. package/lib-prod/index.js +1 -0
  480. package/lib-prod/index.js.map +1 -1
  481. package/lib-prod/package.json +1 -1
  482. package/lib-prod/taon-cms-backoffice/index.d.ts +1 -1
  483. package/lib-prod/taon-cms-backoffice/index.js +1 -1
  484. package/lib-prod/taon-cms-backoffice/taon-cms-website-features/index.d.ts +1 -1
  485. package/lib-prod/taon-cms-backoffice/taon-cms-website-features/index.js +1 -1
  486. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/index.d.ts +1 -1
  487. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/index.js +1 -1
  488. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/index.d.ts +1 -1
  489. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/index.js +1 -1
  490. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/index.d.ts +1 -1
  491. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/index.js +1 -1
  492. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/index.d.ts +1 -1
  493. package/lib-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/index.js +1 -1
  494. package/lib-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.i18n.data.js +1 -1
  495. package/lib-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.i18n.data.js +1 -1
  496. package/lib-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.i18n.data.js +1 -1
  497. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.d.ts +2 -0
  498. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.js +30 -10
  499. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.js.map +1 -1
  500. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.d.ts +2 -0
  501. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.js +26 -10
  502. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.js.map +1 -1
  503. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.d.ts +2 -0
  504. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.js +30 -10
  505. package/lib-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.js.map +1 -1
  506. package/lib-prod/taon-cms-content/taon-cms-content-storage.controller.d.ts +12 -1
  507. package/lib-prod/taon-cms-content/taon-cms-content-storage.controller.js +44 -0
  508. package/lib-prod/taon-cms-content/taon-cms-content-storage.controller.js.map +1 -1
  509. package/lib-prod/taon-cms-content/taon-cms-content.constants.d.ts +4 -0
  510. package/lib-prod/taon-cms-content/taon-cms-content.constants.js +4 -0
  511. package/lib-prod/taon-cms-content/taon-cms-content.constants.js.map +1 -1
  512. package/lib-prod/taon-cms-content/taon-cms-content.controller.d.ts +4 -0
  513. package/lib-prod/taon-cms-content/taon-cms-content.controller.js +71 -0
  514. package/lib-prod/taon-cms-content/taon-cms-content.controller.js.map +1 -1
  515. package/lib-prod/taon-cms-content/taon-cms-content.models.d.ts +2 -1
  516. package/lib-prod/taon-cms-content/taon-cms-content.models.js +1 -0
  517. package/lib-prod/taon-cms-content/taon-cms-content.models.js.map +1 -1
  518. package/lib-prod/taon-cms-content/taon-cms-content.provider.d.ts +4 -0
  519. package/lib-prod/taon-cms-content/taon-cms-content.provider.js +4 -0
  520. package/lib-prod/taon-cms-content/taon-cms-content.provider.js.map +1 -1
  521. package/lib-prod/taon-cms-content/taon-cms-content.repository.js +17 -1
  522. package/lib-prod/taon-cms-content/taon-cms-content.repository.js.map +1 -1
  523. package/lib-prod/taon-cms-content/taon-cms-content.validation.js +1 -1
  524. package/lib-prod/taon-cms-content/taon-cms-content.validation.js.map +1 -1
  525. package/lib-prod/taon-posts/taon-cms-course-backoffice/index.d.ts +1 -1
  526. package/lib-prod/taon-posts/taon-cms-course-backoffice/index.js +1 -1
  527. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.d.ts +2 -0
  528. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.js +151 -136
  529. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.js.map +1 -1
  530. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.d.ts +2 -0
  531. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.js +148 -136
  532. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.js.map +1 -1
  533. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.d.ts +2 -0
  534. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.js +151 -136
  535. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.js.map +1 -1
  536. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.d.ts +10 -2
  537. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.js +88 -69
  538. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.js.map +1 -1
  539. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.d.ts +4 -0
  540. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.js +86 -69
  541. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.js.map +1 -1
  542. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.d.ts +10 -2
  543. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.js +88 -69
  544. package/lib-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.js.map +1 -1
  545. package/lib-prod/taon-posts/taon-cms-post/index.d.ts +1 -1
  546. package/lib-prod/taon-posts/taon-cms-post/index.js +1 -1
  547. package/lib-prod/taon-posts/taon-cms-post/taon-cms-thumbnail.js +1 -0
  548. package/lib-prod/taon-posts/taon-cms-post/taon-cms-thumbnail.js.map +1 -1
  549. package/lib-prod/taon-posts/taon-cms-posts-backoffice/index.d.ts +1 -1
  550. package/lib-prod/taon-posts/taon-cms-posts-backoffice/index.js +1 -1
  551. package/lib-prod/taon-posts/taon-cms-revision-chooser/index.d.ts +1 -1
  552. package/lib-prod/taon-posts/taon-cms-revision-chooser/index.js +1 -1
  553. package/lib-prod/taon-posts/taon-related-post-chooser/index.d.ts +1 -1
  554. package/lib-prod/taon-posts/taon-related-post-chooser/index.js +1 -1
  555. package/lib-prod/taon-spreadsheet-table/index.d.ts +2 -0
  556. package/lib-prod/taon-spreadsheet-table/index.js +4 -0
  557. package/lib-prod/taon-spreadsheet-table/index.js.map +1 -0
  558. package/lib-prod/taon-spreadsheet-table/taon-spreadsheet-table.models.d.ts +12 -0
  559. package/lib-prod/taon-spreadsheet-table/taon-spreadsheet-table.models.js +2 -0
  560. package/lib-prod/taon-spreadsheet-table/taon-spreadsheet-table.models.js.map +1 -0
  561. package/lib-prod/taon-spreadsheet-table/taon-spreadsheet-table.utils.d.ts +9 -0
  562. package/lib-prod/taon-spreadsheet-table/taon-spreadsheet-table.utils.js +383 -0
  563. package/lib-prod/taon-spreadsheet-table/taon-spreadsheet-table.utils.js.map +1 -0
  564. package/package.json +1 -1
  565. package/scss/lib/taon-posts/taon-cms-post/taon-cms-post.component.scss +5 -0
  566. package/scss/lib/taon-spreadsheet-table/taon-spreadsheet-table.component.scss +177 -0
  567. package/ts-source/browser-code/build-info._auto-generated_.ts +1 -1
  568. package/ts-source/browser-code/index._auto-generated_.ts +3 -0
  569. package/ts-source/browser-code/index.ts +1 -0
  570. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.i18n.data.ts +1 -1
  571. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.po +1 -1
  572. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.i18n.data.ts +1 -1
  573. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.po +1 -1
  574. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.i18n.data.ts +1 -1
  575. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.po +1 -1
  576. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.ts +32 -10
  577. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.po +20 -10
  578. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.ts +28 -10
  579. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.po +20 -10
  580. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.ts +32 -10
  581. package/ts-source/browser-code/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.po +20 -10
  582. package/ts-source/browser-code/taon-cms-content/taon-cms-content-api.service.ts +40 -42
  583. package/ts-source/browser-code/taon-cms-content/taon-cms-content-storage.controller.ts +15 -1
  584. package/ts-source/browser-code/taon-cms-content/taon-cms-content.config.service.ts +7 -0
  585. package/ts-source/browser-code/taon-cms-content/taon-cms-content.constants.ts +1 -0
  586. package/ts-source/browser-code/taon-cms-content/taon-cms-content.controller.ts +22 -0
  587. package/ts-source/browser-code/taon-cms-content/taon-cms-content.models.ts +2 -1
  588. package/ts-source/browser-code/taon-cms-content/taon-cms-content.provider.ts +1 -0
  589. package/ts-source/browser-code/taon-cms-content/taon-cms-content.repository.ts +16 -0
  590. package/ts-source/browser-code/taon-cms-content/taon-cms-content.validation.ts +1 -1
  591. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.ts +153 -136
  592. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.po +145 -136
  593. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.ts +150 -136
  594. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.po +145 -136
  595. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.ts +153 -136
  596. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.po +145 -136
  597. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.ts +92 -69
  598. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.po +85 -69
  599. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.ts +90 -69
  600. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.po +85 -69
  601. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.ts +92 -69
  602. package/ts-source/browser-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.po +85 -69
  603. package/ts-source/browser-code/taon-posts/taon-cms-post/taon-cms-post.component.html +50 -5
  604. package/ts-source/browser-code/taon-posts/taon-cms-post/taon-cms-post.component.scss +5 -0
  605. package/ts-source/browser-code/taon-posts/taon-cms-post/taon-cms-post.component.ts +169 -14
  606. package/ts-source/browser-code/taon-posts/taon-cms-post/taon-cms-thumbnail.ts +1 -0
  607. package/ts-source/browser-code/taon-spreadsheet-table/index.ts +15 -0
  608. package/ts-source/browser-code/taon-spreadsheet-table/taon-spreadsheet-table.component.html +377 -0
  609. package/ts-source/browser-code/taon-spreadsheet-table/taon-spreadsheet-table.component.scss +188 -0
  610. package/ts-source/browser-code/taon-spreadsheet-table/taon-spreadsheet-table.component.ts +621 -0
  611. package/ts-source/browser-code/taon-spreadsheet-table/taon-spreadsheet-table.models.ts +24 -0
  612. package/ts-source/browser-code/taon-spreadsheet-table/taon-spreadsheet-table.utils.ts +399 -0
  613. package/ts-source/browser-code-prod/build-info._auto-generated_.ts +1 -1
  614. package/ts-source/browser-code-prod/index._auto-generated_.ts +3 -0
  615. package/ts-source/browser-code-prod/index.ts +1 -0
  616. package/ts-source/browser-code-prod/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.translation.ts +1 -1
  617. package/ts-source/browser-code-prod/taon-cms-backoffice/i18n/taon-cms-backoffice.translation.ts +1 -1
  618. package/ts-source/browser-code-prod/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.translation.ts +1 -1
  619. package/ts-source/browser-code-prod/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.translation.ts +1 -1
  620. package/ts-source/browser-code-prod/taon-cms-category/i18n/taon-cms-category.translation.ts +1 -1
  621. package/ts-source/browser-code-prod/taon-cms-comment/i18n/taon-cms-comment.translation.ts +1 -1
  622. package/ts-source/browser-code-prod/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.translation.ts +1 -1
  623. package/ts-source/browser-code-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.translation.ts +1 -1
  624. package/ts-source/browser-code-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.translation.ts +1 -1
  625. package/ts-source/browser-code-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.translation.ts +1 -1
  626. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.i18n.data.ts +1 -1
  627. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.po +1 -1
  628. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.i18n.data.ts +1 -1
  629. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.po +1 -1
  630. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.i18n.data.ts +1 -1
  631. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.po +1 -1
  632. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.ts +32 -10
  633. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.po +20 -10
  634. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.ts +28 -10
  635. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.po +20 -10
  636. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.ts +32 -10
  637. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.po +20 -10
  638. package/ts-source/browser-code-prod/taon-cms-content/i18n/taon-cms-content.translation.ts +1 -1
  639. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content-api.service.ts +40 -42
  640. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content-storage.controller.ts +15 -1
  641. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content.config.service.ts +7 -0
  642. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content.constants.ts +1 -0
  643. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content.controller.ts +22 -0
  644. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content.models.ts +2 -1
  645. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content.provider.ts +1 -0
  646. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content.repository.ts +16 -0
  647. package/ts-source/browser-code-prod/taon-cms-content/taon-cms-content.validation.ts +1 -1
  648. package/ts-source/browser-code-prod/taon-cms-tag/i18n/taon-cms-tag.translation.ts +1 -1
  649. package/ts-source/browser-code-prod/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.translation.ts +1 -1
  650. package/ts-source/browser-code-prod/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.translation.ts +1 -1
  651. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.ts +153 -136
  652. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.po +145 -136
  653. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.ts +150 -136
  654. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.po +145 -136
  655. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.ts +153 -136
  656. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.po +145 -136
  657. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.ts +92 -69
  658. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.po +85 -69
  659. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.ts +90 -69
  660. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.po +85 -69
  661. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.ts +92 -69
  662. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.po +85 -69
  663. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.translation.ts +1 -1
  664. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/taon-cms-post.component.html +50 -5
  665. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/taon-cms-post.component.scss +5 -0
  666. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/taon-cms-post.component.ts +169 -14
  667. package/ts-source/browser-code-prod/taon-posts/taon-cms-post/taon-cms-thumbnail.ts +1 -0
  668. package/ts-source/browser-code-prod/taon-posts/taon-cms-post-details-page/i18n/taon-cms-post-details-page.translation.ts +1 -1
  669. package/ts-source/browser-code-prod/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.translation.ts +1 -1
  670. package/ts-source/browser-code-prod/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.translation.ts +1 -1
  671. package/ts-source/browser-code-prod/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.translation.ts +1 -1
  672. package/ts-source/browser-code-prod/taon-spreadsheet-table/index.split-namespaces.json +4 -0
  673. package/ts-source/browser-code-prod/taon-spreadsheet-table/index.ts +15 -0
  674. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.html +377 -0
  675. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.scss +188 -0
  676. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.split-namespaces.json +4 -0
  677. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.ts +621 -0
  678. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.models.split-namespaces.json +4 -0
  679. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.models.ts +24 -0
  680. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.utils.split-namespaces.json +4 -0
  681. package/ts-source/browser-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.utils.ts +399 -0
  682. package/ts-source/websql-code/build-info._auto-generated_.ts +1 -1
  683. package/ts-source/websql-code/index._auto-generated_.ts +3 -0
  684. package/ts-source/websql-code/index.ts +1 -0
  685. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.i18n.data.ts +1 -1
  686. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.po +1 -1
  687. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.i18n.data.ts +1 -1
  688. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.po +1 -1
  689. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.i18n.data.ts +1 -1
  690. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.po +1 -1
  691. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.ts +32 -10
  692. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.po +20 -10
  693. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.ts +28 -10
  694. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.po +20 -10
  695. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.ts +32 -10
  696. package/ts-source/websql-code/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.po +20 -10
  697. package/ts-source/websql-code/taon-cms-content/taon-cms-content-api.service.ts +40 -42
  698. package/ts-source/websql-code/taon-cms-content/taon-cms-content-storage.controller.ts +15 -1
  699. package/ts-source/websql-code/taon-cms-content/taon-cms-content.config.service.ts +7 -0
  700. package/ts-source/websql-code/taon-cms-content/taon-cms-content.constants.ts +1 -0
  701. package/ts-source/websql-code/taon-cms-content/taon-cms-content.controller.ts +53 -0
  702. package/ts-source/websql-code/taon-cms-content/taon-cms-content.models.ts +2 -1
  703. package/ts-source/websql-code/taon-cms-content/taon-cms-content.provider.ts +1 -0
  704. package/ts-source/websql-code/taon-cms-content/taon-cms-content.repository.ts +17 -1
  705. package/ts-source/websql-code/taon-cms-content/taon-cms-content.validation.ts +1 -1
  706. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.ts +153 -136
  707. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.po +145 -136
  708. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.ts +150 -136
  709. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.po +145 -136
  710. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.ts +153 -136
  711. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.po +145 -136
  712. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.ts +92 -69
  713. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.po +85 -69
  714. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.ts +90 -69
  715. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.po +85 -69
  716. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.ts +92 -69
  717. package/ts-source/websql-code/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.po +85 -69
  718. package/ts-source/websql-code/taon-posts/taon-cms-post/taon-cms-post.component.html +50 -5
  719. package/ts-source/websql-code/taon-posts/taon-cms-post/taon-cms-post.component.scss +5 -0
  720. package/ts-source/websql-code/taon-posts/taon-cms-post/taon-cms-post.component.ts +169 -14
  721. package/ts-source/websql-code/taon-posts/taon-cms-post/taon-cms-thumbnail.ts +1 -0
  722. package/ts-source/websql-code/taon-spreadsheet-table/index.ts +15 -0
  723. package/ts-source/websql-code/taon-spreadsheet-table/taon-spreadsheet-table.component.html +377 -0
  724. package/ts-source/websql-code/taon-spreadsheet-table/taon-spreadsheet-table.component.scss +188 -0
  725. package/ts-source/websql-code/taon-spreadsheet-table/taon-spreadsheet-table.component.ts +621 -0
  726. package/ts-source/websql-code/taon-spreadsheet-table/taon-spreadsheet-table.models.ts +24 -0
  727. package/ts-source/websql-code/taon-spreadsheet-table/taon-spreadsheet-table.utils.ts +399 -0
  728. package/ts-source/websql-code-prod/build-info._auto-generated_.ts +1 -1
  729. package/ts-source/websql-code-prod/index._auto-generated_.ts +3 -0
  730. package/ts-source/websql-code-prod/index.ts +1 -0
  731. package/ts-source/websql-code-prod/taon-cms-assets-backoffice/i18n/taon-cms-assets-backoffice.translation.ts +1 -1
  732. package/ts-source/websql-code-prod/taon-cms-backoffice/i18n/taon-cms-backoffice.translation.ts +1 -1
  733. package/ts-source/websql-code-prod/taon-cms-backoffice/taon-cms-website-features/i18n/taon-cms-website-features.translation.ts +1 -1
  734. package/ts-source/websql-code-prod/taon-cms-backoffice/taon-cms-website-features/taon-cms-website-feature/i18n/taon-cms-website-feature.translation.ts +1 -1
  735. package/ts-source/websql-code-prod/taon-cms-category/i18n/taon-cms-category.translation.ts +1 -1
  736. package/ts-source/websql-code-prod/taon-cms-comment/i18n/taon-cms-comment.translation.ts +1 -1
  737. package/ts-source/websql-code-prod/taon-cms-comment/taon-cms-comments-backoffice/i18n/taon-cms-comments-backoffice.translation.ts +1 -1
  738. package/ts-source/websql-code-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-all-preview-backoffice/i18n/taon-cms-comments-all-preview-backoffice.translation.ts +1 -1
  739. package/ts-source/websql-code-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-blocked-users/i18n/taon-cms-comments-blocked-users.translation.ts +1 -1
  740. package/ts-source/websql-code-prod/taon-cms-comment/taon-cms-comments-backoffice/taon-cms-comments-filters-settings/i18n/taon-cms-comments-filters-settings.translation.ts +1 -1
  741. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.i18n.data.ts +1 -1
  742. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.de-DE.po +1 -1
  743. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.i18n.data.ts +1 -1
  744. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.en-US.po +1 -1
  745. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.i18n.data.ts +1 -1
  746. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content-storage.controller.ts.pl-PL.po +1 -1
  747. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.i18n.data.ts +32 -10
  748. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.de-DE.po +20 -10
  749. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.i18n.data.ts +28 -10
  750. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.en-US.po +20 -10
  751. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.i18n.data.ts +32 -10
  752. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content.controller.ts.pl-PL.po +20 -10
  753. package/ts-source/websql-code-prod/taon-cms-content/i18n/taon-cms-content.translation.ts +1 -1
  754. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content-api.service.ts +40 -42
  755. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content-storage.controller.ts +15 -1
  756. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content.config.service.ts +7 -0
  757. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content.constants.ts +1 -0
  758. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content.controller.ts +53 -0
  759. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content.models.ts +2 -1
  760. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content.provider.ts +1 -0
  761. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content.repository.ts +17 -1
  762. package/ts-source/websql-code-prod/taon-cms-content/taon-cms-content.validation.ts +1 -1
  763. package/ts-source/websql-code-prod/taon-cms-tag/i18n/taon-cms-tag.translation.ts +1 -1
  764. package/ts-source/websql-code-prod/taon-cms-taxonomy-backoffice/i18n/taon-cms-taxonomy-backoffice.translation.ts +1 -1
  765. package/ts-source/websql-code-prod/taon-posts/taon-cms-course-backoffice/i18n/taon-cms-course-backoffice.translation.ts +1 -1
  766. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.i18n.data.ts +153 -136
  767. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.de-DE.po +145 -136
  768. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.i18n.data.ts +150 -136
  769. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.en-US.po +145 -136
  770. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.i18n.data.ts +153 -136
  771. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.html.pl-PL.po +145 -136
  772. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.i18n.data.ts +92 -69
  773. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.de-DE.po +85 -69
  774. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.i18n.data.ts +90 -69
  775. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.en-US.po +85 -69
  776. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.i18n.data.ts +92 -69
  777. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.component.ts.pl-PL.po +85 -69
  778. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/i18n/taon-cms-post.translation.ts +1 -1
  779. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/taon-cms-post.component.html +50 -5
  780. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/taon-cms-post.component.scss +5 -0
  781. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/taon-cms-post.component.ts +169 -14
  782. package/ts-source/websql-code-prod/taon-posts/taon-cms-post/taon-cms-thumbnail.ts +1 -0
  783. package/ts-source/websql-code-prod/taon-posts/taon-cms-post-details-page/i18n/taon-cms-post-details-page.translation.ts +1 -1
  784. package/ts-source/websql-code-prod/taon-posts/taon-cms-posts-backoffice/i18n/taon-cms-posts-backoffice.translation.ts +1 -1
  785. package/ts-source/websql-code-prod/taon-posts/taon-cms-revision-chooser/i18n/taon-cms-revision-chooser.translation.ts +1 -1
  786. package/ts-source/websql-code-prod/taon-posts/taon-related-post-chooser/i18n/taon-related-post-chooser.translation.ts +1 -1
  787. package/ts-source/websql-code-prod/taon-spreadsheet-table/index.split-namespaces.json +4 -0
  788. package/ts-source/websql-code-prod/taon-spreadsheet-table/index.ts +15 -0
  789. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.html +377 -0
  790. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.scss +188 -0
  791. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.split-namespaces.json +4 -0
  792. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.component.ts +621 -0
  793. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.models.split-namespaces.json +4 -0
  794. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.models.ts +24 -0
  795. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.utils.split-namespaces.json +4 -0
  796. package/ts-source/websql-code-prod/taon-spreadsheet-table/taon-spreadsheet-table.utils.ts +399 -0
  797. package/websql/fesm2022/{cms-websql-taon-cms-post.component.html.de-DE.i18n.data-C5Nxovtl.mjs → cms-websql-taon-cms-post.component.html.de-DE.i18n.data-BdxSK5vd.mjs} +152 -137
  798. package/websql/fesm2022/cms-websql-taon-cms-post.component.html.de-DE.i18n.data-BdxSK5vd.mjs.map +1 -0
  799. package/websql/fesm2022/{cms-websql-taon-cms-post.component.html.en-US.i18n.data-BTWsCDp3.mjs → cms-websql-taon-cms-post.component.html.en-US.i18n.data-YiL6-4Aq.mjs} +149 -137
  800. package/websql/fesm2022/cms-websql-taon-cms-post.component.html.en-US.i18n.data-YiL6-4Aq.mjs.map +1 -0
  801. package/websql/fesm2022/{cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-CZhAYAQh.mjs → cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-BzEKPeB_.mjs} +152 -137
  802. package/websql/fesm2022/cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-BzEKPeB_.mjs.map +1 -0
  803. package/websql/fesm2022/{cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-D10xOAxh.mjs → cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-B_w2dmsP.mjs} +89 -70
  804. package/websql/fesm2022/cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-B_w2dmsP.mjs.map +1 -0
  805. package/websql/fesm2022/{cms-websql-taon-cms-post.component.ts.en-US.i18n.data-D1Fh0Nd6.mjs → cms-websql-taon-cms-post.component.ts.en-US.i18n.data-B8Vs_fm6.mjs} +87 -70
  806. package/websql/fesm2022/cms-websql-taon-cms-post.component.ts.en-US.i18n.data-B8Vs_fm6.mjs.map +1 -0
  807. package/websql/fesm2022/{cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-C-sVs8_U.mjs → cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-BaPFft3W.mjs} +89 -70
  808. package/websql/fesm2022/cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-BaPFft3W.mjs.map +1 -0
  809. package/websql/fesm2022/cms-websql.mjs +1459 -158
  810. package/websql/fesm2022/cms-websql.mjs.map +1 -1
  811. package/websql/package.json +1 -1
  812. package/websql/types/cms-websql.d.ts +124 -9
  813. package/websql-prod/fesm2022/{cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-C8qKRpYs.mjs → cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-BC9-axt9.mjs} +152 -137
  814. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-BC9-axt9.mjs.map +1 -0
  815. package/websql-prod/fesm2022/{cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-ClarunPV.mjs → cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-DfkiEwvY.mjs} +149 -137
  816. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-DfkiEwvY.mjs.map +1 -0
  817. package/websql-prod/fesm2022/{cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-YtHY1P_8.mjs → cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-DX25wx9y.mjs} +152 -137
  818. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-DX25wx9y.mjs.map +1 -0
  819. package/websql-prod/fesm2022/{cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-Dv-cf9Kp.mjs → cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-mk6gw3C2.mjs} +89 -70
  820. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-mk6gw3C2.mjs.map +1 -0
  821. package/websql-prod/fesm2022/{cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-CHBqahB1.mjs → cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-BOO0IBXz.mjs} +87 -70
  822. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-BOO0IBXz.mjs.map +1 -0
  823. package/websql-prod/fesm2022/{cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-btgJU4-b.mjs → cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-BcYSlBzW.mjs} +89 -70
  824. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-BcYSlBzW.mjs.map +1 -0
  825. package/websql-prod/fesm2022/cms-websql-prod.mjs +1459 -158
  826. package/websql-prod/fesm2022/cms-websql-prod.mjs.map +1 -1
  827. package/websql-prod/package.json +1 -1
  828. package/websql-prod/types/cms-websql-prod.d.ts +124 -9
  829. package/browser/fesm2022/cms-browser-taon-cms-post.component.html.de-DE.i18n.data-Bv4175Nc.mjs.map +0 -1
  830. package/browser/fesm2022/cms-browser-taon-cms-post.component.html.en-US.i18n.data-CY1znnmK.mjs.map +0 -1
  831. package/browser/fesm2022/cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-BcbuzOtn.mjs.map +0 -1
  832. package/browser/fesm2022/cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DzuYOTSn.mjs.map +0 -1
  833. package/browser/fesm2022/cms-browser-taon-cms-post.component.ts.en-US.i18n.data-MgewtvKr.mjs.map +0 -1
  834. package/browser/fesm2022/cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-B1ILfsGK.mjs.map +0 -1
  835. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DpFy5ZSO.mjs.map +0 -1
  836. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-DLaPQZZj.mjs.map +0 -1
  837. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-alQxEnOY.mjs.map +0 -1
  838. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-4KAdwPda.mjs.map +0 -1
  839. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CyuTYn77.mjs.map +0 -1
  840. package/browser-prod/fesm2022/cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-CgmdVwt_.mjs.map +0 -1
  841. package/websql/fesm2022/cms-websql-taon-cms-post.component.html.de-DE.i18n.data-C5Nxovtl.mjs.map +0 -1
  842. package/websql/fesm2022/cms-websql-taon-cms-post.component.html.en-US.i18n.data-BTWsCDp3.mjs.map +0 -1
  843. package/websql/fesm2022/cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-CZhAYAQh.mjs.map +0 -1
  844. package/websql/fesm2022/cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-D10xOAxh.mjs.map +0 -1
  845. package/websql/fesm2022/cms-websql-taon-cms-post.component.ts.en-US.i18n.data-D1Fh0Nd6.mjs.map +0 -1
  846. package/websql/fesm2022/cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-C-sVs8_U.mjs.map +0 -1
  847. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-C8qKRpYs.mjs.map +0 -1
  848. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-ClarunPV.mjs.map +0 -1
  849. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-YtHY1P_8.mjs.map +0 -1
  850. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-Dv-cf9Kp.mjs.map +0 -1
  851. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-CHBqahB1.mjs.map +0 -1
  852. package/websql-prod/fesm2022/cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-btgJU4-b.mjs.map +0 -1
@@ -6,16 +6,16 @@ import { UtilsJson, _ } from 'tnp-core/websql';
6
6
  import { BaseCommandLineFeature } from 'tnp-helpers/websql';
7
7
  import { Translation } from '@taon-dev/i18n/websql';
8
8
  import * as i0 from '@angular/core';
9
- import { Injectable, inject, DestroyRef, signal, Input, ChangeDetectionStrategy, Component, input, effect, Directive, output, ViewChild, ElementRef, EventEmitter, SecurityContext, HostListener, Output, computed, model, viewChild, untracked } from '@angular/core';
9
+ import { Injectable, inject, DestroyRef, signal, Input, ChangeDetectionStrategy, Component, input, effect, model, computed, Directive, output, ViewChild, ElementRef, EventEmitter, SecurityContext, HostListener, Output, viewChild, untracked } from '@angular/core';
10
10
  import * as i1 from '@angular/material/button';
11
11
  import { MatButtonModule } from '@angular/material/button';
12
- import { Observable, of, map as map$1, finalize, take, forkJoin, switchMap, from, firstValueFrom, combineLatest } from 'rxjs';
12
+ import { of, map as map$1, finalize, Observable, take, forkJoin, switchMap, from, firstValueFrom, combineLatest } from 'rxjs';
13
13
  import { map, catchError } from 'rxjs/operators';
14
- import { Resource } from 'ng2-rest/websql';
15
14
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
16
15
  import { TaonDatatableComponent, adminLazyRoute as adminLazyRoute$1, TaonConfirmDialogComponent, insertTable, createTableEditor, TaonAdminContentFooterService } from '@taon-dev/ui/websql';
17
- import { AsyncPipe, DatePipe, NgTemplateOutlet } from '@angular/common';
16
+ import { AsyncPipe, DatePipe, NgTemplateOutlet, JsonPipe } from '@angular/common';
18
17
  import { RouterOutlet, ActivatedRoute, Router, RouterLink } from '@angular/router';
18
+ import { Resource } from 'ng2-rest/websql';
19
19
  import * as i1$1 from '@angular/forms';
20
20
  import { FormsModule } from '@angular/forms';
21
21
  import * as i3 from '@angular/material/dialog';
@@ -26,20 +26,22 @@ import * as i5 from '@angular/material/input';
26
26
  import { MatInputModule } from '@angular/material/input';
27
27
  import * as i5$1 from '@angular/material/select';
28
28
  import { MatSelectModule } from '@angular/material/select';
29
+ import * as i17 from '@angular/cdk/drag-drop';
30
+ import { moveItemInArray, CdkDrag, CdkDragHandle, CdkDropList, DragDropModule } from '@angular/cdk/drag-drop';
31
+ import * as i2 from '@angular/material/icon';
32
+ import { MatIconModule } from '@angular/material/icon';
29
33
  import * as i3$1 from '@angular/material/checkbox';
30
34
  import { MatCheckboxModule } from '@angular/material/checkbox';
31
- import * as i5$2 from '@angular/material/icon';
32
- import { MatIconModule } from '@angular/material/icon';
33
35
  import * as i3$2 from '@angular/material/radio';
34
36
  import { MatRadioModule } from '@angular/material/radio';
35
37
  import JSZip from 'jszip';
36
38
  import { DomSanitizer } from '@angular/platform-browser';
37
- import * as i17 from '@angular/cdk/drag-drop';
38
- import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
39
39
  import * as i3$3 from '@angular/material/card';
40
40
  import { MatCardModule } from '@angular/material/card';
41
41
  import * as i11 from '@angular/material/chips';
42
42
  import { MatChipsModule } from '@angular/material/chips';
43
+ import * as i16 from '@angular/material/divider';
44
+ import { MatDividerModule } from '@angular/material/divider';
43
45
  import * as i4$1 from '@angular/material/expansion';
44
46
  import { MatExpansionModule } from '@angular/material/expansion';
45
47
  import * as i8 from '@angular/material/menu';
@@ -53,8 +55,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
53
55
  import * as i10 from 'ngx-editor';
54
56
  import { toHTML, NgxEditorModule } from 'ngx-editor';
55
57
  import { addRowBefore, addRowAfter, deleteRow, addColumnBefore, addColumnAfter, deleteColumn, deleteTable } from 'prosemirror-tables';
56
- import * as i16 from '@angular/material/divider';
57
- import { MatDividerModule } from '@angular/material/divider';
58
58
 
59
59
  let TaonCmsApplicationHookEntity = class TaonCmsApplicationHookEntity extends TaonBaseEntity {
60
60
  };
@@ -88,6 +88,7 @@ var TaonCmsContentType;
88
88
  TaonCmsContentType["Video"] = "video";
89
89
  TaonCmsContentType["Audio"] = "audio";
90
90
  TaonCmsContentType["Attachment"] = "attachment";
91
+ TaonCmsContentType["JsonTable"] = "json-table";
91
92
  })(TaonCmsContentType || (TaonCmsContentType = {}));
92
93
  var TaonCmsContentState;
93
94
  (function (TaonCmsContentState) {
@@ -426,7 +427,7 @@ const CURRENT_PACKAGE_TAON_VERSION = 'v21';
426
427
  /**
427
428
  * Autogenerated by current cli tool. Use *tnp release* to bump version.
428
429
  */
429
- const CURRENT_PACKAGE_VERSION = '21.0.25';
430
+ const CURRENT_PACKAGE_VERSION = '21.0.27';
430
431
  // THIS FILE IS GENERATED - DO NOT MODIFY
431
432
 
432
433
  //#region tnp-helpers cli template
@@ -538,7 +539,7 @@ function validateContentInput(input, creating = false) {
538
539
  }
539
540
  if (input.type !== undefined &&
540
541
  !Object.values(TaonCmsContentType).includes(input.type)) {
541
- contentError('type must be normal, video, audio, or attachment.');
542
+ contentError('type must be normal, video, audio, attachment, or json-table.');
542
543
  }
543
544
  for (const field of ['title', 'slug']) {
544
545
  if (creating || input[field] !== undefined) {
@@ -1542,6 +1543,389 @@ TaonCmsContentAssetEntity = __decorate([
1542
1543
  })
1543
1544
  ], TaonCmsContentAssetEntity);
1544
1545
 
1546
+ const dangerousKeys = new Set(['__proto__', 'prototype', 'constructor']);
1547
+ const supportedTypes = new Set([
1548
+ 'string',
1549
+ 'number',
1550
+ 'boolean',
1551
+ 'date',
1552
+ 'json',
1553
+ ]);
1554
+ const maximumColumns = 200;
1555
+ const maximumRows = 10_000;
1556
+ const TAON_SPREADSHEET_MAX_BYTES = 10_000_000;
1557
+ function normalizeSpreadsheetKey(header) {
1558
+ const words = header
1559
+ .normalize('NFKD')
1560
+ .replace(/[\u0300-\u036f]/g, '')
1561
+ .match(/[A-Za-z0-9]+/g);
1562
+ if (!words?.length) {
1563
+ return '';
1564
+ }
1565
+ return words
1566
+ .map((word, index) => index === 0
1567
+ ? word.charAt(0).toLowerCase() + word.slice(1)
1568
+ : word.charAt(0).toUpperCase() + word.slice(1))
1569
+ .join('');
1570
+ }
1571
+ function validateSpreadsheetData(value) {
1572
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1573
+ throw new Error('Table data must be an object.');
1574
+ }
1575
+ const source = value;
1576
+ if (source.version !== 1 ||
1577
+ !Array.isArray(source.columns) ||
1578
+ !Array.isArray(source.rows)) {
1579
+ throw new Error('Table data must use the supported version 1 format.');
1580
+ }
1581
+ if (source.columns.length > maximumColumns) {
1582
+ throw new Error(`A table cannot have more than ${maximumColumns} columns.`);
1583
+ }
1584
+ if (source.rows.length > maximumRows) {
1585
+ throw new Error(`A table cannot have more than ${maximumRows} rows.`);
1586
+ }
1587
+ const keys = new Set();
1588
+ const columns = source.columns.map((column, index) => {
1589
+ if (!column || typeof column !== 'object') {
1590
+ throw new Error(`Column ${index + 1} is invalid.`);
1591
+ }
1592
+ const candidate = column;
1593
+ if (typeof candidate.key !== 'string' ||
1594
+ !/^[a-z][A-Za-z0-9]*$/.test(candidate.key) ||
1595
+ dangerousKeys.has(candidate.key)) {
1596
+ throw new Error(`Column ${index + 1} has an invalid key.`);
1597
+ }
1598
+ if (keys.has(candidate.key)) {
1599
+ throw new Error(`The column key "${candidate.key}" is duplicated.`);
1600
+ }
1601
+ if (!supportedTypes.has(candidate.type)) {
1602
+ throw new Error(`Column "${candidate.key}" has an unsupported type.`);
1603
+ }
1604
+ if (candidate.label !== undefined && typeof candidate.label !== 'string') {
1605
+ throw new Error(`Column "${candidate.key}" has an invalid label.`);
1606
+ }
1607
+ if (candidate.width !== undefined &&
1608
+ (!Number.isFinite(candidate.width) ||
1609
+ candidate.width < 40 ||
1610
+ candidate.width > 1000)) {
1611
+ throw new Error(`Column "${candidate.key}" has an invalid width.`);
1612
+ }
1613
+ keys.add(candidate.key);
1614
+ return {
1615
+ key: candidate.key,
1616
+ ...(candidate.label !== undefined ? { label: candidate.label } : {}),
1617
+ type: candidate.type,
1618
+ ...(candidate.width !== undefined ? { width: candidate.width } : {}),
1619
+ };
1620
+ });
1621
+ const rows = source.rows.map((row, rowIndex) => {
1622
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
1623
+ throw new Error(`Row ${rowIndex + 1} must be an object.`);
1624
+ }
1625
+ const result = {};
1626
+ for (const key of Object.keys(row)) {
1627
+ if (dangerousKeys.has(key)) {
1628
+ throw new Error(`Row ${rowIndex + 1} contains a dangerous key.`);
1629
+ }
1630
+ if (!keys.has(key)) {
1631
+ throw new Error(`Row ${rowIndex + 1} contains unknown column "${key}".`);
1632
+ }
1633
+ const cell = row[key];
1634
+ const column = columns.find(candidate => candidate.key === key);
1635
+ if (cell !== null && cell !== undefined) {
1636
+ const validType = column.type === 'string'
1637
+ ? typeof cell === 'string'
1638
+ : column.type === 'number'
1639
+ ? typeof cell === 'number' && Number.isFinite(cell)
1640
+ : column.type === 'boolean'
1641
+ ? typeof cell === 'boolean'
1642
+ : column.type === 'date'
1643
+ ? typeof cell === 'string' && isValidDateOnly(cell)
1644
+ : true;
1645
+ if (!validType) {
1646
+ throw new Error(`Row ${rowIndex + 1}, column "${key}" does not match type "${column.type}".`);
1647
+ }
1648
+ }
1649
+ if (column.type === 'json' && cell !== null && cell !== undefined) {
1650
+ assertSafeJson(cell, `Row ${rowIndex + 1}, column "${key}"`);
1651
+ }
1652
+ result[key] = cell;
1653
+ }
1654
+ return result;
1655
+ });
1656
+ return { version: 1, columns, rows };
1657
+ }
1658
+ function parseSpreadsheetDocument(body) {
1659
+ if (!body?.trim()) {
1660
+ return { version: 1, columns: [], rows: [] };
1661
+ }
1662
+ let parsed;
1663
+ try {
1664
+ parsed = JSON.parse(body);
1665
+ }
1666
+ catch {
1667
+ throw new Error('The saved JSON Table content is not valid JSON.');
1668
+ }
1669
+ if (parsed &&
1670
+ typeof parsed === 'object' &&
1671
+ !Array.isArray(parsed) &&
1672
+ parsed.version === 1) {
1673
+ return validateSpreadsheetData(parsed);
1674
+ }
1675
+ if (!Array.isArray(parsed)) {
1676
+ throw new Error('JSON Table content must be a versioned table or an array.');
1677
+ }
1678
+ return inferSpreadsheetData(parsed);
1679
+ }
1680
+ function importSpreadsheetJson(text) {
1681
+ let parsed;
1682
+ try {
1683
+ parsed = JSON.parse(text.replace(/^\uFEFF/, ''));
1684
+ }
1685
+ catch {
1686
+ throw new Error('The selected JSON file is not valid JSON.');
1687
+ }
1688
+ if (parsed &&
1689
+ typeof parsed === 'object' &&
1690
+ !Array.isArray(parsed) &&
1691
+ parsed.version === 1) {
1692
+ return validateSpreadsheetData(parsed);
1693
+ }
1694
+ if (!Array.isArray(parsed)) {
1695
+ throw new Error('JSON import must contain an array of row objects.');
1696
+ }
1697
+ return inferSpreadsheetData(parsed);
1698
+ }
1699
+ function inferSpreadsheetData(rows) {
1700
+ if (rows.length > maximumRows) {
1701
+ throw new Error(`A table cannot have more than ${maximumRows} rows.`);
1702
+ }
1703
+ if (rows.some(row => !row || typeof row !== 'object' || Array.isArray(row))) {
1704
+ throw new Error('Every imported row must be an object.');
1705
+ }
1706
+ const headers = [...new Set(rows.flatMap(row => Object.keys(row)))];
1707
+ if (headers.length > maximumColumns) {
1708
+ throw new Error(`A table cannot have more than ${maximumColumns} columns.`);
1709
+ }
1710
+ const usedKeys = new Set();
1711
+ const columns = headers.map(header => {
1712
+ let key = normalizeSpreadsheetKey(header);
1713
+ if (!key || !/^[A-Za-z]/.test(key)) {
1714
+ key = `column${key ? key.charAt(0).toUpperCase() + key.slice(1) : ''}`;
1715
+ }
1716
+ const base = key;
1717
+ let suffix = 2;
1718
+ while (usedKeys.has(key) || dangerousKeys.has(key)) {
1719
+ key = `${base}${suffix++}`;
1720
+ }
1721
+ usedKeys.add(key);
1722
+ const values = rows.map(row => row[header]);
1723
+ return {
1724
+ key,
1725
+ ...(header !== key ? { label: header } : {}),
1726
+ type: inferType(values),
1727
+ };
1728
+ });
1729
+ const keyByHeader = new Map(headers.map((header, index) => [header, columns[index].key]));
1730
+ const normalizedRows = rows.map(row => {
1731
+ const result = {};
1732
+ for (const header of headers) {
1733
+ const key = keyByHeader.get(header);
1734
+ result[key] = row[header] ?? null;
1735
+ }
1736
+ return result;
1737
+ });
1738
+ return validateSpreadsheetData({
1739
+ version: 1,
1740
+ columns,
1741
+ rows: normalizedRows,
1742
+ });
1743
+ }
1744
+ function parseSpreadsheetCsv(text, delimiter) {
1745
+ const source = text.replace(/^\uFEFF/, '');
1746
+ const separator = delimiter ?? detectDelimiter(source);
1747
+ const records = [];
1748
+ let record = [];
1749
+ let field = '';
1750
+ let quoted = false;
1751
+ for (let index = 0; index < source.length; index += 1) {
1752
+ const char = source[index];
1753
+ if (quoted) {
1754
+ if (char === '"' && source[index + 1] === '"') {
1755
+ field += '"';
1756
+ index += 1;
1757
+ }
1758
+ else if (char === '"') {
1759
+ quoted = false;
1760
+ }
1761
+ else {
1762
+ field += char;
1763
+ }
1764
+ }
1765
+ else if (char === '"' && field.length === 0) {
1766
+ quoted = true;
1767
+ }
1768
+ else if (char === separator) {
1769
+ record.push(field);
1770
+ field = '';
1771
+ }
1772
+ else if (char === '\r' || char === '\n') {
1773
+ record.push(field);
1774
+ records.push(record);
1775
+ record = [];
1776
+ field = '';
1777
+ if (char === '\r' && source[index + 1] === '\n') {
1778
+ index += 1;
1779
+ }
1780
+ }
1781
+ else {
1782
+ field += char;
1783
+ }
1784
+ }
1785
+ if (quoted) {
1786
+ throw new Error('The CSV file contains an unterminated quoted value.');
1787
+ }
1788
+ if (field.length || record.length) {
1789
+ record.push(field);
1790
+ records.push(record);
1791
+ }
1792
+ const nonEmptyRecords = records.filter(row => row.some(cell => cell !== ''));
1793
+ if (!nonEmptyRecords.length) {
1794
+ return { version: 1, columns: [], rows: [] };
1795
+ }
1796
+ const headers = nonEmptyRecords[0].map(header => header.trim());
1797
+ if (!headers.length || headers.some(header => !header)) {
1798
+ throw new Error('CSV header cells must not be empty.');
1799
+ }
1800
+ if (new Set(headers).size !== headers.length) {
1801
+ throw new Error('CSV headers must be unique before import.');
1802
+ }
1803
+ const rows = nonEmptyRecords.slice(1).map((cells, rowIndex) => {
1804
+ if (cells.length > headers.length) {
1805
+ throw new Error(`CSV row ${rowIndex + 2} has more cells than the header.`);
1806
+ }
1807
+ const result = {};
1808
+ headers.forEach((header, index) => {
1809
+ result[header] = parseCsvValue(cells[index] ?? '');
1810
+ });
1811
+ return result;
1812
+ });
1813
+ const inferred = inferSpreadsheetData(rows.length
1814
+ ? rows
1815
+ : [Object.fromEntries(headers.map(header => [header, '']))]);
1816
+ return {
1817
+ version: 1,
1818
+ columns: inferred.columns,
1819
+ rows: rows.length ? inferred.rows : [],
1820
+ };
1821
+ }
1822
+ function serializeSpreadsheetCsv(data) {
1823
+ const escape = (value) => {
1824
+ let text = value === null || value === undefined
1825
+ ? ''
1826
+ : typeof value === 'string'
1827
+ ? value
1828
+ : JSON.stringify(value);
1829
+ if (/^[\t\r\n ]*[=+\-@]/.test(text)) {
1830
+ text = `'${text}`;
1831
+ }
1832
+ return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
1833
+ };
1834
+ const header = data.columns.map(column => escape(column.label || column.key));
1835
+ const rows = data.rows.map(row => data.columns.map(column => escape(row[column.key] ?? '')).join(','));
1836
+ return [header.join(','), ...rows].join('\r\n');
1837
+ }
1838
+ function inferType(values) {
1839
+ const present = values.filter(value => value !== null && value !== undefined && value !== '');
1840
+ if (present.length &&
1841
+ present.every(value => typeof value === 'string' && isValidDateOnly(value))) {
1842
+ return 'date';
1843
+ }
1844
+ if (present.length && present.every(value => typeof value === 'boolean')) {
1845
+ return 'boolean';
1846
+ }
1847
+ if (present.length &&
1848
+ present.every(value => typeof value === 'number' && Number.isFinite(value))) {
1849
+ return 'number';
1850
+ }
1851
+ if (present.length && present.every(value => typeof value === 'object')) {
1852
+ return 'json';
1853
+ }
1854
+ return 'string';
1855
+ }
1856
+ function detectDelimiter(text) {
1857
+ const firstLine = text.split(/\r\n|\n|\r/, 1)[0] ?? '';
1858
+ const candidates = [',', ';', '\t'];
1859
+ return candidates.reduce((selected, candidate) => countUnquoted(firstLine, candidate) > countUnquoted(firstLine, selected)
1860
+ ? candidate
1861
+ : selected);
1862
+ }
1863
+ function countUnquoted(line, delimiter) {
1864
+ let quoted = false;
1865
+ let count = 0;
1866
+ for (let index = 0; index < line.length; index += 1) {
1867
+ if (line[index] === '"' && line[index + 1] === '"' && quoted) {
1868
+ index += 1;
1869
+ }
1870
+ else if (line[index] === '"') {
1871
+ quoted = !quoted;
1872
+ }
1873
+ else if (line[index] === delimiter && !quoted) {
1874
+ count += 1;
1875
+ }
1876
+ }
1877
+ return count;
1878
+ }
1879
+ function parseCsvValue(value) {
1880
+ if (!value) {
1881
+ return null;
1882
+ }
1883
+ if (/^(true|false)$/i.test(value)) {
1884
+ return value.toLowerCase() === 'true';
1885
+ }
1886
+ if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) {
1887
+ const number = Number(value);
1888
+ if (Number.isFinite(number)) {
1889
+ return number;
1890
+ }
1891
+ }
1892
+ return value;
1893
+ }
1894
+ function isValidDateOnly(value) {
1895
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
1896
+ return false;
1897
+ }
1898
+ const date = new Date(`${value}T00:00:00.000Z`);
1899
+ return (!Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value);
1900
+ }
1901
+ function assertSafeJson(value, path) {
1902
+ if (value === null ||
1903
+ typeof value === 'string' ||
1904
+ typeof value === 'boolean') {
1905
+ return;
1906
+ }
1907
+ if (typeof value === 'number') {
1908
+ if (!Number.isFinite(value)) {
1909
+ throw new Error(`${path} contains a non-finite number.`);
1910
+ }
1911
+ return;
1912
+ }
1913
+ if (Array.isArray(value)) {
1914
+ value.forEach((item, index) => assertSafeJson(item, `${path}[${index}]`));
1915
+ return;
1916
+ }
1917
+ if (typeof value === 'object') {
1918
+ for (const [key, nestedValue] of Object.entries(value)) {
1919
+ if (dangerousKeys.has(key)) {
1920
+ throw new Error(`${path} contains a dangerous JSON key.`);
1921
+ }
1922
+ assertSafeJson(nestedValue, `${path}.${key}`);
1923
+ }
1924
+ return;
1925
+ }
1926
+ throw new Error(`${path} contains a value that cannot be serialized as JSON.`);
1927
+ }
1928
+
1545
1929
  //#endregion
1546
1930
  let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRepository {
1547
1931
  constructor() {
@@ -2061,7 +2445,7 @@ let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRe
2061
2445
  if (content.status !== 'archived') {
2062
2446
  contentError('Only archived posts can be permanently deleted.', 409);
2063
2447
  }
2064
- const repository = manager.getRepository(this.target);
2448
+ const repository = manager.getRepository(TaonCmsContentEntity);
2065
2449
  const assetRepository = manager.getRepository(TaonCmsContentAssetEntity);
2066
2450
  const assetKeys = [
2067
2451
  content.videoKey,
@@ -2666,6 +3050,21 @@ let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRe
2666
3050
  if (title === undefined || slug === undefined) {
2667
3051
  contentError('title and slug are required.');
2668
3052
  }
3053
+ if (type === TaonCmsContentType.JsonTable) {
3054
+ try {
3055
+ const body = input.body === undefined ? (current?.body ?? null) : input.body;
3056
+ if (typeof body === 'string' && body.length > TAON_SPREADSHEET_MAX_BYTES) {
3057
+ contentError('JSON Table content exceeds the 10 MB limit.');
3058
+ }
3059
+ parseSpreadsheetDocument(body);
3060
+ }
3061
+ catch (error) {
3062
+ if (error instanceof Error) {
3063
+ contentError(`JSON Table content is invalid: ${error.message}`);
3064
+ }
3065
+ throw error;
3066
+ }
3067
+ }
2669
3068
  const publishedAt = input.publishedAt === undefined
2670
3069
  ? (current?.publishedAt ?? null)
2671
3070
  : input.publishedAt;
@@ -4002,6 +4401,10 @@ let TaonCmsContentStorageController = class TaonCmsContentStorageController exte
4002
4401
  /* */
4003
4402
  /* */
4004
4403
  /* */
4404
+ /* */
4405
+ /* */
4406
+ /* */
4407
+ /* */
4005
4408
  return (void 0);
4006
4409
  }
4007
4410
  async handleUploadFiles(files, queryParams, req, res) {
@@ -4042,6 +4445,50 @@ let TaonCmsContentStorageController = class TaonCmsContentStorageController exte
4042
4445
  /* */
4043
4446
  return (void 0);
4044
4447
  }
4448
+ /**
4449
+ * Access is checked in beforeEachRequest (CMS managers only).
4450
+ * The declared size is fixed at start and enforced for every part.
4451
+ */
4452
+ async authorizeMultipartUpload(options) {
4453
+ /* */
4454
+ /* */
4455
+ /* */
4456
+ /* */
4457
+ /* */
4458
+ /* */
4459
+ /* */
4460
+ /* */
4461
+ /* */
4462
+ /* */
4463
+ /* */
4464
+ return (void 0);
4465
+ }
4466
+ async handleCompletedMultipartUpload(object, queryParams, req) {
4467
+ /* */
4468
+ /* */
4469
+ /* */
4470
+ /* */
4471
+ /* */
4472
+ return (void 0);
4473
+ }
4474
+ async withDownloadUrl(object, req) {
4475
+ /* */
4476
+ /* */
4477
+ /* */
4478
+ /* */
4479
+ /* */
4480
+ /* */
4481
+ /* */
4482
+ /* */
4483
+ /* */
4484
+ /* */
4485
+ /* */
4486
+ /* */
4487
+ /* */
4488
+ /* */
4489
+ /* */
4490
+ return (void 0);
4491
+ }
4045
4492
  afterFileUploadHook() { }
4046
4493
  async isMediaObjectAvailable(key) {
4047
4494
  /* */
@@ -4205,6 +4652,7 @@ function defaultThumbnailUrl(key) {
4205
4652
  video: '<rect x="248" y="132" width="144" height="100" rx="16"/><path d="m308 158 40 24-40 24z"/>',
4206
4653
  audio: '<path d="M280 164v40m20-60v80m20-98v116m20-98v80m20-60v40"/>',
4207
4654
  attachment: '<path d="M276 124h60l32 32v84h-92z M336 124v36h32m-70 30h48m-48 22h36"/>',
4655
+ 'json-table': '<path d="M252 130h136v100H252z M252 164h136m-92-34v100m46-100v100m-90-66h136m-136 33h136"/>',
4208
4656
  };
4209
4657
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 640 360"><defs><linearGradient id="bg" x2="1" y2="1"><stop stop-color="#eaf0ff"/><stop offset="1" stop-color="#e4f5f0"/></linearGradient></defs><rect width="640" height="360" rx="24" fill="url(#bg)"/><circle cx="320" cy="180" r="112" fill="#fff" opacity=".65"/><g fill="none" stroke="#52669a" stroke-width="7" stroke-linecap="round" stroke-linejoin="round">${shapes[type]}</g></svg>`;
4210
4658
  return `data:image/svg+xml,${encodeURIComponent(svg)}`;
@@ -4379,6 +4827,8 @@ let TaonCmsContentController = class TaonCmsContentController extends TaonBaseCr
4379
4827
  /* */
4380
4828
  /* */
4381
4829
  /* */
4830
+ /* */
4831
+ /* */
4382
4832
  return (void 0);
4383
4833
  }
4384
4834
  //#endregion
@@ -4530,6 +4980,57 @@ let TaonCmsContentController = class TaonCmsContentController extends TaonBaseCr
4530
4980
  //#endregion
4531
4981
  }
4532
4982
  //#endregion
4983
+ getJsonTableBySlug(slug) {
4984
+ //#region @websqlFunc
4985
+ return async (req, _res, { t }) => {
4986
+ const post = await this.requireJsonTablePost(slug, req, t, false);
4987
+ return this.jsonTableRows(post.body, t);
4988
+ };
4989
+ //#endregion
4990
+ }
4991
+ getPublicJsonTableBySlug(slug) {
4992
+ //#region @websqlFunc
4993
+ return async (req, _res, { t }) => {
4994
+ const post = await this.requireJsonTablePost(slug, req, t, true);
4995
+ return this.jsonTableRows(post.body, t);
4996
+ };
4997
+ //#endregion
4998
+ }
4999
+ async requireJsonTablePost(slug, req, t, publicOnly) {
5000
+ //#region @websqlFunc
5001
+ const normalizedSlug = typeof slug === 'string' ? slug.trim() : '';
5002
+ if (!normalizedSlug || normalizedSlug.length > 255) {
5003
+ contentError(t.gettext('The post slug is invalid.'), 400);
5004
+ }
5005
+ const post = await this.taonCmsContentRepository.getContentBySlugForMedia(normalizedSlug);
5006
+ if (!post) {
5007
+ contentError(t.gettext('The post was not found.'), 404);
5008
+ }
5009
+ if (post.type !== TaonCmsContentType.JsonTable) {
5010
+ contentError(t.gettext('The post is not a JSON Table.'), 400);
5011
+ }
5012
+ await this.requireReadAccess(post, publicOnly, req, t);
5013
+ return post;
5014
+ //#endregion
5015
+ }
5016
+ jsonTableRows(body, t) {
5017
+ //#region @websqlFunc
5018
+ if (body && body.length > TAON_SPREADSHEET_MAX_BYTES) {
5019
+ contentError('JSON Table content exceeds the 10 MB limit.', 400);
5020
+ }
5021
+ try {
5022
+ return parseSpreadsheetDocument(body).rows;
5023
+ }
5024
+ catch (error) {
5025
+ if (error instanceof Error) {
5026
+ contentError(t.gettext('JSON Table content is invalid: [[ error ]]', {
5027
+ error: error.message,
5028
+ }), 400);
5029
+ }
5030
+ throw error;
5031
+ }
5032
+ //#endregion
5033
+ }
4533
5034
  getPublicBySlug(slug) {
4534
5035
  //#region @websqlFunc
4535
5036
  return async (req, _res, { t }) => {
@@ -4864,6 +5365,20 @@ __decorate([
4864
5365
  __metadata("design:paramtypes", [String]),
4865
5366
  __metadata("design:returntype", Object)
4866
5367
  ], TaonCmsContentController.prototype, "getBySlug", null);
5368
+ __decorate([
5369
+ GET(),
5370
+ __param(0, Query('slug')),
5371
+ __metadata("design:type", Function),
5372
+ __metadata("design:paramtypes", [String]),
5373
+ __metadata("design:returntype", Object)
5374
+ ], TaonCmsContentController.prototype, "getJsonTableBySlug", null);
5375
+ __decorate([
5376
+ GET(),
5377
+ __param(0, Query('slug')),
5378
+ __metadata("design:type", Function),
5379
+ __metadata("design:paramtypes", [String]),
5380
+ __metadata("design:returntype", Object)
5381
+ ], TaonCmsContentController.prototype, "getPublicJsonTableBySlug", null);
4867
5382
  __decorate([
4868
5383
  GET(),
4869
5384
  __param(0, Query('slug')),
@@ -5046,6 +5561,8 @@ TaonCmsContentController = __decorate([
5046
5561
  allowedMethods: [
5047
5562
  'getAll',
5048
5563
  'getBy',
5564
+ 'getJsonTableBySlug',
5565
+ 'getPublicJsonTableBySlug',
5049
5566
  'getPublicBySlug',
5050
5567
  'getPublicVideoChildrenBySlug',
5051
5568
  'createContent',
@@ -5096,6 +5613,13 @@ TaonCmsContentMiddleware = __decorate([
5096
5613
 
5097
5614
  //#endregion
5098
5615
  let TaonCmsContentProvider = class TaonCmsContentProvider extends TaonBaseProvider {
5616
+ constructor() {
5617
+ super(...arguments);
5618
+ /**
5619
+ * Slugs offered in the post editor "Required Slugs" selector.
5620
+ */
5621
+ this.requiredSlugs = [];
5622
+ }
5099
5623
  async _() {
5100
5624
  if (!this.siteName) {
5101
5625
  throw new Error(`Please provide siteName in TaonCmsContentProvider`);
@@ -5456,80 +5980,20 @@ const TaonCmsAbstractContext = createContext(() => ({
5456
5980
  }));
5457
5981
  // THIS FILE IS GENERATED - DO NOT MODIFY
5458
5982
 
5459
- const progressCallbacks = new WeakMap();
5460
- // Keep Taon's middleware, retries and response decoding; replace only the
5461
- // final Fetch transport for uploads that request browser upload events.
5462
- function trackCmsUpload(url, body, onProgress) {
5463
- const key = `cms-upload-progress-POST-${new URL(url).pathname}`;
5464
- if (!Resource.methodsInterceptors.has(key)) {
5465
- Resource.methodsInterceptors.set(key, {
5466
- intercept: ({ req, next }) => {
5467
- if (!(req.body instanceof FormData)) {
5468
- return next.handle(req);
5469
- }
5470
- const uploadBody = req.body;
5471
- const callback = progressCallbacks.get(uploadBody);
5472
- if (!callback) {
5473
- return next.handle(req);
5474
- }
5475
- return new Observable(subscriber => {
5476
- const xhr = new XMLHttpRequest();
5477
- xhr.open(req.method || 'POST', req.url);
5478
- xhr.withCredentials = req.credentials === 'include';
5479
- new Headers(req.headers).forEach((value, name) => {
5480
- // The browser must supply the multipart boundary.
5481
- if (name.toLowerCase() !== 'content-type') {
5482
- xhr.setRequestHeader(name, value);
5483
- }
5484
- });
5485
- xhr.upload.onprogress = event => {
5486
- if (event.lengthComputable && event.total > 0) {
5487
- callback(Math.min(100, Math.floor((event.loaded * 100) / event.total)));
5488
- }
5489
- };
5490
- xhr.onload = () => {
5491
- const headers = new Headers();
5492
- for (const line of xhr
5493
- .getAllResponseHeaders()
5494
- .trim()
5495
- .split(/[\r\n]+/)) {
5496
- const separator = line.indexOf(':');
5497
- if (separator > 0) {
5498
- headers.append(line.slice(0, separator), line.slice(separator + 1).trim());
5499
- }
5500
- }
5501
- subscriber.next(new Response([204, 205, 304].includes(xhr.status) ? null : xhr.responseText, {
5502
- status: xhr.status,
5503
- statusText: xhr.statusText,
5504
- headers,
5505
- }));
5506
- subscriber.complete();
5507
- };
5508
- xhr.onerror = () => subscriber.error(new Error('Media upload network error.'));
5509
- xhr.ontimeout = () => subscriber.error(new Error('Media upload timed out.'));
5510
- xhr.onabort = () => subscriber.error(new Error('Media upload was cancelled.'));
5511
- const abort = () => xhr.abort();
5512
- req.signal?.addEventListener('abort', abort, { once: true });
5513
- if (req.signal?.aborted) {
5514
- subscriber.error(new Error('Media upload was cancelled.'));
5515
- }
5516
- else {
5517
- callback(0);
5518
- xhr.send(uploadBody);
5519
- }
5520
- return () => {
5521
- req.signal?.removeEventListener('abort', abort);
5522
- if (xhr.readyState !== XMLHttpRequest.DONE) {
5523
- xhr.abort();
5524
- }
5525
- };
5526
- });
5527
- },
5528
- });
5529
- }
5530
- progressCallbacks.set(body, onProgress);
5531
- return () => progressCallbacks.delete(body);
5532
- }
5983
+ const TaonCmsContentDefaultsValues = {
5984
+ description: '',
5985
+ version: 0,
5986
+ id: void 0,
5987
+ type: TaonCmsContentType.Normal,
5988
+ videoKey: null,
5989
+ audioKey: null,
5990
+ attachmentKey: null,
5991
+ thumbnailKey: null,
5992
+ };
5993
+ /**
5994
+ * Strict maximum size of a single CMS media upload (1 GB).
5995
+ */
5996
+ const TAON_CMS_MAX_UPLOAD_BYTES = 1_000_000_000;
5533
5997
 
5534
5998
  //#region imports
5535
5999
  //#endregion
@@ -5562,23 +6026,33 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5562
6026
  }
5563
6027
  //#endregion
5564
6028
  async getPublicBySlug(slug) {
5565
- const response = await this.taonCmsContentController
5566
- .getPublicBySlug(slug)
5567
- .request();
6029
+ const response = await this.taonCmsContentController.getPublicBySlug(slug).request();
5568
6030
  if (response.statusCode >= 400 || !response.body?.json) {
5569
6031
  throw new Error(response.responseText || 'The post could not be loaded.');
5570
6032
  }
5571
6033
  return response.body.json;
5572
6034
  }
5573
- async getPublicVideoChildrenBySlug(slug) {
5574
- const response = await this.taonCmsContentController
5575
- .getPublicVideoChildrenBySlug(slug)
5576
- .request();
6035
+ async getJsonTableBySlug(slug) {
6036
+ const response = await this.taonCmsContentController.getJsonTableBySlug(slug).request();
5577
6037
  if (response.statusCode >= 400 || !Array.isArray(response.body?.json)) {
5578
- throw new Error(response.responseText || 'Public video children could not be loaded.');
6038
+ throw new Error(response.responseText || 'The JSON Table could not be loaded.');
5579
6039
  }
5580
6040
  return response.body.json;
5581
6041
  }
6042
+ getPublicJsonTableBySlug(slug) {
6043
+ return this.taonCmsContentController.getPublicJsonTableBySlug(slug)
6044
+ .request().observable.pipe(map(c => c.body.json), catchError(error => {
6045
+ console.error(`Failed to load JSON table "${slug}":`, error);
6046
+ return of([]);
6047
+ }));
6048
+ }
6049
+ getPublicVideoChildrenBySlug(slug) {
6050
+ return this.taonCmsContentController.getPublicVideoChildrenBySlug(slug)
6051
+ .request().observable.pipe(map(c => c.body.json), catchError(error => {
6052
+ console.error(`Failed to load public video children for "${slug}":`, error);
6053
+ return of([]);
6054
+ }));
6055
+ }
5582
6056
  //#region API / get video link by slug
5583
6057
  /**
5584
6058
  * Get link to private video
@@ -5641,7 +6115,7 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5641
6115
  //#region API / get public content by hook
5642
6116
  getPublicContentByHook(hook) {
5643
6117
  return this.taonCmsContentController.getPublicContentByHook(hook)
5644
- .request().observable.pipe(map(res => res.body.text), catchError(() => of('---')));
6118
+ .request().observable.pipe(map(res => (res.body.text || '')?.replace(/\\n/g, '\n')), catchError(() => of('---')));
5645
6119
  }
5646
6120
  //#endregion
5647
6121
  //#region API / list application hooks
@@ -5651,26 +6125,21 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5651
6125
  }
5652
6126
  //#endregion
5653
6127
  //#region API / upload media
5654
- async uploadMedia(file, onProgress) {
5655
- const formData = new FormData();
5656
- formData.append('file', file);
5657
- const stopTracking = onProgress
5658
- ? trackCmsUpload(this.taonCmsContentStorageController.getThisControllerMethodFullUrl('uploadFormDataToServer'), formData, onProgress)
5659
- : undefined;
5660
- try {
5661
- const response = await this.taonCmsContentStorageController
5662
- .uploadFormDataToServer(formData)
5663
- .request();
5664
- const objects = response.body.json;
5665
- if (!Array.isArray(objects) || objects.length !== 1 || !objects[0]?.key) {
5666
- throw new Error('The upload did not return a media key.');
5667
- }
5668
- onProgress?.(100);
5669
- return objects[0];
5670
- }
5671
- finally {
5672
- stopTracking?.();
6128
+ async uploadMedia(file, onProgress, signal) {
6129
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
6130
+ throw new Error(`The file "${file.name}" is too large. The maximum upload size is 1 GB.`);
6131
+ }
6132
+ const objects = await this.taonCmsContentStorageController.uploadFileInChunks(file, {
6133
+ fileName: file.name,
6134
+ contentType: file.type || 'application/octet-stream',
6135
+ signal,
6136
+ onProgress,
6137
+ });
6138
+ if (!Array.isArray(objects) || objects.length !== 1 || !objects[0]?.key) {
6139
+ throw new Error('The upload did not return a media key.');
5673
6140
  }
6141
+ onProgress?.(100);
6142
+ return objects[0];
5674
6143
  }
5675
6144
  //#endregion
5676
6145
  async deleteUnattachedMedia(key) {
@@ -5739,10 +6208,7 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5739
6208
  }
5740
6209
  //#endregion
5741
6210
  permanentlyDeleteContent(id, expectedVersion) {
5742
- return this.taonCmsContentController
5743
- .permanentlyDeleteContent(id, expectedVersion)
5744
- .request()
5745
- .observable.pipe(map(res => res.body.json));
6211
+ return this.taonCmsContentController.permanentlyDeleteContent(id, expectedVersion).request().observable.pipe(map(res => res.body.json));
5746
6212
  }
5747
6213
  //#region API / restore content
5748
6214
  restoreContent(id, input) {
@@ -5767,7 +6233,8 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5767
6233
  }
5768
6234
  //#endregion
5769
6235
  async getParentPost(id) {
5770
- return (await this.taonCmsContentController.getParentPost(id).request()).body.json;
6236
+ return (await this.taonCmsContentController.getParentPost(id).request())
6237
+ .body.json;
5771
6238
  }
5772
6239
  async getVisibilityConfiguration(id) {
5773
6240
  return (await this.taonCmsContentController
@@ -5775,7 +6242,8 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5775
6242
  .request()).body.json;
5776
6243
  }
5777
6244
  async getPostVersions(id) {
5778
- return (await this.taonCmsContentController.getPostVersions(id).request()).body.json;
6245
+ return (await this.taonCmsContentController.getPostVersions(id).request())
6246
+ .body.json;
5779
6247
  }
5780
6248
  //#region API / list permissions
5781
6249
  listPermissions(id) {
@@ -5800,16 +6268,10 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5800
6268
  }
5801
6269
  //#endregion
5802
6270
  linkVersionPost(id, versionPostId, kind, expectedVersion) {
5803
- return this.taonCmsContentController
5804
- .linkVersionPost(id, versionPostId, kind, expectedVersion)
5805
- .request()
5806
- .observable.pipe(map(res => res.body.json));
6271
+ return this.taonCmsContentController.linkVersionPost(id, versionPostId, kind, expectedVersion).request().observable.pipe(map(res => res.body.json));
5807
6272
  }
5808
6273
  removeVersionPost(id, kind, expectedVersion) {
5809
- return this.taonCmsContentController
5810
- .removeVersionPost(id, kind, expectedVersion)
5811
- .request()
5812
- .observable.pipe(map(res => res.body.json));
6274
+ return this.taonCmsContentController.removeVersionPost(id, kind, expectedVersion).request().observable.pipe(map(res => res.body.json));
5813
6275
  }
5814
6276
  //#region API / get all entites
5815
6277
  /**
@@ -6373,6 +6835,13 @@ class TaonCmsContentConfigService extends TaonBaseAngularService {
6373
6835
  get siteName() {
6374
6836
  return this.taonCmsContentProvider.siteName;
6375
6837
  }
6838
+ get requiredSlugs() {
6839
+ return [
6840
+ ...new Set((this.taonCmsContentProvider.requiredSlugs ?? [])
6841
+ .map(slug => String(slug ?? '').trim())
6842
+ .filter(Boolean)),
6843
+ ];
6844
+ }
6376
6845
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
6377
6846
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService }); }
6378
6847
  }
@@ -6380,17 +6849,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
6380
6849
  type: Injectable
6381
6850
  }] });
6382
6851
 
6383
- const TaonCmsContentDefaultsValues = {
6384
- description: '',
6385
- version: 0,
6386
- id: void 0,
6387
- type: TaonCmsContentType.Normal,
6388
- videoKey: null,
6389
- audioKey: null,
6390
- attachmentKey: null,
6391
- thumbnailKey: null,
6392
- };
6393
-
6394
6852
  /**
6395
6853
  * Resolves allowlisted values as HTML text/attribute data, not markup.
6396
6854
  * The returned HTML must still pass through the renderer's HTML sanitizer.
@@ -6419,6 +6877,81 @@ function resolveTemplateVariables(content, context) {
6419
6877
  });
6420
6878
  }
6421
6879
 
6880
+ const progressCallbacks = new WeakMap();
6881
+ // Keep Taon's middleware, retries and response decoding; replace only the
6882
+ // final Fetch transport for uploads that request browser upload events.
6883
+ function trackCmsUpload(url, body, onProgress) {
6884
+ const key = `cms-upload-progress-POST-${new URL(url).pathname}`;
6885
+ if (!Resource.methodsInterceptors.has(key)) {
6886
+ Resource.methodsInterceptors.set(key, {
6887
+ intercept: ({ req, next }) => {
6888
+ if (!(req.body instanceof FormData)) {
6889
+ return next.handle(req);
6890
+ }
6891
+ const uploadBody = req.body;
6892
+ const callback = progressCallbacks.get(uploadBody);
6893
+ if (!callback) {
6894
+ return next.handle(req);
6895
+ }
6896
+ return new Observable(subscriber => {
6897
+ const xhr = new XMLHttpRequest();
6898
+ xhr.open(req.method || 'POST', req.url);
6899
+ xhr.withCredentials = req.credentials === 'include';
6900
+ new Headers(req.headers).forEach((value, name) => {
6901
+ // The browser must supply the multipart boundary.
6902
+ if (name.toLowerCase() !== 'content-type') {
6903
+ xhr.setRequestHeader(name, value);
6904
+ }
6905
+ });
6906
+ xhr.upload.onprogress = event => {
6907
+ if (event.lengthComputable && event.total > 0) {
6908
+ callback(Math.min(100, Math.floor((event.loaded * 100) / event.total)));
6909
+ }
6910
+ };
6911
+ xhr.onload = () => {
6912
+ const headers = new Headers();
6913
+ for (const line of xhr
6914
+ .getAllResponseHeaders()
6915
+ .trim()
6916
+ .split(/[\r\n]+/)) {
6917
+ const separator = line.indexOf(':');
6918
+ if (separator > 0) {
6919
+ headers.append(line.slice(0, separator), line.slice(separator + 1).trim());
6920
+ }
6921
+ }
6922
+ subscriber.next(new Response([204, 205, 304].includes(xhr.status) ? null : xhr.responseText, {
6923
+ status: xhr.status,
6924
+ statusText: xhr.statusText,
6925
+ headers,
6926
+ }));
6927
+ subscriber.complete();
6928
+ };
6929
+ xhr.onerror = () => subscriber.error(new Error('Media upload network error.'));
6930
+ xhr.ontimeout = () => subscriber.error(new Error('Media upload timed out.'));
6931
+ xhr.onabort = () => subscriber.error(new Error('Media upload was cancelled.'));
6932
+ const abort = () => xhr.abort();
6933
+ req.signal?.addEventListener('abort', abort, { once: true });
6934
+ if (req.signal?.aborted) {
6935
+ subscriber.error(new Error('Media upload was cancelled.'));
6936
+ }
6937
+ else {
6938
+ callback(0);
6939
+ xhr.send(uploadBody);
6940
+ }
6941
+ return () => {
6942
+ req.signal?.removeEventListener('abort', abort);
6943
+ if (xhr.readyState !== XMLHttpRequest.DONE) {
6944
+ xhr.abort();
6945
+ }
6946
+ };
6947
+ });
6948
+ },
6949
+ });
6950
+ }
6951
+ progressCallbacks.set(body, onProgress);
6952
+ return () => progressCallbacks.delete(body);
6953
+ }
6954
+
6422
6955
  class TaonCmsContentAssetApiService extends TaonBaseAngularService {
6423
6956
  constructor() {
6424
6957
  super(...arguments);
@@ -7007,6 +7540,606 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
7007
7540
  }]
7008
7541
  }] });
7009
7542
 
7543
+ class TaonSpreadsheetTableComponent {
7544
+ constructor() {
7545
+ this.data = model({
7546
+ version: 1,
7547
+ columns: [],
7548
+ rows: [],
7549
+ }, ...(ngDevMode ? [{ debugName: "data" }] : []));
7550
+ this.readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
7551
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
7552
+ this.validationError = model('', ...(ngDevMode ? [{ debugName: "validationError" }] : []));
7553
+ this.view = signal('table', ...(ngDevMode ? [{ debugName: "view" }] : []));
7554
+ this.activeCell = signal(null, ...(ngDevMode ? [{ debugName: "activeCell" }] : []));
7555
+ this.pendingImport = signal(null, ...(ngDevMode ? [{ debugName: "pendingImport" }] : []));
7556
+ this.importFormat = signal('', ...(ngDevMode ? [{ debugName: "importFormat" }] : []));
7557
+ this.importMode = signal('replace', ...(ngDevMode ? [{ debugName: "importMode" }] : []));
7558
+ this.columnTypes = [
7559
+ 'string',
7560
+ 'number',
7561
+ 'boolean',
7562
+ 'date',
7563
+ 'json',
7564
+ ];
7565
+ this.jsonPreview = computed(() => JSON.stringify(this.data().rows, null, 2), ...(ngDevMode ? [{ debugName: "jsonPreview" }] : []));
7566
+ this.undoStack = [];
7567
+ this.redoStack = [];
7568
+ }
7569
+ addColumn() {
7570
+ if (!this.canEdit()) {
7571
+ return;
7572
+ }
7573
+ const data = this.data();
7574
+ let number = data.columns.length + 1;
7575
+ let key = `column${number}`;
7576
+ while (data.columns.some(column => column.key === key)) {
7577
+ number += 1;
7578
+ key = `column${number}`;
7579
+ }
7580
+ this.commit({
7581
+ ...data,
7582
+ columns: [
7583
+ ...data.columns,
7584
+ { key, label: `Column ${number}`, type: 'string' },
7585
+ ],
7586
+ rows: data.rows.map(row => ({ ...row, [key]: null })),
7587
+ });
7588
+ }
7589
+ addRow() {
7590
+ if (!this.canEdit()) {
7591
+ return;
7592
+ }
7593
+ const row = {};
7594
+ for (const column of this.data().columns) {
7595
+ row[column.key] = null;
7596
+ }
7597
+ this.commit({ ...this.data(), rows: [...this.data().rows, row] });
7598
+ }
7599
+ deleteRow(rowIndex) {
7600
+ if (!this.canEdit() ||
7601
+ !globalThis.confirm('Delete this row? This cannot be undone.')) {
7602
+ return;
7603
+ }
7604
+ this.commit({
7605
+ ...this.data(),
7606
+ rows: this.data().rows.filter((_, index) => index !== rowIndex),
7607
+ });
7608
+ }
7609
+ duplicateRow(rowIndex) {
7610
+ if (!this.canEdit()) {
7611
+ return;
7612
+ }
7613
+ const rows = [...this.data().rows];
7614
+ rows.splice(rowIndex + 1, 0, { ...rows[rowIndex] });
7615
+ this.commit({ ...this.data(), rows });
7616
+ }
7617
+ deleteColumn(columnIndex) {
7618
+ if (!this.canEdit() ||
7619
+ !globalThis.confirm('Delete this column and its cell values? This cannot be undone.')) {
7620
+ return;
7621
+ }
7622
+ const column = this.data().columns[columnIndex];
7623
+ const rows = this.data().rows.map(row => {
7624
+ const { [column.key]: _removed, ...remaining } = row;
7625
+ return remaining;
7626
+ });
7627
+ this.commit({
7628
+ ...this.data(),
7629
+ columns: this.data().columns.filter((_, index) => index !== columnIndex),
7630
+ rows,
7631
+ });
7632
+ }
7633
+ renameColumn(columnIndex, field, value) {
7634
+ if (!this.canEdit()) {
7635
+ return;
7636
+ }
7637
+ const columns = this.data().columns.map(column => ({ ...column }));
7638
+ const previousKey = columns[columnIndex].key;
7639
+ const nextValue = field === 'key' ? value.trim() : value;
7640
+ const nextKey = field === 'key' ? nextValue : previousKey;
7641
+ if (field === 'key' &&
7642
+ (!/^[a-z][A-Za-z0-9]*$/.test(nextKey) ||
7643
+ ['__proto__', 'prototype', 'constructor'].includes(nextKey) ||
7644
+ columns.some((column, index) => index !== columnIndex && column.key === nextKey))) {
7645
+ this.validationError.set('Column keys must be unique, safe camelCase identifiers.');
7646
+ return;
7647
+ }
7648
+ columns[columnIndex] = { ...columns[columnIndex], [field]: nextValue };
7649
+ const rows = field === 'key' && previousKey !== nextKey
7650
+ ? this.data().rows.map(row => {
7651
+ const { [previousKey]: value, ...remaining } = row;
7652
+ return { ...remaining, [nextKey]: value };
7653
+ })
7654
+ : this.data().rows;
7655
+ this.commit({ ...this.data(), columns, rows });
7656
+ }
7657
+ setColumnType(columnIndex, type) {
7658
+ if (!this.canEdit()) {
7659
+ return;
7660
+ }
7661
+ const columns = this.data().columns.map(column => ({ ...column }));
7662
+ columns[columnIndex] = { ...columns[columnIndex], type };
7663
+ this.commit({ ...this.data(), columns });
7664
+ }
7665
+ setCell(rowIndex, column, event) {
7666
+ if (!this.canEdit()) {
7667
+ return;
7668
+ }
7669
+ const target = event.target;
7670
+ let value = target.value;
7671
+ if (column.type === 'number') {
7672
+ value = target.value === '' ? null : Number(target.value);
7673
+ if (value !== null && !Number.isFinite(value)) {
7674
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter a valid number.`);
7675
+ return;
7676
+ }
7677
+ }
7678
+ else if (column.type === 'boolean') {
7679
+ value = target.checked;
7680
+ }
7681
+ else if (column.type === 'date') {
7682
+ value = target.value
7683
+ ? new Date(`${target.value}T00:00:00.000Z`).toISOString().slice(0, 10)
7684
+ : null;
7685
+ }
7686
+ else if (column.type === 'json') {
7687
+ if (!target.value.trim()) {
7688
+ value = null;
7689
+ }
7690
+ else {
7691
+ try {
7692
+ value = JSON.parse(target.value);
7693
+ }
7694
+ catch {
7695
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter valid JSON.`);
7696
+ return;
7697
+ }
7698
+ }
7699
+ }
7700
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: value } : row);
7701
+ this.commit({ ...this.data(), rows });
7702
+ }
7703
+ onCellKeydown(event, row, column) {
7704
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
7705
+ const target = event.target;
7706
+ if (target.tagName === 'TEXTAREA') {
7707
+ return;
7708
+ }
7709
+ if (target instanceof HTMLInputElement &&
7710
+ target.type !== 'checkbox' &&
7711
+ (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
7712
+ const atBoundary = event.key === 'ArrowLeft'
7713
+ ? target.selectionStart === 0
7714
+ : target.selectionStart === target.value.length;
7715
+ if (!atBoundary) {
7716
+ return;
7717
+ }
7718
+ }
7719
+ event.preventDefault();
7720
+ const next = {
7721
+ row: row +
7722
+ (event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0),
7723
+ column: column +
7724
+ (event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0),
7725
+ };
7726
+ this.focusCell(next.row, next.column);
7727
+ return;
7728
+ }
7729
+ if (event.target.tagName === 'TEXTAREA' &&
7730
+ event.key === 'Enter' &&
7731
+ !(event.ctrlKey || event.metaKey)) {
7732
+ return;
7733
+ }
7734
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'z') {
7735
+ event.preventDefault();
7736
+ event.shiftKey ? this.redo() : this.undo();
7737
+ return;
7738
+ }
7739
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'y') {
7740
+ event.preventDefault();
7741
+ this.redo();
7742
+ return;
7743
+ }
7744
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') {
7745
+ event.preventDefault();
7746
+ const value = this.cellValue(row, this.data().columns[column]);
7747
+ void (navigator.clipboard?.writeText(value) ??
7748
+ Promise.reject(new Error('Clipboard access is unavailable.'))).catch(() => {
7749
+ this.validationError.set('Could not access the clipboard.');
7750
+ });
7751
+ return;
7752
+ }
7753
+ if (event.key === 'Escape') {
7754
+ event.target.blur();
7755
+ return;
7756
+ }
7757
+ if (event.key === 'Tab' || event.key === 'Enter') {
7758
+ event.preventDefault();
7759
+ const direction = event.shiftKey ? -1 : 1;
7760
+ let next = { row, column };
7761
+ if (event.key === 'Tab') {
7762
+ let nextColumn = column + direction;
7763
+ let nextRow = row;
7764
+ if (nextColumn >= this.data().columns.length) {
7765
+ nextColumn = 0;
7766
+ nextRow += 1;
7767
+ }
7768
+ else if (nextColumn < 0) {
7769
+ nextColumn = this.data().columns.length - 1;
7770
+ nextRow -= 1;
7771
+ }
7772
+ next = { row: nextRow, column: nextColumn };
7773
+ }
7774
+ else {
7775
+ next = { row: row + direction, column };
7776
+ }
7777
+ if (event.key === 'Tab' &&
7778
+ !event.shiftKey &&
7779
+ next.row === this.data().rows.length) {
7780
+ this.addRow();
7781
+ }
7782
+ this.activeCell.set(next);
7783
+ this.focusCell(next.row, next.column);
7784
+ }
7785
+ else if (event.key === 'Backspace' || event.key === 'Delete') {
7786
+ if (!['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
7787
+ event.preventDefault();
7788
+ this.clearCell(row, column);
7789
+ }
7790
+ }
7791
+ }
7792
+ onCellPaste(event, rowIndex, columnIndex) {
7793
+ if (!this.canEdit()) {
7794
+ return;
7795
+ }
7796
+ const text = event.clipboardData?.getData('text/plain');
7797
+ if (!text || (!text.includes('\t') && !/[\r\n]/.test(text))) {
7798
+ return;
7799
+ }
7800
+ event.preventDefault();
7801
+ try {
7802
+ const matrix = parseClipboardMatrix(text);
7803
+ const columns = [...this.data().columns];
7804
+ const pastedWidth = Math.max(...matrix.map(row => row.length));
7805
+ while (columns.length < columnIndex + pastedWidth &&
7806
+ columns.length < 200) {
7807
+ let number = columns.length + 1;
7808
+ let key = `column${number}`;
7809
+ while (columns.some(column => column.key === key)) {
7810
+ key = `column${++number}`;
7811
+ }
7812
+ columns.push({ key, label: `Column ${number}`, type: 'string' });
7813
+ }
7814
+ if (columns.length < columnIndex + pastedWidth) {
7815
+ throw new Error('Pasted data exceeds the 200-column table limit.');
7816
+ }
7817
+ const rows = [...this.data().rows];
7818
+ while (rows.length < rowIndex + matrix.length && rows.length < 10_000) {
7819
+ const newRow = {};
7820
+ for (const column of columns) {
7821
+ newRow[column.key] = null;
7822
+ }
7823
+ rows.push(newRow);
7824
+ }
7825
+ if (rows.length < rowIndex + matrix.length) {
7826
+ throw new Error('Pasted data exceeds the 10,000-row table limit.');
7827
+ }
7828
+ matrix.forEach((values, pastedRowIndex) => {
7829
+ values.forEach((rawValue, pastedColumnIndex) => {
7830
+ const targetColumn = columnIndex + pastedColumnIndex;
7831
+ const column = columns[targetColumn];
7832
+ rows[rowIndex + pastedRowIndex] = {
7833
+ ...rows[rowIndex + pastedRowIndex],
7834
+ [column.key]: parsePastedCell(rawValue, column),
7835
+ };
7836
+ });
7837
+ });
7838
+ this.commit({ version: 1, columns, rows });
7839
+ }
7840
+ catch (error) {
7841
+ this.validationError.set(error instanceof Error ? error.message : 'The pasted data is invalid.');
7842
+ }
7843
+ }
7844
+ onRowDrop(event) {
7845
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7846
+ return;
7847
+ }
7848
+ const rows = [...this.data().rows];
7849
+ moveItemInArray(rows, event.previousIndex, event.currentIndex);
7850
+ this.commit({ ...this.data(), rows });
7851
+ }
7852
+ onColumnDrop(event) {
7853
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7854
+ return;
7855
+ }
7856
+ const columns = [...this.data().columns];
7857
+ moveItemInArray(columns, event.previousIndex, event.currentIndex);
7858
+ this.commit({ ...this.data(), columns });
7859
+ }
7860
+ undo() {
7861
+ if (!this.canEdit()) {
7862
+ return;
7863
+ }
7864
+ const previous = this.undoStack.pop();
7865
+ if (!previous) {
7866
+ return;
7867
+ }
7868
+ this.redoStack.push(this.data());
7869
+ this.data.set(previous);
7870
+ this.validationError.set('');
7871
+ }
7872
+ redo() {
7873
+ if (!this.canEdit()) {
7874
+ return;
7875
+ }
7876
+ const next = this.redoStack.pop();
7877
+ if (!next) {
7878
+ return;
7879
+ }
7880
+ this.undoStack.push(this.data());
7881
+ this.data.set(next);
7882
+ this.validationError.set('');
7883
+ }
7884
+ async importFile(event) {
7885
+ const inputElement = event.target;
7886
+ const file = inputElement.files?.[0];
7887
+ inputElement.value = '';
7888
+ if (!file || !this.canEdit()) {
7889
+ return;
7890
+ }
7891
+ try {
7892
+ if (file.size > TAON_SPREADSHEET_MAX_BYTES) {
7893
+ throw new Error('Import files must be 10 MB or smaller.');
7894
+ }
7895
+ const text = await file.text();
7896
+ const imported = /\.json$/i.test(file.name)
7897
+ ? importSpreadsheetJson(text)
7898
+ : /\.csv$/i.test(file.name)
7899
+ ? parseSpreadsheetCsv(text)
7900
+ : (() => {
7901
+ throw new Error('Only JSON and CSV files are supported.');
7902
+ })();
7903
+ this.importFormat.set(/\.json$/i.test(file.name) ? 'JSON' : 'CSV');
7904
+ this.importMode.set('replace');
7905
+ this.pendingImport.set(imported);
7906
+ }
7907
+ catch (error) {
7908
+ this.validationError.set(error instanceof Error
7909
+ ? error.message
7910
+ : 'The selected file could not be imported.');
7911
+ }
7912
+ }
7913
+ applyImport() {
7914
+ const imported = this.pendingImport();
7915
+ if (!imported || !this.canEdit()) {
7916
+ return;
7917
+ }
7918
+ if (this.importMode() === 'replace' &&
7919
+ (this.data().rows.length > 0 || this.data().columns.length > 0) &&
7920
+ !globalThis.confirm('Replace the current table with the imported data?')) {
7921
+ return;
7922
+ }
7923
+ if (this.importMode() === 'append') {
7924
+ const currentColumns = this.data().columns;
7925
+ const compatible = currentColumns.length === imported.columns.length &&
7926
+ currentColumns.every((column, index) => column.key === imported.columns[index].key &&
7927
+ column.type === imported.columns[index].type);
7928
+ if (!compatible) {
7929
+ this.validationError.set('Append requires imported columns with the same keys, types, and order as this table.');
7930
+ return;
7931
+ }
7932
+ this.commit({
7933
+ ...this.data(),
7934
+ rows: [...this.data().rows, ...imported.rows],
7935
+ });
7936
+ }
7937
+ else {
7938
+ this.commit(imported);
7939
+ }
7940
+ this.pendingImport.set(null);
7941
+ }
7942
+ cancelImport() {
7943
+ this.pendingImport.set(null);
7944
+ }
7945
+ previewCell(row, column) {
7946
+ const value = row[column.key];
7947
+ return value === undefined
7948
+ ? ''
7949
+ : typeof value === 'string'
7950
+ ? value
7951
+ : JSON.stringify(value);
7952
+ }
7953
+ downloadJson(fullTable = false) {
7954
+ const content = JSON.stringify(fullTable ? this.data() : this.data().rows, null, 2);
7955
+ this.download(content, fullTable ? 'json-table.json' : 'table-data.json', 'application/json');
7956
+ }
7957
+ downloadCsv() {
7958
+ this.download(serializeSpreadsheetCsv(this.data()), 'table-data.csv', 'text/csv');
7959
+ }
7960
+ async copyJson() {
7961
+ try {
7962
+ if (!navigator.clipboard) {
7963
+ throw new Error('Clipboard access is unavailable.');
7964
+ }
7965
+ await navigator.clipboard.writeText(this.jsonPreview());
7966
+ this.validationError.set('');
7967
+ }
7968
+ catch {
7969
+ this.validationError.set('Could not access the clipboard. Copy the preview manually.');
7970
+ }
7971
+ }
7972
+ keyFromLabel(label) {
7973
+ return normalizeSpreadsheetKey(label);
7974
+ }
7975
+ setColumnWidth(columnIndex, value) {
7976
+ if (!this.canEdit()) {
7977
+ return;
7978
+ }
7979
+ const width = Number(value);
7980
+ if (!Number.isFinite(width) || width < 40 || width > 1000) {
7981
+ this.validationError.set('Column width must be between 40 and 1000 pixels.');
7982
+ return;
7983
+ }
7984
+ const columns = this.data().columns.map(column => ({ ...column }));
7985
+ columns[columnIndex] = { ...columns[columnIndex], width };
7986
+ this.commit({ ...this.data(), columns });
7987
+ }
7988
+ cellValue(rowIndex, column) {
7989
+ const value = this.data().rows[rowIndex]?.[column.key];
7990
+ if (column.type === 'json' && value !== null && value !== undefined) {
7991
+ return JSON.stringify(value);
7992
+ }
7993
+ return value === null || value === undefined ? '' : String(value);
7994
+ }
7995
+ trackColumn(_index, column) {
7996
+ return column.key;
7997
+ }
7998
+ clearCell(rowIndex, columnIndex) {
7999
+ const column = this.data().columns[columnIndex];
8000
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: null } : row);
8001
+ this.commit({ ...this.data(), rows });
8002
+ }
8003
+ focusCell(row, column) {
8004
+ if (row < 0 ||
8005
+ row >= this.data().rows.length ||
8006
+ column < 0 ||
8007
+ column >= this.data().columns.length) {
8008
+ return;
8009
+ }
8010
+ this.activeCell.set({ row, column });
8011
+ setTimeout(() => {
8012
+ document
8013
+ .querySelector(`[data-cell="${row}:${column}"]`)
8014
+ ?.focus();
8015
+ });
8016
+ }
8017
+ commit(value) {
8018
+ try {
8019
+ const validated = validateSpreadsheetData(value);
8020
+ this.undoStack.push(this.data());
8021
+ if (this.undoStack.length > 100) {
8022
+ this.undoStack.shift();
8023
+ }
8024
+ this.redoStack.length = 0;
8025
+ this.data.set(validated);
8026
+ this.validationError.set('');
8027
+ }
8028
+ catch (error) {
8029
+ this.validationError.set(error instanceof Error ? error.message : 'Table data is invalid.');
8030
+ }
8031
+ }
8032
+ canEdit() {
8033
+ return !this.readOnly() && !this.disabled();
8034
+ }
8035
+ download(content, fileName, contentType) {
8036
+ const url = URL.createObjectURL(new Blob([content], { type: contentType }));
8037
+ const anchor = document.createElement('a');
8038
+ anchor.href = url;
8039
+ anchor.download = fileName;
8040
+ anchor.click();
8041
+ URL.revokeObjectURL(url);
8042
+ }
8043
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8044
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: TaonSpreadsheetTableComponent, isStandalone: true, selector: "taon-spreadsheet-table", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, validationError: { classPropertyName: "validationError", publicName: "validationError", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { data: "dataChange", validationError: "validationErrorChange" }, ngImport: i0, template: "<section\n class=\"spreadsheet\"\n [class.read-only]=\"readOnly() || disabled()\">\n <header class=\"toolbar\">\n @if (!readOnly()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"addRow()\">\n <mat-icon>add</mat-icon> Add row\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"addColumn()\">\n <mat-icon>view_column</mat-icon> Add column\n </button>\n <label class=\"import-button\">\n <mat-icon>upload_file</mat-icon> Import JSON/CSV\n <input\n type=\"file\"\n accept=\".json,.csv,application/json,text/csv\"\n [disabled]=\"disabled()\"\n (change)=\"importFile($event)\" />\n </label>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"undo()\">\n Undo\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"redo()\">\n Redo\n </button>\n }\n <div class=\"w-full\">\n <button\n mat-button\n type=\"button\"\n (click)=\"view.set('table')\">\n Table\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"view.set('json')\">\n JSON Preview\n </button>\n </div>\n @if (!readOnly()) {\n <button\n mat-button\n type=\"button\"\n (click)=\"view.set('settings')\">\n Settings\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"downloadJson()\">\n Export JSON\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"downloadJson(true)\">\n Full JSON\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"downloadCsv()\">\n Export CSV\n </button>\n }\n </header>\n\n @if (validationError()) {\n <p\n class=\"validation-error\"\n role=\"alert\">\n {{ validationError() }}\n </p>\n }\n\n @if (pendingImport(); as imported) {\n <section\n class=\"import-preview\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"import-preview-title\">\n <h2 id=\"import-preview-title\">Preview {{ importFormat() }} import</h2>\n <p>\n {{ imported.rows.length }} rows \u00B7 {{ imported.columns.length }} columns\n </p>\n @if (data().rows.length || data().columns.length) {\n <label>\n Import mode\n <mat-select\n [value]=\"importMode()\"\n (selectionChange)=\"importMode.set($event.value)\">\n <mat-option value=\"replace\">Replace table</mat-option>\n <mat-option value=\"append\">Append rows</mat-option>\n </mat-select>\n </label>\n }\n <div class=\"table-scroll\">\n <table>\n <thead>\n <tr>\n @for (column of imported.columns; track column.key) {\n <th>\n {{ column.label || column.key }}\n <small>{{ column.key }} \u00B7 {{ column.type }}</small>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of imported.rows.slice(0, 5); track $index) {\n <tr>\n @for (column of imported.columns; track column.key) {\n <td>{{ previewCell(row, column) }}</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n <footer>\n <button\n mat-button\n type=\"button\"\n (click)=\"cancelImport()\">\n Cancel\n </button>\n <button\n mat-flat-button\n type=\"button\"\n (click)=\"applyImport()\">\n Apply import\n </button>\n </footer>\n </section>\n }\n\n @if (view() === 'table') {\n @if (!data().columns.length) {\n <div class=\"empty-state\">\n <p>No columns yet. Add a column or import a JSON/CSV file.</p>\n @if (!readOnly()) {\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"addColumn()\">\n Add first column\n </button>\n }\n </div>\n } @else {\n <div class=\"table-scroll\">\n <table>\n <thead\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListDisabled]=\"readOnly() || disabled()\"\n (cdkDropListDropped)=\"onColumnDrop($event)\">\n <tr>\n <th class=\"row-number\">#</th>\n @for (\n column of data().columns;\n track trackColumn($index, column)\n ) {\n <th\n cdkDrag\n cdkDragHandle\n [cdkDragDisabled]=\"readOnly() || disabled()\"\n [style.minWidth.px]=\"column.width || 140\">\n {{ column.label || column.key }}\n @if (!readOnly()) {\n <small>{{ column.type }}</small>\n }\n </th>\n }\n @if (!readOnly()) {\n <th class=\"row-actions\">Actions</th>\n }\n </tr>\n </thead>\n <tbody\n cdkDropList\n [cdkDropListDisabled]=\"readOnly() || disabled()\"\n (cdkDropListDropped)=\"onRowDrop($event)\">\n @for (row of data().rows; track $index; let rowIndex = $index) {\n <tr\n cdkDrag\n [cdkDragDisabled]=\"readOnly() || disabled()\">\n <th\n class=\"row-number\"\n cdkDragHandle>\n {{ rowIndex + 1 }}\n </th>\n @for (\n column of data().columns;\n track trackColumn($index, column);\n let columnIndex = $index\n ) {\n <td\n [class.active]=\"\n activeCell()?.row === rowIndex &&\n activeCell()?.column === columnIndex\n \">\n @if (column.type === 'boolean') {\n <input\n type=\"checkbox\"\n [checked]=\"row[column.key] === true\"\n [disabled]=\"readOnly() || disabled()\"\n [attr.data-cell]=\"rowIndex + ':' + columnIndex\"\n (focus)=\"\n activeCell.set({ row: rowIndex, column: columnIndex })\n \"\n (change)=\"setCell(rowIndex, column, $event)\"\n (keydown)=\"\n onCellKeydown($event, rowIndex, columnIndex)\n \" />\n } @else if (column.type === 'json') {\n <textarea\n [value]=\"cellValue(rowIndex, column)\"\n [readonly]=\"readOnly() || disabled()\"\n [attr.data-cell]=\"rowIndex + ':' + columnIndex\"\n (focus)=\"\n activeCell.set({ row: rowIndex, column: columnIndex })\n \"\n (input)=\"setCell(rowIndex, column, $event)\"\n (paste)=\"onCellPaste($event, rowIndex, columnIndex)\"\n (keydown)=\"\n onCellKeydown($event, rowIndex, columnIndex)\n \"></textarea>\n } @else {\n <input\n [type]=\"\n column.type === 'number'\n ? 'number'\n : column.type === 'date'\n ? 'date'\n : 'text'\n \"\n [value]=\"cellValue(rowIndex, column)\"\n [readonly]=\"readOnly() || disabled()\"\n [attr.data-cell]=\"rowIndex + ':' + columnIndex\"\n (focus)=\"\n activeCell.set({ row: rowIndex, column: columnIndex })\n \"\n (input)=\"setCell(rowIndex, column, $event)\"\n (paste)=\"onCellPaste($event, rowIndex, columnIndex)\"\n (keydown)=\"\n onCellKeydown($event, rowIndex, columnIndex)\n \" />\n }\n </td>\n }\n @if (!readOnly()) {\n <td class=\"row-actions\">\n <button\n mat-icon-button\n type=\"button\"\n aria-label=\"Duplicate row\"\n (click)=\"duplicateRow(rowIndex)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n aria-label=\"Delete row\"\n (click)=\"deleteRow(rowIndex)\">\n <mat-icon>delete</mat-icon>\n </button>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (view() === 'json') {\n <div class=\"json-preview\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"copyJson()\">\n Copy JSON\n </button>\n <pre>{{ jsonPreview() }}</pre>\n </div>\n } @else {\n <div class=\"settings\">\n @for (\n column of data().columns;\n track trackColumn($index, column);\n let columnIndex = $index\n ) {\n <fieldset>\n <legend>Column {{ columnIndex + 1 }}</legend>\n <label>\n Label\n <input\n [value]=\"column.label || ''\"\n [disabled]=\"disabled()\"\n (change)=\"\n renameColumn(columnIndex, 'label', $any($event.target).value)\n \" />\n </label>\n <label>\n Key\n <input\n [value]=\"column.key\"\n [disabled]=\"disabled()\"\n (change)=\"\n renameColumn(columnIndex, 'key', $any($event.target).value)\n \" />\n </label>\n <label>\n Type\n <mat-select\n [value]=\"column.type\"\n [disabled]=\"disabled()\"\n (selectionChange)=\"setColumnType(columnIndex, $event.value)\">\n @for (type of columnTypes; track type) {\n <mat-option [value]=\"type\">{{ type }}</mat-option>\n }\n </mat-select>\n </label>\n <label>\n Width (px)\n <input\n type=\"number\"\n min=\"40\"\n max=\"1000\"\n [value]=\"column.width || 140\"\n [disabled]=\"disabled()\"\n (change)=\"\n setColumnWidth(columnIndex, $any($event.target).value)\n \" />\n </label>\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"deleteColumn(columnIndex)\">\n Delete column\n </button>\n </fieldset>\n }\n </div>\n }\n</section>\n", styles: [":host{display:block;min-width:0}.spreadsheet{border:1px solid rgba(136,136,136,.3333333333);border-radius:6px;min-width:0}.toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem;padding:.5rem;border-bottom:1px solid rgba(136,136,136,.3333333333)}.import-button{display:inline-flex;align-items:center;gap:.35rem;padding:0 .75rem;cursor:pointer;font:inherit}.import-button input{display:none}.table-scroll{max-width:100%;max-height:60vh;overflow:auto}table{width:max-content;min-width:100%;border-collapse:separate;border-spacing:0}th,td{padding:.35rem;border-right:1px solid rgba(136,136,136,.2);border-bottom:1px solid rgba(136,136,136,.2);text-align:left}thead th{position:sticky;top:0;z-index:1;background:var(--mat-sys-surface, white)}thead small{display:block;opacity:.65}.row-number{position:sticky;left:0;z-index:2;width:3rem;background:var(--mat-sys-surface, white);text-align:center}td input:not([type=checkbox]),td textarea{box-sizing:border-box;width:100%;min-width:7rem;padding:.4rem;border:1px solid transparent;color:inherit;background:transparent;font:inherit}td textarea{min-height:2.5rem;resize:vertical}td input:focus,td textarea:focus,td.active{outline:2px solid #3f51b5;outline-offset:-2px}.row-actions{white-space:nowrap}.empty-state,.json-preview,.settings{padding:1rem}.json-preview pre{max-height:60vh;overflow:auto;padding:1rem;background:#8881;font-family:monospace;white-space:pre-wrap;overflow-wrap:anywhere}.settings{display:grid;gap:.75rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr))}.settings fieldset{display:grid;gap:.75rem;min-width:0}.settings label{display:grid;gap:.25rem}.settings input{min-width:0;padding:.5rem}.validation-error{margin:0;padding:.75rem 1rem;color:#b3261e}.import-preview{position:fixed;inset:10vh max(1rem,(100vw - 56rem) / 2);z-index:1000;display:grid;align-content:start;gap:.75rem;overflow:auto;padding:1.25rem;border:1px solid rgba(136,136,136,.4666666667);border-radius:8px;background:var(--mat-sys-surface, white);box-shadow:0 1rem 4rem #0005}.import-preview h2,.import-preview p{margin:0}.import-preview footer{display:flex;justify-content:flex-end;gap:.5rem}.read-only td input,.read-only td textarea{cursor:default}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8045
+ }
8046
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, decorators: [{
8047
+ type: Component,
8048
+ args: [{ selector: 'taon-spreadsheet-table', standalone: true, imports: [
8049
+ CdkDrag,
8050
+ CdkDragHandle,
8051
+ CdkDropList,
8052
+ MatButtonModule,
8053
+ MatIconModule,
8054
+ MatSelectModule,
8055
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section\n class=\"spreadsheet\"\n [class.read-only]=\"readOnly() || disabled()\">\n <header class=\"toolbar\">\n @if (!readOnly()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"addRow()\">\n <mat-icon>add</mat-icon> Add row\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"addColumn()\">\n <mat-icon>view_column</mat-icon> Add column\n </button>\n <label class=\"import-button\">\n <mat-icon>upload_file</mat-icon> Import JSON/CSV\n <input\n type=\"file\"\n accept=\".json,.csv,application/json,text/csv\"\n [disabled]=\"disabled()\"\n (change)=\"importFile($event)\" />\n </label>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"undo()\">\n Undo\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"redo()\">\n Redo\n </button>\n }\n <div class=\"w-full\">\n <button\n mat-button\n type=\"button\"\n (click)=\"view.set('table')\">\n Table\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"view.set('json')\">\n JSON Preview\n </button>\n </div>\n @if (!readOnly()) {\n <button\n mat-button\n type=\"button\"\n (click)=\"view.set('settings')\">\n Settings\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"downloadJson()\">\n Export JSON\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"downloadJson(true)\">\n Full JSON\n </button>\n <button\n mat-button\n type=\"button\"\n (click)=\"downloadCsv()\">\n Export CSV\n </button>\n }\n </header>\n\n @if (validationError()) {\n <p\n class=\"validation-error\"\n role=\"alert\">\n {{ validationError() }}\n </p>\n }\n\n @if (pendingImport(); as imported) {\n <section\n class=\"import-preview\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"import-preview-title\">\n <h2 id=\"import-preview-title\">Preview {{ importFormat() }} import</h2>\n <p>\n {{ imported.rows.length }} rows \u00B7 {{ imported.columns.length }} columns\n </p>\n @if (data().rows.length || data().columns.length) {\n <label>\n Import mode\n <mat-select\n [value]=\"importMode()\"\n (selectionChange)=\"importMode.set($event.value)\">\n <mat-option value=\"replace\">Replace table</mat-option>\n <mat-option value=\"append\">Append rows</mat-option>\n </mat-select>\n </label>\n }\n <div class=\"table-scroll\">\n <table>\n <thead>\n <tr>\n @for (column of imported.columns; track column.key) {\n <th>\n {{ column.label || column.key }}\n <small>{{ column.key }} \u00B7 {{ column.type }}</small>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of imported.rows.slice(0, 5); track $index) {\n <tr>\n @for (column of imported.columns; track column.key) {\n <td>{{ previewCell(row, column) }}</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n <footer>\n <button\n mat-button\n type=\"button\"\n (click)=\"cancelImport()\">\n Cancel\n </button>\n <button\n mat-flat-button\n type=\"button\"\n (click)=\"applyImport()\">\n Apply import\n </button>\n </footer>\n </section>\n }\n\n @if (view() === 'table') {\n @if (!data().columns.length) {\n <div class=\"empty-state\">\n <p>No columns yet. Add a column or import a JSON/CSV file.</p>\n @if (!readOnly()) {\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"addColumn()\">\n Add first column\n </button>\n }\n </div>\n } @else {\n <div class=\"table-scroll\">\n <table>\n <thead\n cdkDropList\n cdkDropListOrientation=\"horizontal\"\n [cdkDropListDisabled]=\"readOnly() || disabled()\"\n (cdkDropListDropped)=\"onColumnDrop($event)\">\n <tr>\n <th class=\"row-number\">#</th>\n @for (\n column of data().columns;\n track trackColumn($index, column)\n ) {\n <th\n cdkDrag\n cdkDragHandle\n [cdkDragDisabled]=\"readOnly() || disabled()\"\n [style.minWidth.px]=\"column.width || 140\">\n {{ column.label || column.key }}\n @if (!readOnly()) {\n <small>{{ column.type }}</small>\n }\n </th>\n }\n @if (!readOnly()) {\n <th class=\"row-actions\">Actions</th>\n }\n </tr>\n </thead>\n <tbody\n cdkDropList\n [cdkDropListDisabled]=\"readOnly() || disabled()\"\n (cdkDropListDropped)=\"onRowDrop($event)\">\n @for (row of data().rows; track $index; let rowIndex = $index) {\n <tr\n cdkDrag\n [cdkDragDisabled]=\"readOnly() || disabled()\">\n <th\n class=\"row-number\"\n cdkDragHandle>\n {{ rowIndex + 1 }}\n </th>\n @for (\n column of data().columns;\n track trackColumn($index, column);\n let columnIndex = $index\n ) {\n <td\n [class.active]=\"\n activeCell()?.row === rowIndex &&\n activeCell()?.column === columnIndex\n \">\n @if (column.type === 'boolean') {\n <input\n type=\"checkbox\"\n [checked]=\"row[column.key] === true\"\n [disabled]=\"readOnly() || disabled()\"\n [attr.data-cell]=\"rowIndex + ':' + columnIndex\"\n (focus)=\"\n activeCell.set({ row: rowIndex, column: columnIndex })\n \"\n (change)=\"setCell(rowIndex, column, $event)\"\n (keydown)=\"\n onCellKeydown($event, rowIndex, columnIndex)\n \" />\n } @else if (column.type === 'json') {\n <textarea\n [value]=\"cellValue(rowIndex, column)\"\n [readonly]=\"readOnly() || disabled()\"\n [attr.data-cell]=\"rowIndex + ':' + columnIndex\"\n (focus)=\"\n activeCell.set({ row: rowIndex, column: columnIndex })\n \"\n (input)=\"setCell(rowIndex, column, $event)\"\n (paste)=\"onCellPaste($event, rowIndex, columnIndex)\"\n (keydown)=\"\n onCellKeydown($event, rowIndex, columnIndex)\n \"></textarea>\n } @else {\n <input\n [type]=\"\n column.type === 'number'\n ? 'number'\n : column.type === 'date'\n ? 'date'\n : 'text'\n \"\n [value]=\"cellValue(rowIndex, column)\"\n [readonly]=\"readOnly() || disabled()\"\n [attr.data-cell]=\"rowIndex + ':' + columnIndex\"\n (focus)=\"\n activeCell.set({ row: rowIndex, column: columnIndex })\n \"\n (input)=\"setCell(rowIndex, column, $event)\"\n (paste)=\"onCellPaste($event, rowIndex, columnIndex)\"\n (keydown)=\"\n onCellKeydown($event, rowIndex, columnIndex)\n \" />\n }\n </td>\n }\n @if (!readOnly()) {\n <td class=\"row-actions\">\n <button\n mat-icon-button\n type=\"button\"\n aria-label=\"Duplicate row\"\n (click)=\"duplicateRow(rowIndex)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n aria-label=\"Delete row\"\n (click)=\"deleteRow(rowIndex)\">\n <mat-icon>delete</mat-icon>\n </button>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n } @else if (view() === 'json') {\n <div class=\"json-preview\">\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"copyJson()\">\n Copy JSON\n </button>\n <pre>{{ jsonPreview() }}</pre>\n </div>\n } @else {\n <div class=\"settings\">\n @for (\n column of data().columns;\n track trackColumn($index, column);\n let columnIndex = $index\n ) {\n <fieldset>\n <legend>Column {{ columnIndex + 1 }}</legend>\n <label>\n Label\n <input\n [value]=\"column.label || ''\"\n [disabled]=\"disabled()\"\n (change)=\"\n renameColumn(columnIndex, 'label', $any($event.target).value)\n \" />\n </label>\n <label>\n Key\n <input\n [value]=\"column.key\"\n [disabled]=\"disabled()\"\n (change)=\"\n renameColumn(columnIndex, 'key', $any($event.target).value)\n \" />\n </label>\n <label>\n Type\n <mat-select\n [value]=\"column.type\"\n [disabled]=\"disabled()\"\n (selectionChange)=\"setColumnType(columnIndex, $event.value)\">\n @for (type of columnTypes; track type) {\n <mat-option [value]=\"type\">{{ type }}</mat-option>\n }\n </mat-select>\n </label>\n <label>\n Width (px)\n <input\n type=\"number\"\n min=\"40\"\n max=\"1000\"\n [value]=\"column.width || 140\"\n [disabled]=\"disabled()\"\n (change)=\"\n setColumnWidth(columnIndex, $any($event.target).value)\n \" />\n </label>\n <button\n mat-stroked-button\n type=\"button\"\n [disabled]=\"disabled()\"\n (click)=\"deleteColumn(columnIndex)\">\n Delete column\n </button>\n </fieldset>\n }\n </div>\n }\n</section>\n", styles: [":host{display:block;min-width:0}.spreadsheet{border:1px solid rgba(136,136,136,.3333333333);border-radius:6px;min-width:0}.toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem;padding:.5rem;border-bottom:1px solid rgba(136,136,136,.3333333333)}.import-button{display:inline-flex;align-items:center;gap:.35rem;padding:0 .75rem;cursor:pointer;font:inherit}.import-button input{display:none}.table-scroll{max-width:100%;max-height:60vh;overflow:auto}table{width:max-content;min-width:100%;border-collapse:separate;border-spacing:0}th,td{padding:.35rem;border-right:1px solid rgba(136,136,136,.2);border-bottom:1px solid rgba(136,136,136,.2);text-align:left}thead th{position:sticky;top:0;z-index:1;background:var(--mat-sys-surface, white)}thead small{display:block;opacity:.65}.row-number{position:sticky;left:0;z-index:2;width:3rem;background:var(--mat-sys-surface, white);text-align:center}td input:not([type=checkbox]),td textarea{box-sizing:border-box;width:100%;min-width:7rem;padding:.4rem;border:1px solid transparent;color:inherit;background:transparent;font:inherit}td textarea{min-height:2.5rem;resize:vertical}td input:focus,td textarea:focus,td.active{outline:2px solid #3f51b5;outline-offset:-2px}.row-actions{white-space:nowrap}.empty-state,.json-preview,.settings{padding:1rem}.json-preview pre{max-height:60vh;overflow:auto;padding:1rem;background:#8881;font-family:monospace;white-space:pre-wrap;overflow-wrap:anywhere}.settings{display:grid;gap:.75rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr))}.settings fieldset{display:grid;gap:.75rem;min-width:0}.settings label{display:grid;gap:.25rem}.settings input{min-width:0;padding:.5rem}.validation-error{margin:0;padding:.75rem 1rem;color:#b3261e}.import-preview{position:fixed;inset:10vh max(1rem,(100vw - 56rem) / 2);z-index:1000;display:grid;align-content:start;gap:.75rem;overflow:auto;padding:1.25rem;border:1px solid rgba(136,136,136,.4666666667);border-radius:8px;background:var(--mat-sys-surface, white);box-shadow:0 1rem 4rem #0005}.import-preview h2,.import-preview p{margin:0}.import-preview footer{display:flex;justify-content:flex-end;gap:.5rem}.read-only td input,.read-only td textarea{cursor:default}\n"] }]
8056
+ }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }, { type: i0.Output, args: ["dataChange"] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], validationError: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationError", required: false }] }, { type: i0.Output, args: ["validationErrorChange"] }] } });
8057
+ function parseClipboardMatrix(value) {
8058
+ const rows = [];
8059
+ let row = [];
8060
+ let cell = '';
8061
+ let quoted = false;
8062
+ for (let index = 0; index < value.length; index += 1) {
8063
+ const character = value[index];
8064
+ if (quoted) {
8065
+ if (character === '"' && value[index + 1] === '"') {
8066
+ cell += '"';
8067
+ index += 1;
8068
+ }
8069
+ else if (character === '"') {
8070
+ quoted = false;
8071
+ }
8072
+ else {
8073
+ cell += character;
8074
+ }
8075
+ }
8076
+ else if (character === '"' && !cell) {
8077
+ quoted = true;
8078
+ }
8079
+ else if (character === '\t') {
8080
+ row.push(cell);
8081
+ cell = '';
8082
+ }
8083
+ else if (character === '\r' || character === '\n') {
8084
+ row.push(cell);
8085
+ rows.push(row);
8086
+ row = [];
8087
+ cell = '';
8088
+ if (character === '\r' && value[index + 1] === '\n') {
8089
+ index += 1;
8090
+ }
8091
+ }
8092
+ else {
8093
+ cell += character;
8094
+ }
8095
+ }
8096
+ if (quoted) {
8097
+ throw new Error('Pasted data contains an unterminated quoted value.');
8098
+ }
8099
+ if (cell.length || row.length) {
8100
+ row.push(cell);
8101
+ rows.push(row);
8102
+ }
8103
+ return rows;
8104
+ }
8105
+ function parsePastedCell(value, column) {
8106
+ if (value === '') {
8107
+ return null;
8108
+ }
8109
+ if (column.type === 'number') {
8110
+ const parsed = Number(value);
8111
+ if (!Number.isFinite(parsed)) {
8112
+ throw new Error(`"${value}" is not a valid number for ${column.label || column.key}.`);
8113
+ }
8114
+ return parsed;
8115
+ }
8116
+ if (column.type === 'boolean') {
8117
+ if (/^(true|1|yes)$/i.test(value)) {
8118
+ return true;
8119
+ }
8120
+ if (/^(false|0|no)$/i.test(value)) {
8121
+ return false;
8122
+ }
8123
+ throw new Error(`"${value}" is not a valid boolean for ${column.label || column.key}.`);
8124
+ }
8125
+ if (column.type === 'date') {
8126
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
8127
+ Number.isNaN(Date.parse(`${value}T00:00:00.000Z`))) {
8128
+ throw new Error(`"${value}" is not a valid date for ${column.label || column.key}.`);
8129
+ }
8130
+ return value;
8131
+ }
8132
+ if (column.type === 'json') {
8133
+ try {
8134
+ return JSON.parse(value);
8135
+ }
8136
+ catch {
8137
+ throw new Error(`"${value}" is not valid JSON for ${column.label || column.key}.`);
8138
+ }
8139
+ }
8140
+ return value;
8141
+ }
8142
+
7010
8143
  //#region imports
7011
8144
  //#endregion
7012
8145
  class TaonCmsWebsiteFeatureApiService extends TaonBaseAngularService {
@@ -7170,7 +8303,7 @@ class TaonCmsWebsiteFeaturesComponent {
7170
8303
  }
7171
8304
  }
7172
8305
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7173
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: TaonCmsWebsiteFeaturesComponent, isStandalone: true, selector: "app-taon-cms-website-features", providers: [TaonCmsWebsiteFeatureApiService], ngImport: i0, template: "<h1>{{ t.gettext('Website Features') }}</h1>\n@if (error()) {\n <p role=\"alert\">{{ error() }}</p>\n <button mat-button type=\"button\" [disabled]=\"busy()\" (click)=\"reload()\">\n {{ t.gettext('Reload features') }}\n </button>\n}\n<p role=\"status\" aria-live=\"polite\">{{ feedback() }}</p>\n<ul [attr.aria-busy]=\"busy()\">\n @for (feature of features(); track feature.key) {\n <li>\n <mat-checkbox\n [checked]=\"feature.enabled\"\n [disabled]=\"busy()\"\n [attr.aria-label]=\"feature.name\"\n (change)=\"toggle(feature, $event.checked)\" />\n <div class=\"feature-name\">\n <span>{{ feature.name }}</span>\n <small>{{ feature.key }}</small>\n </div>\n <button mat-icon-button type=\"button\"\n [attr.aria-label]=\"t.gettext('Copy feature key')\"\n (click)=\"copyKey(feature.key)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n <button mat-icon-button type=\"button\" [disabled]=\"busy()\"\n [attr.aria-label]=\"t.gettext('Delete feature')\"\n (click)=\"deleteFeature(feature)\">\n <mat-icon>delete</mat-icon>\n </button>\n </li>\n }\n</ul>\n<form (ngSubmit)=\"addFeature()\">\n <mat-form-field class=\"flex-grow\">\n <mat-label>{{ t.gettext('Enter feature name') }}</mat-label>\n <input matInput name=\"featureName\" [(ngModel)]=\"name\"\n [disabled]=\"busy() || !loaded()\"\n [placeholder]=\"t.gettext('Enter feature name')\" />\n </mat-form-field>\n <button mat-flat-button type=\"submit\" class=\"flex\"\n [disabled]=\"busy() || !loaded() || !name.trim()\">\n {{ t.gettext('Add feature') }}\n </button>\n</form>\n", styles: [":host{display:block}ul{padding:0;list-style:none}li,form{display:flex;align-items:center;gap:.75rem;margin-block:.75rem}.feature-name{flex:1;min-width:0;overflow-wrap:anywhere}small{display:block;opacity:.7}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8306
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: TaonCmsWebsiteFeaturesComponent, isStandalone: true, selector: "app-taon-cms-website-features", providers: [TaonCmsWebsiteFeatureApiService], ngImport: i0, template: "<h1>{{ t.gettext('Website Features') }}</h1>\n@if (error()) {\n <p role=\"alert\">{{ error() }}</p>\n <button mat-button type=\"button\" [disabled]=\"busy()\" (click)=\"reload()\">\n {{ t.gettext('Reload features') }}\n </button>\n}\n<p role=\"status\" aria-live=\"polite\">{{ feedback() }}</p>\n<ul [attr.aria-busy]=\"busy()\">\n @for (feature of features(); track feature.key) {\n <li>\n <mat-checkbox\n [checked]=\"feature.enabled\"\n [disabled]=\"busy()\"\n [attr.aria-label]=\"feature.name\"\n (change)=\"toggle(feature, $event.checked)\" />\n <div class=\"feature-name\">\n <span>{{ feature.name }}</span>\n <small>{{ feature.key }}</small>\n </div>\n <button mat-icon-button type=\"button\"\n [attr.aria-label]=\"t.gettext('Copy feature key')\"\n (click)=\"copyKey(feature.key)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n <button mat-icon-button type=\"button\" [disabled]=\"busy()\"\n [attr.aria-label]=\"t.gettext('Delete feature')\"\n (click)=\"deleteFeature(feature)\">\n <mat-icon>delete</mat-icon>\n </button>\n </li>\n }\n</ul>\n<form (ngSubmit)=\"addFeature()\">\n <mat-form-field class=\"flex-grow\">\n <mat-label>{{ t.gettext('Enter feature name') }}</mat-label>\n <input matInput name=\"featureName\" [(ngModel)]=\"name\"\n [disabled]=\"busy() || !loaded()\"\n [placeholder]=\"t.gettext('Enter feature name')\" />\n </mat-form-field>\n <button mat-flat-button type=\"submit\" class=\"flex\"\n [disabled]=\"busy() || !loaded() || !name.trim()\">\n {{ t.gettext('Add feature') }}\n </button>\n</form>\n", styles: [":host{display:block}ul{padding:0;list-style:none}li,form{display:flex;align-items:center;gap:.75rem;margin-block:.75rem}.feature-name{flex:1;min-width:0;overflow-wrap:anywhere}small{display:block;opacity:.7}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7174
8307
  }
7175
8308
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, decorators: [{
7176
8309
  type: Component,
@@ -7274,7 +8407,7 @@ const TaonCmsCommentsBackofficeRoutes = [
7274
8407
  */
7275
8408
  // export default TaonCmsCommentsBackofficeRoutes;
7276
8409
 
7277
- const t$f = Translation.for('src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts', { "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-NG3-0ZeO.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-DncFM6D3.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-s2dXTIUw.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DAA5R9fI.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CyKmux96.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DgAHAzJM.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BTv1ecMg.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-CNPdfcRa.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-B5JcoASn.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.html.en-US.i18n.data-BTWsCDp3.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-CZhAYAQh.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.html.de-DE.i18n.data-C5Nxovtl.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.ts.en-US.i18n.data-D1Fh0Nd6.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-C-sVs8_U.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-D10xOAxh.mjs')).default } });
8410
+ const t$f = Translation.for('src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts', { "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-NG3-0ZeO.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-DncFM6D3.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-s2dXTIUw.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DAA5R9fI.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CyKmux96.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DgAHAzJM.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BTv1ecMg.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-CNPdfcRa.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-B5JcoASn.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.html.en-US.i18n.data-YiL6-4Aq.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-BzEKPeB_.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.html.de-DE.i18n.data-BdxSK5vd.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.ts.en-US.i18n.data-B8Vs_fm6.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-BaPFft3W.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-B_w2dmsP.mjs')).default } });
7278
8411
  class TaonCmsPostChoiceDialogComponent {
7279
8412
  constructor() {
7280
8413
  this.t = t$f.for(this);
@@ -7677,7 +8810,7 @@ class TaonCmsCourseBackofficeComponent extends TaonCmsPostTableActions {
7677
8810
  });
7678
8811
  }
7679
8812
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
7680
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: TaonCmsCourseBackofficeComponent, isStandalone: true, selector: "taon-cms-course-backoffice", providers: [TaonCmsContentApiService], viewQueries: [{ propertyName: "datatable", first: true, predicate: TaonDatatableComponent, descendants: true }, { propertyName: "coverTemplate", first: true, predicate: ["coverTemplate"], descendants: true, static: true }, { propertyName: "visibilityTemplate", first: true, predicate: ["visibilityTemplate"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<h1>{{ t.gettext('Courses') }}</h1>\n<h2 style=\"color: goldenrod;\">{{ t.gettext('A course is a post with related posts and a thumbnail. Any post that is not archived can be a course.') }}</h2>\n<button mat-button type=\"button\" [disabled]=\"loading()\" (click)=\"reload()\">\n {{ t.gettext('Reload courses') }}\n</button>\n@if (loading()) {\n <p role=\"status\">{{ t.gettext('Loading courses...') }}</p>\n}\n@if (error()) {\n <p role=\"alert\">{{ error() }}</p>\n}\n<taon-datatable\n [data]=\"courses()\"\n [columns]=\"columns\"\n [allowedColumns]=\"['id', 'title', 'status']\"\n tableId=\"cms-courses\"\n storeTableFilters=\"query-params\"\n [filtersTemplate]=\"filtersTemplate\"\n [hideAddButton]=\"true\"\n searchMode=\"advanced\"\n [expansionTemplate]=\"expansionTemplate\">\n</taon-datatable>\n<ng-template #coverTemplate let-post>\n <taon-cms-course-cover [post]=\"post\"></taon-cms-course-cover>\n</ng-template>\n<ng-template #visibilityTemplate let-post>\n <span class=\"visibility\">\n <mat-icon aria-hidden=\"true\">\n {{ post.status === 'published' ? 'lock_open' : 'lock' }}\n </mat-icon>\n {{ post.status === 'published' ? t.gettext('Public') : t.gettext('Private') }}\n </span>\n</ng-template>\n<ng-template #expansionTemplate let-course>\n <taon-cms-course-contents\n [course]=\"course\"\n [actionsColumn]=\"actionsColumn\"\n (editPostRequested)=\"openDetails($event)\">\n </taon-cms-course-contents>\n</ng-template>\n<ng-template\n #filtersTemplate\n let-filters=\"filters\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('ID') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"filters.id\"\n (keyup.enter)=\"datatable.applyAdvancedFilters()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Title') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"filters.title\"\n (keyup.enter)=\"datatable.applyAdvancedFilters()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Visibility') }}</mat-label>\n <mat-select [(ngModel)]=\"filters.status\">\n <mat-option value=\"published\">{{ t.gettext('Public') }}</mat-option>\n <mat-option value=\"draft\">{{ t.gettext('Private') }}</mat-option>\n </mat-select>\n </mat-form-field>\n</ng-template>\n", styles: [":host{display:block}.visibility{display:inline-flex;align-items:center;gap:.4rem}:host ::ng-deep td.cms-course-public{background-color:#2e7d3214}:host ::ng-deep td.cms-course-private{background-color:#c6282812}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: TaonDatatableComponent, selector: "taon-datatable", inputs: ["pageNumber", "pageSize", "pageSizeOptions", "allowedColumns", "expansionTemplate", "entityCrudController", "columns", "hideSearch", "hideAddButton", "safe", "searchMode", "filtersTemplate", "storeTablePagination", "storeTableFilters", "tableId", "callQueryMethod", "data", "showPaginator"], outputs: ["expansionChange", "addingItem"] }, { kind: "component", type: TaonCmsCourseContentsComponent, selector: "taon-cms-course-contents", inputs: ["course", "actionsColumn"], outputs: ["editPostRequested"] }, { kind: "component", type: TaonCmsCourseCoverComponent, selector: "taon-cms-course-cover", inputs: ["post"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8813
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: TaonCmsCourseBackofficeComponent, isStandalone: true, selector: "taon-cms-course-backoffice", providers: [TaonCmsContentApiService], viewQueries: [{ propertyName: "datatable", first: true, predicate: TaonDatatableComponent, descendants: true }, { propertyName: "coverTemplate", first: true, predicate: ["coverTemplate"], descendants: true, static: true }, { propertyName: "visibilityTemplate", first: true, predicate: ["visibilityTemplate"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<h1>{{ t.gettext('Courses') }}</h1>\n<h2 style=\"color: goldenrod;\">{{ t.gettext('A course is a post with related posts and a thumbnail. Any post that is not archived can be a course.') }}</h2>\n<button mat-button type=\"button\" [disabled]=\"loading()\" (click)=\"reload()\">\n {{ t.gettext('Reload courses') }}\n</button>\n@if (loading()) {\n <p role=\"status\">{{ t.gettext('Loading courses...') }}</p>\n}\n@if (error()) {\n <p role=\"alert\">{{ error() }}</p>\n}\n<taon-datatable\n [data]=\"courses()\"\n [columns]=\"columns\"\n [allowedColumns]=\"['id', 'title', 'status']\"\n tableId=\"cms-courses\"\n storeTableFilters=\"query-params\"\n [filtersTemplate]=\"filtersTemplate\"\n [hideAddButton]=\"true\"\n searchMode=\"advanced\"\n [expansionTemplate]=\"expansionTemplate\">\n</taon-datatable>\n<ng-template #coverTemplate let-post>\n <taon-cms-course-cover [post]=\"post\"></taon-cms-course-cover>\n</ng-template>\n<ng-template #visibilityTemplate let-post>\n <span class=\"visibility\">\n <mat-icon aria-hidden=\"true\">\n {{ post.status === 'published' ? 'lock_open' : 'lock' }}\n </mat-icon>\n {{ post.status === 'published' ? t.gettext('Public') : t.gettext('Private') }}\n </span>\n</ng-template>\n<ng-template #expansionTemplate let-course>\n <taon-cms-course-contents\n [course]=\"course\"\n [actionsColumn]=\"actionsColumn\"\n (editPostRequested)=\"openDetails($event)\">\n </taon-cms-course-contents>\n</ng-template>\n<ng-template\n #filtersTemplate\n let-filters=\"filters\">\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('ID') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"filters.id\"\n (keyup.enter)=\"datatable.applyAdvancedFilters()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Title') }}</mat-label>\n <input\n matInput\n [(ngModel)]=\"filters.title\"\n (keyup.enter)=\"datatable.applyAdvancedFilters()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Visibility') }}</mat-label>\n <mat-select [(ngModel)]=\"filters.status\">\n <mat-option value=\"published\">{{ t.gettext('Public') }}</mat-option>\n <mat-option value=\"draft\">{{ t.gettext('Private') }}</mat-option>\n </mat-select>\n </mat-form-field>\n</ng-template>\n", styles: [":host{display:block}.visibility{display:inline-flex;align-items:center;gap:.4rem}:host ::ng-deep td.cms-course-public{background-color:#2e7d3214}:host ::ng-deep td.cms-course-private{background-color:#c6282812}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: TaonDatatableComponent, selector: "taon-datatable", inputs: ["pageNumber", "pageSize", "pageSizeOptions", "allowedColumns", "expansionTemplate", "entityCrudController", "columns", "hideSearch", "hideAddButton", "safe", "searchMode", "filtersTemplate", "storeTablePagination", "storeTableFilters", "tableId", "callQueryMethod", "data", "showPaginator"], outputs: ["expansionChange", "addingItem"] }, { kind: "component", type: TaonCmsCourseContentsComponent, selector: "taon-cms-course-contents", inputs: ["course", "actionsColumn"], outputs: ["editPostRequested"] }, { kind: "component", type: TaonCmsCourseCoverComponent, selector: "taon-cms-course-cover", inputs: ["post"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7681
8814
  }
7682
8815
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, decorators: [{
7683
8816
  type: Component,
@@ -8331,7 +9464,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8331
9464
  args: ['paste', ['$event']]
8332
9465
  }] } });
8333
9466
 
8334
- const t$a = Translation.for('src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts', { "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-NG3-0ZeO.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-DncFM6D3.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-s2dXTIUw.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DAA5R9fI.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CyKmux96.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DgAHAzJM.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BTv1ecMg.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-CNPdfcRa.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-B5JcoASn.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.html.en-US.i18n.data-BTWsCDp3.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-CZhAYAQh.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.html.de-DE.i18n.data-C5Nxovtl.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.ts.en-US.i18n.data-D1Fh0Nd6.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-C-sVs8_U.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-D10xOAxh.mjs')).default } });
9467
+ const t$a = Translation.for('src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts', { "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-NG3-0ZeO.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-DncFM6D3.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-s2dXTIUw.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DAA5R9fI.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CyKmux96.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DgAHAzJM.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BTv1ecMg.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-CNPdfcRa.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-B5JcoASn.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.html.en-US.i18n.data-YiL6-4Aq.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-BzEKPeB_.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.html.de-DE.i18n.data-BdxSK5vd.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.ts.en-US.i18n.data-B8Vs_fm6.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-BaPFft3W.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-B_w2dmsP.mjs')).default } });
8335
9468
  class TaonCmsPostThumbnailComponent {
8336
9469
  constructor() {
8337
9470
  this.t = t$a.for(this);
@@ -8537,8 +9670,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8537
9670
 
8538
9671
  //#region imports
8539
9672
  //#endregion
8540
- const t$7 = Translation.for('src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts', { "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-NG3-0ZeO.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-DncFM6D3.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-s2dXTIUw.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DAA5R9fI.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CyKmux96.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DgAHAzJM.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BTv1ecMg.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-CNPdfcRa.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-B5JcoASn.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.html.en-US.i18n.data-BTWsCDp3.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-CZhAYAQh.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.html.de-DE.i18n.data-C5Nxovtl.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.ts.en-US.i18n.data-D1Fh0Nd6.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-C-sVs8_U.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-D10xOAxh.mjs')).default } });
9673
+ const t$7 = Translation.for('src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts', { "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-NG3-0ZeO.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-DncFM6D3.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-s2dXTIUw.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DAA5R9fI.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CyKmux96.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DgAHAzJM.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BTv1ecMg.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-CNPdfcRa.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-B5JcoASn.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.html.en-US.i18n.data-YiL6-4Aq.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.html.pl-PL.i18n.data-BzEKPeB_.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.html.de-DE.i18n.data-BdxSK5vd.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-taon-cms-post.component.ts.en-US.i18n.data-B8Vs_fm6.mjs')).default, "pl-PL": async () => (await import('./cms-websql-taon-cms-post.component.ts.pl-PL.i18n.data-BaPFft3W.mjs')).default, "de-DE": async () => (await import('./cms-websql-taon-cms-post.component.ts.de-DE.i18n.data-B_w2dmsP.mjs')).default } });
8541
9674
  class TaonCmsPostComponent {
9675
+ get requiredSlugs() {
9676
+ return this.contentConfig.requiredSlugs;
9677
+ }
8542
9678
  insertTable(event) {
8543
9679
  event.preventDefault();
8544
9680
  const editor = this.contentEditor();
@@ -8559,6 +9695,7 @@ class TaonCmsPostComponent {
8559
9695
  constructor() {
8560
9696
  //#region fields and getters
8561
9697
  this.t = t$7.for(this);
9698
+ this.contentType = TaonCmsContentType;
8562
9699
  this.post = input(null, ...(ngDevMode ? [{ debugName: "post" }] : []));
8563
9700
  this.mode = model('view', ...(ngDevMode ? [{ debugName: "mode" }] : []));
8564
9701
  this.changed = output();
@@ -8585,6 +9722,12 @@ class TaonCmsPostComponent {
8585
9722
  categoryId: null,
8586
9723
  tagIds: [],
8587
9724
  }, ...(ngDevMode ? [{ debugName: "draft" }] : []));
9725
+ this.jsonTableData = signal({
9726
+ version: 1,
9727
+ columns: [],
9728
+ rows: [],
9729
+ }, ...(ngDevMode ? [{ debugName: "jsonTableData" }] : []));
9730
+ this.jsonTableValidationError = signal('', ...(ngDevMode ? [{ debugName: "jsonTableValidationError" }] : []));
8588
9731
  this.postTypes = [
8589
9732
  {
8590
9733
  value: TaonCmsContentType.Normal,
@@ -8596,6 +9739,10 @@ class TaonCmsPostComponent {
8596
9739
  value: TaonCmsContentType.Attachment,
8597
9740
  label: this.t.gettext('Attachment post'),
8598
9741
  },
9742
+ {
9743
+ value: TaonCmsContentType.JsonTable,
9744
+ label: this.t.gettext('JSON Table post'),
9745
+ },
8599
9746
  ];
8600
9747
  this.currentPostType = computed(() => this.postTypes.find(type => type.value === this.draft().type) ??
8601
9748
  this.postTypes[0], ...(ngDevMode ? [{ debugName: "currentPostType" }] : []));
@@ -8607,6 +9754,8 @@ class TaonCmsPostComponent {
8607
9754
  return 'audiotrack';
8608
9755
  case TaonCmsContentType.Attachment:
8609
9756
  return 'attach_file';
9757
+ case TaonCmsContentType.JsonTable:
9758
+ return 'table_chart';
8610
9759
  default:
8611
9760
  return 'article';
8612
9761
  }
@@ -8667,6 +9816,9 @@ class TaonCmsPostComponent {
8667
9816
  this.stagedThumbnail = signal(null, ...(ngDevMode ? [{ debugName: "stagedThumbnail" }] : []));
8668
9817
  this.uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : []));
8669
9818
  this.uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
9819
+ this.mediaUploadAbort = null;
9820
+ this.childUploadAbort = null;
9821
+ this.childUploadRunning = signal(false, ...(ngDevMode ? [{ debugName: "childUploadRunning" }] : []));
8670
9822
  this.mediaPreviewUrl = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewUrl" }] : []));
8671
9823
  this.mediaPreviewLoading = signal(false, ...(ngDevMode ? [{ debugName: "mediaPreviewLoading" }] : []));
8672
9824
  this.mediaPreviewError = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewError" }] : []));
@@ -8779,7 +9931,9 @@ class TaonCmsPostComponent {
8779
9931
  !this.permissionsLoading() &&
8780
9932
  !this.permissionsError() &&
8781
9933
  !this.taxonomyLoading() &&
8782
- !this.taxonomyError(), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
9934
+ !this.taxonomyError() &&
9935
+ (this.draft().type !== TaonCmsContentType.JsonTable ||
9936
+ !this.jsonTableValidationError()), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
8783
9937
  this.statusLabel = computed(() => {
8784
9938
  const status = this.mode() === 'add'
8785
9939
  ? this.draftStatus()
@@ -8801,6 +9955,10 @@ class TaonCmsPostComponent {
8801
9955
  (this.mode() === 'edit' && !this.canSave()), ...(ngDevMode ? [{ debugName: "statusControlDisabled" }] : []));
8802
9956
  this.api = inject(TaonCmsContentApiService);
8803
9957
  this.contentConfig = inject(TaonCmsContentConfigService);
9958
+ this.selectedRequiredSlug = computed(() => {
9959
+ const slug = this.draft().slug;
9960
+ return this.requiredSlugs.includes(slug) ? slug : null;
9961
+ }, ...(ngDevMode ? [{ debugName: "selectedRequiredSlug" }] : []));
8804
9962
  this.contentTab = signal(0, ...(ngDevMode ? [{ debugName: "contentTab" }] : []));
8805
9963
  this.contentEditor = signal(null, ...(ngDevMode ? [{ debugName: "contentEditor" }] : []));
8806
9964
  this.rawContent = viewChild('rawContent', ...(ngDevMode ? [{ debugName: "rawContent" }] : []));
@@ -9011,7 +10169,10 @@ class TaonCmsPostComponent {
9011
10169
  });
9012
10170
  }
9013
10171
  //#endregion
9014
- ngOnDestroy() { }
10172
+ ngOnDestroy() {
10173
+ this.mediaUploadAbort?.abort();
10174
+ this.childUploadAbort?.abort();
10175
+ }
9015
10176
  //#region methods / load related posts
9016
10177
  loadTaxonomy(preserveSelectedTags = false) {
9017
10178
  this.taxonomyLoad?.unsubscribe();
@@ -9131,6 +10292,10 @@ class TaonCmsPostComponent {
9131
10292
  this.error.set(this.t.gettext('Please select an image for the thumbnail.'));
9132
10293
  return;
9133
10294
  }
10295
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
10296
+ this.error.set(this.fileTooLargeMessage(file));
10297
+ return;
10298
+ }
9134
10299
  this.busy.set(true);
9135
10300
  this.error.set('');
9136
10301
  try {
@@ -9714,6 +10879,33 @@ class TaonCmsPostComponent {
9714
10879
  //#endregion
9715
10880
  //#region methods / update draft
9716
10881
  updateDraft(field, value) {
10882
+ if (field === 'type' &&
10883
+ value === TaonCmsContentType.JsonTable &&
10884
+ this.draft().type !== TaonCmsContentType.JsonTable) {
10885
+ let tableData = {
10886
+ version: 1,
10887
+ columns: [],
10888
+ rows: [],
10889
+ };
10890
+ if (this.draft().body.trim()) {
10891
+ try {
10892
+ tableData = parseSpreadsheetDocument(this.draft().body);
10893
+ }
10894
+ catch {
10895
+ if (!globalThis.confirm('Changing the post to a JSON Table will replace its current body. Continue?')) {
10896
+ return;
10897
+ }
10898
+ }
10899
+ }
10900
+ this.jsonTableData.set(tableData);
10901
+ this.jsonTableValidationError.set('');
10902
+ this.draft.update(draft => ({
10903
+ ...draft,
10904
+ type: value,
10905
+ body: JSON.stringify(tableData),
10906
+ }));
10907
+ return;
10908
+ }
9717
10909
  if (field === 'title' && this.mode() === 'add') {
9718
10910
  const title = String(value);
9719
10911
  this.draft.update(draft => ({
@@ -9747,10 +10939,13 @@ class TaonCmsPostComponent {
9747
10939
  if (!this.currentPost() || this.busy() || !files.length) {
9748
10940
  return;
9749
10941
  }
9750
- this.childBatchError.set('');
10942
+ const oversized = files.filter(file => file.size > TAON_CMS_MAX_UPLOAD_BYTES);
10943
+ this.childBatchError.set(oversized.map(file => this.fileTooLargeMessage(file)).join(' '));
9751
10944
  this.childFiles.update(selected => [
9752
10945
  ...selected,
9753
- ...files.map(file => ({
10946
+ ...files
10947
+ .filter(file => file.size <= TAON_CMS_MAX_UPLOAD_BYTES)
10948
+ .map(file => ({
9754
10949
  id: ++this.childFileSequence,
9755
10950
  file,
9756
10951
  status: 'pending',
@@ -9781,10 +10976,16 @@ class TaonCmsPostComponent {
9781
10976
  const pendingFiles = this.childFiles()
9782
10977
  .map((file, index) => ({ file, index }))
9783
10978
  .filter(({ file }) => file.status !== 'success');
10979
+ const abort = new AbortController();
10980
+ this.childUploadAbort = abort;
10981
+ this.childUploadRunning.set(true);
9784
10982
  this.busy.set(true);
9785
10983
  this.childBatchError.set('');
9786
10984
  try {
9787
10985
  for (const { file: selectedFile, index } of pendingFiles) {
10986
+ if (abort.signal.aborted) {
10987
+ break;
10988
+ }
9788
10989
  const expectedTitle = this.childTitlePreviews()[index];
9789
10990
  this.updateRelatedChildFile(selectedFile.id, {
9790
10991
  status: 'processing',
@@ -9800,22 +11001,24 @@ class TaonCmsPostComponent {
9800
11001
  }
9801
11002
  let uploads = this.childFiles().find(file => file.id === selectedFile.id)?.uploads;
9802
11003
  if (!uploads?.source) {
11004
+ abort.signal.throwIfAborted();
9803
11005
  const source = await this.api.uploadMedia(selectedFile.file, percentage => this.updateRelatedChildFile(selectedFile.id, {
9804
11006
  progress: type === TaonCmsContentType.Video
9805
11007
  ? Math.round(percentage * 0.65)
9806
11008
  : Math.round(percentage * 0.8),
9807
- }));
11009
+ }), abort.signal);
9808
11010
  uploads = { ...uploads, source };
9809
11011
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9810
11012
  }
9811
11013
  if (type === TaonCmsContentType.Video && !uploads.thumbnail) {
9812
11014
  const thumbnailBlob = await generateVideoThumbnail(selectedFile.file);
9813
11015
  const extension = thumbnailBlob.type === 'image/webp' ? 'webp' : 'png';
11016
+ abort.signal.throwIfAborted();
9814
11017
  const thumbnail = await this.api.uploadMedia(new File([thumbnailBlob], `thumbnail.${extension}`, {
9815
11018
  type: thumbnailBlob.type,
9816
11019
  }), percentage => this.updateRelatedChildFile(selectedFile.id, {
9817
11020
  progress: 65 + Math.round(percentage * 0.25),
9818
- }));
11021
+ }), abort.signal);
9819
11022
  uploads = { ...uploads, thumbnail };
9820
11023
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9821
11024
  }
@@ -9832,6 +11035,8 @@ class TaonCmsPostComponent {
9832
11035
  if (!sourceUpload) {
9833
11036
  throw new Error('The uploaded source file is unavailable.');
9834
11037
  }
11038
+ // Cancellation is honoured only before the child post is created.
11039
+ abort.signal.throwIfAborted();
9835
11040
  const result = await this.api.createRelatedChild(parent.id, {
9836
11041
  titlePrefix,
9837
11042
  expectedTitle,
@@ -9885,7 +11090,10 @@ class TaonCmsPostComponent {
9885
11090
  ? await this.cleanupRelatedChildUploads(selectedFile.id, uploads)
9886
11091
  : []
9887
11092
  : [];
9888
- const reason = error instanceof Error ? error.message : String(error);
11093
+ const reason = abort.signal.aborted
11094
+ ? this.t.gettext('Upload cancelled. No child post was created.')
11095
+ : error?.body?.json?.message ||
11096
+ (error instanceof Error ? error.message : String(error));
9889
11097
  const verificationError = relatedPosts
9890
11098
  ? ''
9891
11099
  : `${this.t.gettext('The creation result could not be verified; retrying will not duplicate an existing child.')} `;
@@ -9899,6 +11107,10 @@ class TaonCmsPostComponent {
9899
11107
  }
9900
11108
  }
9901
11109
  finally {
11110
+ if (this.childUploadAbort === abort) {
11111
+ this.childUploadAbort = null;
11112
+ }
11113
+ this.childUploadRunning.set(false);
9902
11114
  this.busy.set(false);
9903
11115
  this.loadRelatedPosts();
9904
11116
  }
@@ -10036,7 +11248,14 @@ class TaonCmsPostComponent {
10036
11248
  input.value = '';
10037
11249
  return;
10038
11250
  }
11251
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
11252
+ input.value = '';
11253
+ this.error.set(this.fileTooLargeMessage(file));
11254
+ return;
11255
+ }
10039
11256
  const session = this.editorSession;
11257
+ const abort = new AbortController();
11258
+ this.mediaUploadAbort = abort;
10040
11259
  this.busy.set(true);
10041
11260
  this.uploading.set(true);
10042
11261
  this.uploadProgress.set(0);
@@ -10046,7 +11265,7 @@ class TaonCmsPostComponent {
10046
11265
  if (!this.destroyRef.destroyed) {
10047
11266
  this.uploadProgress.set(percentage);
10048
11267
  }
10049
- });
11268
+ }, abort.signal);
10050
11269
  if (!this.destroyRef.destroyed && this.editorSession === session) {
10051
11270
  const fileName = object.fileName || file.name;
10052
11271
  this.mediaFileNames.update(names => ({
@@ -10075,10 +11294,20 @@ class TaonCmsPostComponent {
10075
11294
  }
10076
11295
  }
10077
11296
  catch (error) {
10078
- console.error('[taon-cms-post] Unable to upload media', error);
10079
- this.error.set(this.t.gettext('The media could not be uploaded. Please try again.'));
11297
+ if (abort.signal.aborted) {
11298
+ if (!this.destroyRef.destroyed) {
11299
+ this.error.set(this.t.gettext('The media upload was cancelled.'));
11300
+ }
11301
+ }
11302
+ else {
11303
+ console.error('[taon-cms-post] Unable to upload media', error);
11304
+ this.error.set(`${this.t.gettext('The media could not be uploaded. Please try again.')}${this.uploadErrorDetails(error)}`);
11305
+ }
10080
11306
  }
10081
11307
  finally {
11308
+ if (this.mediaUploadAbort === abort) {
11309
+ this.mediaUploadAbort = null;
11310
+ }
10082
11311
  this.busy.set(false);
10083
11312
  this.uploading.set(false);
10084
11313
  this.uploadProgress.set(0);
@@ -10086,6 +11315,26 @@ class TaonCmsPostComponent {
10086
11315
  }
10087
11316
  }
10088
11317
  //#endregion
11318
+ //#region methods / cancel media upload
11319
+ cancelMediaUpload() {
11320
+ this.mediaUploadAbort?.abort();
11321
+ }
11322
+ //#endregion
11323
+ //#region methods / cancel related child uploads
11324
+ cancelRelatedChildUploads() {
11325
+ this.childUploadAbort?.abort();
11326
+ }
11327
+ //#endregion
11328
+ //#region methods / upload error helpers
11329
+ fileTooLargeMessage(file) {
11330
+ return this.t.gettext('The file "[[ fileName ]]" is too large. The maximum upload size is 1 GB.', { fileName: file.name });
11331
+ }
11332
+ uploadErrorDetails(error) {
11333
+ const message = error?.body?.json?.message ||
11334
+ (error instanceof Error ? error.message : '');
11335
+ return message ? ` (${message})` : '';
11336
+ }
11337
+ //#endregion
10089
11338
  //#region methods / remove media
10090
11339
  removeMedia() {
10091
11340
  const media = this.mediaAttachment();
@@ -10159,6 +11408,11 @@ class TaonCmsPostComponent {
10159
11408
  this.updateDraft('slug', event.target.value);
10160
11409
  }
10161
11410
  }
11411
+ selectRequiredSlug(slug) {
11412
+ if (slug && this.requiredSlugs.includes(slug)) {
11413
+ this.updateDraft('slug', slug);
11414
+ }
11415
+ }
10162
11416
  //#endregion
10163
11417
  //#region methods / edit
10164
11418
  edit() {
@@ -10549,6 +11803,13 @@ class TaonCmsPostComponent {
10549
11803
  categoryId: post?.categoryId ?? null,
10550
11804
  tagIds: [],
10551
11805
  });
11806
+ if ((post?.type ?? TaonCmsContentType.Normal) === TaonCmsContentType.JsonTable) {
11807
+ this.loadJsonTableData(post?.body ?? null);
11808
+ }
11809
+ else {
11810
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11811
+ this.jsonTableValidationError.set('');
11812
+ }
10552
11813
  if (post &&
10553
11814
  post.relatedPosts === undefined &&
10554
11815
  this.mode() !== 'view-clean') {
@@ -10562,6 +11823,34 @@ class TaonCmsPostComponent {
10562
11823
  }
10563
11824
  }
10564
11825
  //#endregion
11826
+ setJsonTableData(value) {
11827
+ try {
11828
+ const data = validateSpreadsheetData(value);
11829
+ this.jsonTableData.set(data);
11830
+ this.draft.update(draft => ({
11831
+ ...draft,
11832
+ body: JSON.stringify(data),
11833
+ }));
11834
+ }
11835
+ catch (error) {
11836
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11837
+ }
11838
+ }
11839
+ loadJsonTableData(body) {
11840
+ try {
11841
+ const data = parseSpreadsheetDocument(body);
11842
+ this.jsonTableData.set(data);
11843
+ this.draft.update(draft => ({
11844
+ ...draft,
11845
+ body: JSON.stringify(data),
11846
+ }));
11847
+ this.jsonTableValidationError.set('');
11848
+ }
11849
+ catch (error) {
11850
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11851
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11852
+ }
11853
+ }
10565
11854
  //#region methods / set application hook selection
10566
11855
  setApplicationHookSelection(post, hooks) {
10567
11856
  const assignedHook = post?.applicationHook !== undefined
@@ -10609,7 +11898,7 @@ class TaonCmsPostComponent {
10609
11898
  TaonPermissionApiService,
10610
11899
  TaonCmsCategoryApiService,
10611
11900
  TaonCmsTagApiService,
10612
- ], viewQueries: [{ propertyName: "rawContent", first: true, predicate: ["rawContent"], descendants: true, isSignal: true }, { propertyName: "actionButtonsTemplateRef", first: true, predicate: ["actionButtonsTemplate"], descendants: true }], ngImport: i0, template: "@if (mode() !== 'view-clean') {\n <ng-template #actionButtonsTemplate>\n <div class=\"buttons\">\n @if (editing()) {\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !canSave()\"\n (click)=\"save()\">\n {{ mode() === 'add' ? t.gettext('Add') : t.gettext('Save') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"cancel()\">\n {{ t.gettext('Cancel') }}\n </button>\n } @else if (currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"edit()\">\n <mat-icon>edit</mat-icon> {{ t.gettext('Edit') }}\n </button>\n }\n @if (mode() !== 'add' && currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"revert()\">\n <mat-icon>history</mat-icon> {{ t.gettext('Revert') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"delete()\">\n <mat-icon>delete</mat-icon> {{ t.gettext('Delete') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"duplicate()\">\n <mat-icon>content_copy</mat-icon> {{ t.gettext('Duplicate') }}\n </button>\n }\n </div>\n </ng-template>\n <header class=\"post-actions\">\n @if (visibilitySource(); as source) {\n <span\n class=\"post-status\"\n tabindex=\"0\"\n [matTooltip]=\"visibilityInheritedTooltip()\"\n [attr.aria-label]=\"visibilityInheritedTooltip()\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"true\">\n {{ statusLabel() }}\n </button>\n </span>\n } @else {\n <button\n class=\"post-status\"\n mat-button\n type=\"button\"\n [disabled]=\"statusControlDisabled()\"\n (click)=\"selectStatus()\">\n {{ statusLabel() }}\n </button>\n }\n <ng-container [ngTemplateOutlet]=\"actionButtonsTemplate\" />\n @if (busy()) {\n <span role=\"status\">{{\n uploading()\n ? t.gettext('Uploading... ') + uploadProgress() + '%'\n : t.gettext('Saving...')\n }}</span>\n }\n </header>\n @if (error()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ error() }}\n </p>\n }\n}\n\n<article\n class=\"post-article\"\n [attr.aria-busy]=\"busy()\">\n @if (editing()) {\n <mat-tab-group animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('CONTENT')\">\n <mat-accordion class=\"post-type-accordion\">\n <mat-expansion-panel #postTypePanel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>{{ currentPostTypeIcon() }}</mat-icon>\n {{\n t.gettext('Current post type: [[ postType ]]', {\n postType: currentPostType().label,\n })\n }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <mat-radio-group\n class=\"post-type-selector\"\n [attr.aria-label]=\"t.gettext('Post type')\"\n [value]=\"draft().type\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"updateDraft('type', $event.value)\">\n @for (type of postTypes; track type.value) {\n <mat-radio-button\n [value]=\"type.value\"\n (click)=\"postTypePanel.close()\"\n >{{ type.label }}</mat-radio-button\n >\n }\n </mat-radio-group>\n </mat-expansion-panel>\n </mat-accordion>\n <div\n class=\"post-title editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post title')\"\n aria-required=\"true\"\n [attr.data-placeholder]=\"placeholderTitle()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditableSingleLine]=\"true\"\n [taonCmsEditable]=\"draft().title\"\n (taonCmsEditableChange)=\"updateDraft('title', $event)\"></div>\n @if (subtitleEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('excerpt')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <div\n class=\"post-excerpt editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post excerpt')\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"placeholderShortIntro()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditable]=\"draft().excerpt\"\n (taonCmsEditableChange)=\"updateDraft('excerpt', $event)\"></div>\n @if (descriptionEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('body')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <section class=\"post-content-editor\">\n <mat-tab-group\n [selectedIndex]=\"contentTab()\"\n (selectedIndexChange)=\"setContentTab($event)\"\n [preserveContent]=\"true\"\n animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('Editor')\">\n @if (contentEditor(); as editor) {\n <ng-template #customMenu>\n <button\n type=\"button\"\n class=\"NgxEditor__MenuItem\"\n title=\"Table\"\n aria-label=\"Table actions\"\n [disabled]=\"busy() || dialogOpen()\"\n [matMenuTriggerFor]=\"tableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\"\n (mousedown)=\"$event.preventDefault()\">\n <mat-icon>table_chart</mat-icon>\n </button>\n\n <mat-menu #tableMenu=\"matMenu\">\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"insertTable($event)\">\n <mat-icon>add</mat-icon>\n <span>Insert table</span>\n </button>\n\n <mat-divider />\n\n @for (item of tableCommands; track item.label) {\n <button\n mat-menu-item\n type=\"button\"\n [disabled]=\"!canRunTableCommand(item.command)\"\n (click)=\"runTableCommand($event, item.command)\">\n {{ item.label }}\n </button>\n }\n </mat-menu>\n </ng-template>\n\n <ngx-editor-menu\n [editor]=\"editor\"\n [customMenuRef]=\"customMenu\"\n [toolbar]=\"contentToolbar\"\n [disabled]=\"busy() || dialogOpen()\" />\n <ngx-editor\n [editor]=\"editor\"\n [placeholder]=\"placeholderTellYourStory()\" />\n }\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Raw')\">\n <textarea\n #rawContent\n class=\"raw-content\"\n [attr.aria-label]=\"t.gettext('Post body HTML')\"\n [value]=\"draft().body\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateRawContent($event)\"\n spellcheck=\"false\"></textarea>\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Preview')\">\n <div\n class=\"post-body content-preview\"\n [innerHTML]=\"resolvedBody()\"></div>\n </mat-tab>\n </mat-tab-group>\n @if (contentTab() !== 2) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !contentEditor()\"\n [matMenuTriggerFor]=\"variableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\">\n {{ t.gettext('Insert variable') }}\n </button>\n }\n <mat-menu #variableMenu=\"matMenu\">\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"siteVariableMenu\">\n {{ t.gettext('Site') }}\n </button>\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"postVariableMenu\">\n {{ t.gettext('Post') }}\n </button>\n </mat-menu>\n <mat-menu #siteVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('frontendUrl')\">\n {{ t.gettext('Frontend URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('site.name')\">\n {{ t.gettext('Site name') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('currentYear')\">\n {{ t.gettext('Current year') }}\n </button>\n </mat-menu>\n <mat-menu #postVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('post.title')\">\n {{ t.gettext('Post title') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.url')\">\n {{ t.gettext('Post URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.id')\">\n {{ t.gettext('Post ID') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.thumbnailUrl')\">\n {{ t.gettext('Post Thumbnail URL') }}\n </button>\n </mat-menu>\n </section>\n <label class=\"slug-field\">\n <span>{{ t.gettext('URL slug') }}</span>\n <input\n [attr.aria-label]=\"t.gettext('URL slug')\"\n [value]=\"draft().slug\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateSlug($event)\"\n [placeholder]=\"t.gettext('my-post')\" />\n </label>\n </mat-tab>\n <mat-tab [label]=\"t.gettext('SETTINGS')\">\n <div class=\"post-settings\">\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-child-files\"\n aria-labelledby=\"related-child-files-heading\">\n <h2 id=\"related-child-files-heading\">\n {{ t.gettext('Create related children from files') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Please save the post before creating children.'\n )\n }}\n </p>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Child title prefix') }}</mat-label>\n <input\n matInput\n [value]=\"childTitlePrefix()\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (input)=\"updateChildTitlePrefix($event)\" />\n </mat-form-field>\n <input\n #relatedChildFilesInput\n hidden\n type=\"file\"\n multiple\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (change)=\"relatedChildFilesSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"relatedChildFilesInput.click()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Choose files') }}\n </button>\n @if (childBatchError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ childBatchError() }}\n </p>\n }\n @if (childFiles().length) {\n <div class=\"related-child-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"childOverallProgress()\"\n [attr.aria-label]=\"\n t.gettext('Overall child creation progress')\n \" />\n <span>\n {{ childOverallProgress() }}%\n {{ t.gettext('overall progress') }}\n </span>\n </div>\n <ul>\n @for (file of childFiles(); track file.id; let index =\n $index) {\n <li>\n <div class=\"related-child-file\">\n <div class=\"related-child-file-heading\">\n <span>{{ file.file.name }}</span>\n <span>{{ childTitlePreviews()[index] }}</span>\n </div>\n <div class=\"related-child-file-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"file.progress\"\n [attr.aria-label]=\"\n t.gettext('Progress for [[ fileName ]]', {\n fileName: file.file.name,\n })\n \" />\n <span>{{ file.progress }}%</span>\n </div>\n @if (file.status === 'processing') {\n <p role=\"status\">{{ t.gettext('Processing...') }}</p>\n } @else if (file.status === 'success') {\n <p role=\"status\">{{ t.gettext('Child created.') }}</p>\n @if (file.error) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error }}\n </p>\n }\n } @else if (file.status === 'error') {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error }}\n </p>\n } @else {\n <p>{{ t.gettext('Waiting to upload.') }}</p>\n }\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove file [[ fileName ]]', {\n fileName: file.file.name,\n })\n \"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"removeRelatedChildFile(file.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"!canUploadRelatedChildren()\"\n (click)=\"uploadAndCreateChildren()\">\n {{ t.gettext('Upload and Create Children') }}\n </button>\n </section>\n </mat-card>\n @if (mediaAttachment(); as media) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"media-attachment\"\n aria-labelledby=\"media-attachment-heading\">\n <h2 id=\"media-attachment-heading\">{{ media.heading }}</h2>\n @if (uploading()) {\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"uploadProgress()\"\n [attr.aria-label]=\"t.gettext('Media upload progress')\" />\n }\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading media preview...') }}\n </p>\n }\n @if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n }\n @if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support video playback.'\n )\n }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support audio playback.'\n )\n }}\n </audio>\n } @else if (\n media.type === 'attachment' && mediaPreviewUrl()\n ) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ t.gettext('Replace') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeMedia()\">\n {{ t.gettext('Remove') }}\n </button>\n } @else {\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ media.uploadLabel }}\n </button>\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-thumbnail\"\n aria-labelledby=\"thumbnail-heading\">\n <h2 id=\"thumbnail-heading\">{{ t.gettext('Thumbnail') }}</h2>\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n } @else {\n <p>{{ t.gettext('No thumbnail selected.') }}</p>\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <input\n #thumbnailInput\n hidden\n type=\"file\"\n accept=\"image/*\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"thumbnailSelected($event)\" />\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"thumbnailInput.click()\">\n {{ t.gettext('Upload Thumbnail') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"applyDefaultThumbnail()\">\n {{ t.gettext('Apply Default Placeholder') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-taxonomy\"\n aria-labelledby=\"category-heading\">\n <h2 id=\"category-heading\">{{ t.gettext('Category / Tags') }}</h2>\n @if (taxonomyLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading categories and tags...') }}\n </p>\n }\n @if (taxonomyError()) {\n <p role=\"alert\">{{ taxonomyError() }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy()\"\n (click)=\"loadTaxonomy()\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Category') }}</mat-label>\n <mat-select\n [value]=\"draft().categoryId\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"updateDraft('categoryId', $event.value)\">\n <mat-option [value]=\"null\">{{\n t.gettext('None')\n }}</mat-option>\n @for (category of categories(); track category.id) {\n <mat-option [value]=\"category.id\">{{\n category.name\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <h3>{{ t.gettext('Tags') }}</h3>\n <mat-chip-set [attr.aria-label]=\"t.gettext('Selected tags')\">\n @for (tag of selectedTags(); track tag.id) {\n <mat-chip\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (removed)=\"removeTag(tag.id)\">\n {{ tag.name }}\n <button\n matChipRemove\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n [attr.aria-label]=\"\n t.gettext('Remove tag [[ name ]]', { name: tag.name })\n \">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip>\n }\n </mat-chip-set>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Search tags') }}</mat-label>\n <input\n matInput\n [value]=\"tagSearch()\"\n (input)=\"filterTags($event)\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Add tag') }}</mat-label>\n <mat-select\n [value]=\"null\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"addTag($event.value)\">\n @for (tag of filteredTags(); track tag.id) {\n <mat-option [value]=\"tag.id\">{{ tag.name }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('category')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add category') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('tag')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add tag') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"parent-post\"\n aria-labelledby=\"parent-post-heading\">\n <h2 id=\"parent-post-heading\">{{ t.gettext('Parent Post') }}</h2>\n @if (parentPostLoading()) {\n <p role=\"status\">{{ t.gettext('Loading parent post...') }}</p>\n } @else if (parentPostError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ parentPostError() }}\n </p>\n } @else if (parentPost(); as parent) {\n <p>\n <taon-cms-post-thumbnail [post]=\"parent\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, parent)\">\n {{ parent.title }}\n </a>\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeParentPost()\">\n <mat-icon>link_off</mat-icon>\n {{ t.gettext('Remove from parent') }}\n </button>\n } @else {\n <p>{{ t.gettext('No parent added') }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !currentPost()\"\n (click)=\"addAsChildOf()\">\n <mat-icon>account_tree</mat-icon>\n @if (currentPost()) {\n {{ t.gettext('Add as a child') }}\n } @else {\n {{\n t.gettext(\n 'Add as a child (disabled \u2014 please save the post first)'\n )\n }}\n }\n </button>\n }\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-posts\"\n aria-labelledby=\"related-posts-heading\">\n <h2 id=\"related-posts-heading\">\n {{ t.gettext('Related posts') }}\n </h2>\n @if (relatedPostsLoading()) {\n <p role=\"status\">{{ t.gettext('Loading related posts...') }}</p>\n } @else if (relatedPostsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ relatedPostsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadRelatedPosts()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul\n cdkDropList\n [cdkDropListData]=\"relatedPosts()\"\n [cdkDropListDisabled]=\"busy() || dialogOpen()\"\n (cdkDropListDropped)=\"reorderRelatedPosts($event)\">\n @for (post of relatedPosts(); track post.id) {\n <li cdkDrag [cdkDragData]=\"post\">\n <button\n mat-icon-button\n cdkDragHandle\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n [attr.aria-label]=\"\n t.gettext('Reorder related post [[ title ]]', {\n title: post.title,\n })\n \">\n <mat-icon>drag_indicator</mat-icon>\n </button>\n <taon-cms-post-thumbnail [post]=\"post\" />\n <span class=\"related-post-title\">\n #{{ post.id }} -\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, post)\">\n {{ post.title }}\n </a>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove related post [[ title ]]', {\n title: post.title,\n })\n \"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeRelatedPost(post.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No related posts yet.') }}</li>\n }\n </ul>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"addRelatedPost()\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add related post') }}\n </button>\n }\n <mat-checkbox\n [checked]=\"applyVisibilityToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyVisibilityToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply visibility to related posts') }}\n </mat-checkbox>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-version-links\"\n aria-labelledby=\"post-version-links-heading\">\n <h2 id=\"post-version-links-heading\">\n {{ t.gettext('Post versions') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext('Save the post first to manage version links.')\n }}\n </p>\n }\n @if (postVersionLinksLoading()) {\n <p role=\"status\">{{ t.gettext('Loading post versions...') }}</p>\n }\n @if (postVersionLinksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ postVersionLinksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPostVersionLinks(currentPost())\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Public post version') }}</h3>\n @if (postVersionLinks().publicVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPublicVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('public')\">\n {{ t.gettext('Replace public version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('public')\">\n {{ t.gettext('Remove public version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No public post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPublicVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPublicVersion()\n \"\n (click)=\"selectVersionPost('public')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add public post version') }}\n </button>\n }\n </div>\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Private post version') }}</h3>\n @if (postVersionLinks().privateVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPrivateVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('private')\">\n {{ t.gettext('Replace private version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('private')\">\n {{ t.gettext('Remove private version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No private post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPrivateVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPrivateVersion()\n \"\n (click)=\"selectVersionPost('private')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add private post version') }}\n </button>\n }\n </div>\n </section>\n </mat-card>\n\n @if (!mediaAttachment()) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"application-hook\"\n aria-labelledby=\"application-hook-heading\">\n <h2 id=\"application-hook-heading\">\n {{ t.gettext('Hooks') }}\n </h2>\n @if (applicationHooksLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading application hooks...') }}\n </p>\n } @else if (applicationHooksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ applicationHooksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadApplicationHooks()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <mat-checkbox\n [checked]=\"useAsApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"setUseAsApplicationHook($event.checked)\">\n {{ t.gettext('Use as application hook') }}\n </mat-checkbox>\n @if (useAsApplicationHook()) {\n <mat-form-field\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Application Hook') }}</mat-label>\n <mat-select\n [value]=\"selectedApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (selectionChange)=\"\n selectedApplicationHook.set($event.value)\n \">\n @for (\n applicationHook of applicationHooks();\n track applicationHook.id\n ) {\n <mat-option\n [value]=\"applicationHook.hook\"\n [disabled]=\"\n applicationHookInUseByAnotherPost(applicationHook)\n \">\n {{ applicationHookLabel(applicationHook.hook) }}\n @if (\n applicationHookInUseByAnotherPost(applicationHook)\n ) {\n {{\n t.gettext('(in use by post # [[ postId ]])', {\n postId: applicationHook.contentId,\n })\n }}\n }\n </mat-option>\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No application hooks configured')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-permissions\"\n aria-labelledby=\"post-permissions-heading\">\n <h2 id=\"post-permissions-heading\">\n {{ t.gettext('Permissions') }}\n </h2>\n @if (permissionSource(); as source) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Permissions are inherited from # [[ postId ]] - [[ title ]]. Edit the source post to change them.',\n { postId: source.id, title: source.title }\n )\n }}\n </p>\n }\n @if (permissionsLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading assigned permissions...') }}\n </p>\n } @else if (permissionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPermissions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul>\n @for (permission of permissions(); track permission.id) {\n <li>\n <span>{{ permission.name }} ({{ permission.code }})</span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove permission [[ name ]]', {\n name: permission.name,\n })\n \"\n [disabled]=\"\n busy() || dialogOpen() || !!permissionSource()\n \"\n (click)=\"removePermission(permission.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No permissions assigned.') }}</li>\n }\n </ul>\n @if (permissionOptionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionOptionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || permissionOptionsLoading()\n \"\n (click)=\"loadPermissionOptions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <div class=\"permission-assignment\">\n <mat-form-field\n class=\"w-full\"\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Permission') }}</mat-label>\n <mat-select\n [value]=\"selectedPermissionId()\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n permissionOptionsLoading() ||\n !!permissionSource()\n \"\n (selectionChange)=\"\n selectedPermissionId.set($event.value)\n \">\n @for (\n permission of availablePermissions();\n track permission.id\n ) {\n <mat-option [value]=\"permission.id\"\n >{{ permission.name }} ({{\n permission.code\n }})</mat-option\n >\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No permissions available')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n !selectedPermissionId() ||\n !!permissionSource()\n \"\n (click)=\"addPermission()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Assign permission') }}\n </button>\n </div>\n }\n }\n @if (!permissionSource()) {\n <mat-checkbox\n [checked]=\"applyPermissionsToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyPermissionsToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply permissions to related posts') }}\n </mat-checkbox>\n }\n </section>\n </mat-card>\n </div>\n </mat-tab>\n </mat-tab-group>\n } @else if (currentPost(); as post) {\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <h1 class=\"post-title\">{{ post.title }}</h1>\n @if (post.excerpt) {\n <p class=\"post-excerpt\">{{ post.excerpt }}</p>\n }\n <div\n class=\"post-body\"\n [innerHTML]=\"resolvedBody()\"></div>\n @if (mediaAttachment(); as media) {\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">{{ t.gettext('Loading media...') }}</p>\n } @else if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n } @else if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [poster]=\"thumbnailPreviewUrl() || null\"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support video playback.') }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support audio playback.') }}\n </audio>\n } @else if (media.type === 'attachment' && mediaPreviewUrl()) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n }\n }\n @if (mode() === 'view-clean') {\n <taon-cms-comments\n [contentId]=\"post.id\"\n class=\"w-full\" />\n }\n } @else {\n <p role=\"status\">{{ t.gettext('No post is loaded.') }}</p>\n }\n</article>\n", styles: [":host{display:block}:host ::ng-deep .NgxEditor .ProseMirror table{width:100%;border-collapse:collapse;table-layout:fixed;margin:12px 0}:host ::ng-deep .NgxEditor .ProseMirror td,:host ::ng-deep .NgxEditor .ProseMirror th{position:relative;min-width:60px;border:1px solid #aaa;padding:8px;vertical-align:top}:host ::ng-deep .NgxEditor .ProseMirror td>p,:host ::ng-deep .NgxEditor .ProseMirror th>p{margin:0}:host ::ng-deep .NgxEditor .ProseMirror .selectedCell{background:#3f51b51f}mat-expansion-panel{border-radius:0!important}.post-actions{display:flex;align-items:center;flex-wrap:wrap;gap:1rem;padding:1rem;border-bottom:1px solid rgba(136,136,136,.2)}.post-status{margin-right:auto;text-transform:capitalize;opacity:.7}.buttons{display:flex;flex-wrap:wrap;gap:.5rem}.post-article{max-width:760px;margin:0 auto;padding:clamp(1.5rem,5vw,4rem) 1.5rem;overflow-wrap:anywhere}.post-settings{display:grid;gap:1rem;margin-top:1rem}.post-setting-card{display:block;padding:20px}.post-title{margin:0 0 1.5rem;font-size:clamp(2rem,5vw,3.5rem);font-weight:700;line-height:1.15;letter-spacing:-.03em}.post-excerpt{margin:0 0 2rem;font-size:1.35rem;line-height:1.6;opacity:.7;white-space:pre-wrap}.post-body{font-family:Georgia,Times New Roman,serif;font-size:1.2rem;line-height:1.8;white-space:pre-wrap}.editable{min-height:1.2em;outline:none}.editable:focus{box-shadow:-3px 0 #8885}.editable:empty:before{content:attr(data-placeholder);opacity:.4;pointer-events:none}.post-content-editor{border:1px solid rgba(136,136,136,.3333333333);border-radius:4px;overflow:hidden}.raw-content{display:block;box-sizing:border-box;width:100%;min-height:20rem;padding:1rem;resize:vertical;border:0;color:inherit;background:transparent;font-family:monospace}ngx-editor{display:block;min-height:20rem}.content-preview{min-height:12rem;padding:1rem}.slug-field{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;margin-top:3rem;padding-top:1rem;border-top:1px solid rgba(136,136,136,.2);font-size:.875rem}.slug-field input{flex:1;min-width:160px;padding:.5rem;color:inherit;background:transparent;border:1px solid rgba(136,136,136,.3333333333);border-radius:4px}.error{padding:1rem;color:#b3261e}.post-type-selector{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1.5rem}.post-type-accordion{display:block;margin-bottom:1rem}.post-type-accordion mat-panel-title{align-items:center;gap:.5rem}.media-attachment,.post-thumbnail,.post-taxonomy,.parent-post,.related-posts,.post-version-links,.application-hook,.post-permissions{margin-top:1.5rem;font-size:.875rem}.media-attachment h2,.post-thumbnail h2,.post-taxonomy h2,.parent-post h2,.related-posts h2,.post-version-links h2,.application-hook h2,.post-permissions h2{font-size:1rem}.media-attachment .thumbnail-preview,.post-thumbnail .thumbnail-preview,.post-taxonomy .thumbnail-preview,.parent-post .thumbnail-preview,.related-posts .thumbnail-preview,.post-version-links .thumbnail-preview,.application-hook .thumbnail-preview,.post-permissions .thumbnail-preview{display:block;width:min(100%,480px);max-height:320px;object-fit:contain;border-radius:12px;margin-bottom:.75rem}.media-attachment .post-taxonomy mat-form-field,.post-thumbnail .post-taxonomy mat-form-field,.post-taxonomy .post-taxonomy mat-form-field,.parent-post .post-taxonomy mat-form-field,.related-posts .post-taxonomy mat-form-field,.post-version-links .post-taxonomy mat-form-field,.application-hook .post-taxonomy mat-form-field,.post-permissions .post-taxonomy mat-form-field{display:block;margin-top:.75rem}.related-posts ul,.related-child-files ul,.post-permissions ul{padding:0;list-style:none}.related-posts mat-form-field,.related-child-files mat-form-field,.post-permissions mat-form-field{display:block;width:min(100%,28rem);margin-top:.75rem}.related-posts .application-hook,.related-child-files .application-hook,.post-permissions .application-hook{display:grid;align-items:start;gap:.75rem}.related-posts .application-hook mat-form-field,.related-child-files .application-hook mat-form-field,.post-permissions .application-hook mat-form-field{width:min(100%,28rem)}.related-posts li,.related-child-files li,.post-permissions li{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.related-child-files{margin-top:1.5rem;font-size:.875rem}.related-child-files li{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem 0;border-bottom:1px solid rgba(136,136,136,.2)}.related-post-title{flex:1}[cdkDragHandle]{cursor:grab}.related-child-file{flex:1;min-width:0}.related-child-file-heading,.related-child-file-progress,.related-child-progress{display:flex;align-items:center;gap:.75rem}.related-child-file-heading{justify-content:space-between;overflow-wrap:anywhere}.related-child-file-progress{margin-top:.5rem}.related-child-file-progress mat-progress-bar{flex:1}.related-child-progress{margin:.75rem 0}.related-child-progress mat-progress-bar{flex:1}.post-version-link{margin-top:1rem;padding-top:.75rem;border-top:1px solid rgba(136,136,136,.2)}.post-version-link h3{font-size:.95rem}video{max-width:100%;max-height:400px!important}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i4$1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i4$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i8.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i8.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: NgxEditorModule }, { kind: "component", type: i10.NgxEditorComponent, selector: "ngx-editor", inputs: ["editor", "outputFormat", "placeholder"], outputs: ["focusOut", "focusIn"] }, { kind: "component", type: i10.MenuComponent, selector: "ngx-editor-menu", inputs: ["toolbar", "colorPresets", "disabled", "editor", "customMenuRef", "dropdownPlacement"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i11.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "directive", type: i11.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i11.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i12.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$2.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$2.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i15.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: TaonCmsEditableDirective, selector: "[taonCmsEditable]", inputs: ["taonCmsEditableFormat", "taonCmsEditableSingleLine", "taonCmsEditable"], outputs: ["taonCmsEditableChange"] }, { kind: "component", type: TaonCmsCommentsComponent, selector: "taon-cms-comments", inputs: ["contentId"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i16.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i17.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i17.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i17.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: TaonCmsPostThumbnailComponent, selector: "taon-cms-post-thumbnail", inputs: ["post"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11901
+ ], viewQueries: [{ propertyName: "rawContent", first: true, predicate: ["rawContent"], descendants: true, isSignal: true }, { propertyName: "actionButtonsTemplateRef", first: true, predicate: ["actionButtonsTemplate"], descendants: true }], ngImport: i0, template: "@if (mode() !== 'view-clean') {\n <ng-template #actionButtonsTemplate>\n <div class=\"buttons\">\n @if (editing()) {\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !canSave()\"\n (click)=\"save()\">\n {{ mode() === 'add' ? t.gettext('Add') : t.gettext('Save') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"cancel()\">\n {{ t.gettext('Cancel') }}\n </button>\n } @else if (currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"edit()\">\n <mat-icon>edit</mat-icon> {{ t.gettext('Edit') }}\n </button>\n }\n @if (mode() !== 'add' && currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"revert()\">\n <mat-icon>history</mat-icon> {{ t.gettext('Revert') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"delete()\">\n <mat-icon>delete</mat-icon> {{ t.gettext('Delete') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"duplicate()\">\n <mat-icon>content_copy</mat-icon> {{ t.gettext('Duplicate') }}\n </button>\n }\n </div>\n </ng-template>\n <header class=\"post-actions\">\n @if (visibilitySource(); as source) {\n <span\n class=\"post-status\"\n tabindex=\"0\"\n [matTooltip]=\"visibilityInheritedTooltip()\"\n [attr.aria-label]=\"visibilityInheritedTooltip()\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"true\">\n {{ statusLabel() }}\n </button>\n </span>\n } @else {\n <button\n class=\"post-status\"\n mat-button\n type=\"button\"\n [disabled]=\"statusControlDisabled()\"\n (click)=\"selectStatus()\">\n {{ statusLabel() }}\n </button>\n }\n <ng-container [ngTemplateOutlet]=\"actionButtonsTemplate\" />\n @if (busy()) {\n <span role=\"status\">{{\n uploading()\n ? t.gettext('Uploading... ') + uploadProgress() + '%'\n : t.gettext('Saving...')\n }}</span>\n }\n </header>\n @if (error()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ error() }}\n </p>\n }\n}\n\n<article\n class=\"post-article\"\n [attr.aria-busy]=\"busy()\">\n @if (editing()) {\n <mat-tab-group animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('CONTENT')\">\n <mat-accordion class=\"post-type-accordion\">\n <mat-expansion-panel #postTypePanel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>{{ currentPostTypeIcon() }}</mat-icon>\n {{\n t.gettext('Current post type: [[ postType ]]', {\n postType: currentPostType().label,\n })\n }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <mat-radio-group\n class=\"post-type-selector\"\n [attr.aria-label]=\"t.gettext('Post type')\"\n [value]=\"draft().type\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"updateDraft('type', $event.value)\">\n @for (type of postTypes; track type.value) {\n <mat-radio-button\n [value]=\"type.value\"\n (click)=\"postTypePanel.close()\"\n >{{ type.label }}</mat-radio-button\n >\n }\n </mat-radio-group>\n </mat-expansion-panel>\n </mat-accordion>\n <div\n class=\"post-title editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post title')\"\n aria-required=\"true\"\n [attr.data-placeholder]=\"placeholderTitle()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditableSingleLine]=\"true\"\n [taonCmsEditable]=\"draft().title\"\n (taonCmsEditableChange)=\"updateDraft('title', $event)\"></div>\n @if (subtitleEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('excerpt')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <div\n class=\"post-excerpt editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post excerpt')\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"placeholderShortIntro()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditable]=\"draft().excerpt\"\n (taonCmsEditableChange)=\"updateDraft('excerpt', $event)\"></div>\n @if (descriptionEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('body')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <section class=\"post-content-editor\">\n @if (draft().type === contentType.JsonTable) {\n <taon-spreadsheet-table\n [data]=\"jsonTableData()\"\n [disabled]=\"busy() || dialogOpen()\"\n [validationError]=\"jsonTableValidationError()\"\n (dataChange)=\"setJsonTableData($event)\"\n (validationErrorChange)=\"jsonTableValidationError.set($event)\" />\n } @else {\n <mat-tab-group\n [selectedIndex]=\"contentTab()\"\n (selectedIndexChange)=\"setContentTab($event)\"\n [preserveContent]=\"true\"\n animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('Editor')\">\n @if (contentEditor(); as editor) {\n <ng-template #customMenu>\n <button\n type=\"button\"\n class=\"NgxEditor__MenuItem\"\n title=\"Table\"\n aria-label=\"Table actions\"\n [disabled]=\"busy() || dialogOpen()\"\n [matMenuTriggerFor]=\"tableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\"\n (mousedown)=\"$event.preventDefault()\">\n <mat-icon>table_chart</mat-icon>\n </button>\n\n <mat-menu #tableMenu=\"matMenu\">\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"insertTable($event)\">\n <mat-icon>add</mat-icon>\n <span>Insert table</span>\n </button>\n\n <mat-divider />\n\n @for (item of tableCommands; track item.label) {\n <button\n mat-menu-item\n type=\"button\"\n [disabled]=\"!canRunTableCommand(item.command)\"\n (click)=\"runTableCommand($event, item.command)\">\n {{ item.label }}\n </button>\n }\n </mat-menu>\n </ng-template>\n\n <ngx-editor-menu\n [editor]=\"editor\"\n [customMenuRef]=\"customMenu\"\n [toolbar]=\"contentToolbar\"\n [disabled]=\"busy() || dialogOpen()\" />\n <ngx-editor\n [editor]=\"editor\"\n [placeholder]=\"placeholderTellYourStory()\" />\n }\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Raw')\">\n <textarea\n #rawContent\n class=\"raw-content\"\n [attr.aria-label]=\"t.gettext('Post body HTML')\"\n [value]=\"draft().body\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateRawContent($event)\"\n spellcheck=\"false\"></textarea>\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Preview')\">\n <div\n class=\"post-body content-preview\"\n [innerHTML]=\"resolvedBody()\"></div>\n </mat-tab>\n </mat-tab-group>\n @if (contentTab() !== 2) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !contentEditor()\"\n [matMenuTriggerFor]=\"variableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\">\n {{ t.gettext('Insert variable') }}\n </button>\n }\n <mat-menu #variableMenu=\"matMenu\">\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"siteVariableMenu\">\n {{ t.gettext('Site') }}\n </button>\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"postVariableMenu\">\n {{ t.gettext('Post') }}\n </button>\n </mat-menu>\n <mat-menu #siteVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('frontendUrl')\">\n {{ t.gettext('Frontend URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('site.name')\">\n {{ t.gettext('Site name') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('currentYear')\">\n {{ t.gettext('Current year') }}\n </button>\n </mat-menu>\n <mat-menu #postVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('post.title')\">\n {{ t.gettext('Post title') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.url')\">\n {{ t.gettext('Post URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.id')\">\n {{ t.gettext('Post ID') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.thumbnailUrl')\">\n {{ t.gettext('Post Thumbnail URL') }}\n </button>\n </mat-menu>\n }\n </section>\n <label class=\"slug-field\">\n <span>{{ t.gettext('URL slug') }}</span>\n <input\n [attr.aria-label]=\"t.gettext('URL slug')\"\n [value]=\"draft().slug\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateSlug($event)\"\n [placeholder]=\"t.gettext('my-post')\" />\n </label>\n @if (requiredSlugs.length) {\n <mat-form-field\n class=\"required-slugs-field\"\n appearance=\"outline\">\n <mat-label>{{ t.gettext('Required Slugs') }}</mat-label>\n <mat-select\n [value]=\"selectedRequiredSlug()\"\n [disabled]=\"busy() || dialogOpen()\"\n (selectionChange)=\"selectRequiredSlug($event.value)\">\n @for (slug of requiredSlugs; track slug) {\n <mat-option [value]=\"slug\">{{ slug }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </mat-tab>\n <mat-tab [label]=\"t.gettext('SETTINGS')\">\n <div class=\"post-settings\">\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-child-files\"\n aria-labelledby=\"related-child-files-heading\">\n <h2 id=\"related-child-files-heading\">\n {{ t.gettext('Create related children from files') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Please save the post before creating children.'\n )\n }}\n </p>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Child title prefix') }}</mat-label>\n <input\n matInput\n [value]=\"childTitlePrefix()\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (input)=\"updateChildTitlePrefix($event)\" />\n </mat-form-field>\n <input\n #relatedChildFilesInput\n hidden\n type=\"file\"\n multiple\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (change)=\"relatedChildFilesSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"relatedChildFilesInput.click()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Choose files') }}\n </button>\n @if (childBatchError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ childBatchError() }}\n </p>\n }\n @if (childFiles().length) {\n <div class=\"related-child-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"childOverallProgress()\"\n [attr.aria-label]=\"\n t.gettext('Overall child creation progress')\n \" />\n <span>\n {{ childOverallProgress() }}%\n {{ t.gettext('overall progress') }}\n </span>\n </div>\n <ul>\n @for (file of childFiles(); track file.id; let index =\n $index) {\n <li>\n <div class=\"related-child-file\">\n <div class=\"related-child-file-heading\">\n <span>{{ file.file.name }}</span>\n <span>{{ childTitlePreviews()[index] }}</span>\n </div>\n <div class=\"related-child-file-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"file.progress\"\n [attr.aria-label]=\"\n t.gettext('Progress for [[ fileName ]]', {\n fileName: file.file.name,\n })\n \" />\n <span>{{ file.progress }}%</span>\n </div>\n @if (file.status === 'processing') {\n <p role=\"status\">{{ t.gettext('Processing...') }}</p>\n } @else if (file.status === 'success') {\n <p role=\"status\">{{ t.gettext('Child created.') }}</p>\n @if (file.error) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error | json }}\n </p>\n }\n } @else if (file.status === 'error') {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error | json }}\n </p>\n } @else {\n <p>{{ t.gettext('Waiting to upload.') }}</p>\n }\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove file [[ fileName ]]', {\n fileName: file.file.name,\n })\n \"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"removeRelatedChildFile(file.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"!canUploadRelatedChildren()\"\n (click)=\"uploadAndCreateChildren()\">\n {{ t.gettext('Upload and Create Children') }}\n </button>\n @if (childUploadRunning()) {\n <button\n mat-button\n type=\"button\"\n (click)=\"cancelRelatedChildUploads()\">\n {{ t.gettext('Cancel upload') }}\n </button>\n }\n </section>\n </mat-card>\n @if (mediaAttachment(); as media) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"media-attachment\"\n aria-labelledby=\"media-attachment-heading\">\n <h2 id=\"media-attachment-heading\">{{ media.heading }}</h2>\n @if (uploading()) {\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"uploadProgress()\"\n [attr.aria-label]=\"t.gettext('Media upload progress')\" />\n <button\n mat-button\n type=\"button\"\n (click)=\"cancelMediaUpload()\">\n {{ t.gettext('Cancel upload') }}\n </button>\n }\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading media preview...') }}\n </p>\n }\n @if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n }\n @if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support video playback.'\n )\n }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support audio playback.'\n )\n }}\n </audio>\n } @else if (\n media.type === 'attachment' && mediaPreviewUrl()\n ) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ t.gettext('Replace') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeMedia()\">\n {{ t.gettext('Remove') }}\n </button>\n } @else {\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ media.uploadLabel }}\n </button>\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-thumbnail\"\n aria-labelledby=\"thumbnail-heading\">\n <h2 id=\"thumbnail-heading\">{{ t.gettext('Thumbnail') }}</h2>\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n } @else {\n <p>{{ t.gettext('No thumbnail selected.') }}</p>\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <input\n #thumbnailInput\n hidden\n type=\"file\"\n accept=\"image/*\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"thumbnailSelected($event)\" />\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"thumbnailInput.click()\">\n {{ t.gettext('Upload Thumbnail') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"applyDefaultThumbnail()\">\n {{ t.gettext('Apply Default Placeholder') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-taxonomy\"\n aria-labelledby=\"category-heading\">\n <h2 id=\"category-heading\">{{ t.gettext('Category / Tags') }}</h2>\n @if (taxonomyLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading categories and tags...') }}\n </p>\n }\n @if (taxonomyError()) {\n <p role=\"alert\">{{ taxonomyError() }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy()\"\n (click)=\"loadTaxonomy()\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Category') }}</mat-label>\n <mat-select\n [value]=\"draft().categoryId\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"updateDraft('categoryId', $event.value)\">\n <mat-option [value]=\"null\">{{\n t.gettext('None')\n }}</mat-option>\n @for (category of categories(); track category.id) {\n <mat-option [value]=\"category.id\">{{\n category.name\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <h3>{{ t.gettext('Tags') }}</h3>\n <mat-chip-set [attr.aria-label]=\"t.gettext('Selected tags')\">\n @for (tag of selectedTags(); track tag.id) {\n <mat-chip\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (removed)=\"removeTag(tag.id)\">\n {{ tag.name }}\n <button\n matChipRemove\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n [attr.aria-label]=\"\n t.gettext('Remove tag [[ name ]]', { name: tag.name })\n \">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip>\n }\n </mat-chip-set>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Search tags') }}</mat-label>\n <input\n matInput\n [value]=\"tagSearch()\"\n (input)=\"filterTags($event)\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Add tag') }}</mat-label>\n <mat-select\n [value]=\"null\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"addTag($event.value)\">\n @for (tag of filteredTags(); track tag.id) {\n <mat-option [value]=\"tag.id\">{{ tag.name }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('category')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add category') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('tag')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add tag') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"parent-post\"\n aria-labelledby=\"parent-post-heading\">\n <h2 id=\"parent-post-heading\">{{ t.gettext('Parent Post') }}</h2>\n @if (parentPostLoading()) {\n <p role=\"status\">{{ t.gettext('Loading parent post...') }}</p>\n } @else if (parentPostError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ parentPostError() }}\n </p>\n } @else if (parentPost(); as parent) {\n <p>\n <taon-cms-post-thumbnail [post]=\"parent\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, parent)\">\n {{ parent.title }}\n </a>\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeParentPost()\">\n <mat-icon>link_off</mat-icon>\n {{ t.gettext('Remove from parent') }}\n </button>\n } @else {\n <p>{{ t.gettext('No parent added') }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !currentPost()\"\n (click)=\"addAsChildOf()\">\n <mat-icon>account_tree</mat-icon>\n @if (currentPost()) {\n {{ t.gettext('Add as a child') }}\n } @else {\n {{\n t.gettext(\n 'Add as a child (disabled \u2014 please save the post first)'\n )\n }}\n }\n </button>\n }\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-posts\"\n aria-labelledby=\"related-posts-heading\">\n <h2 id=\"related-posts-heading\">\n {{ t.gettext('Related posts') }}\n </h2>\n @if (relatedPostsLoading()) {\n <p role=\"status\">{{ t.gettext('Loading related posts...') }}</p>\n } @else if (relatedPostsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ relatedPostsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadRelatedPosts()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul\n cdkDropList\n [cdkDropListData]=\"relatedPosts()\"\n [cdkDropListDisabled]=\"busy() || dialogOpen()\"\n (cdkDropListDropped)=\"reorderRelatedPosts($event)\">\n @for (post of relatedPosts(); track post.id) {\n <li cdkDrag [cdkDragData]=\"post\">\n <button\n mat-icon-button\n cdkDragHandle\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n [attr.aria-label]=\"\n t.gettext('Reorder related post [[ title ]]', {\n title: post.title,\n })\n \">\n <mat-icon>drag_indicator</mat-icon>\n </button>\n <taon-cms-post-thumbnail [post]=\"post\" />\n <span class=\"related-post-title\">\n #{{ post.id }} -\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, post)\">\n {{ post.title }}\n </a>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove related post [[ title ]]', {\n title: post.title,\n })\n \"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeRelatedPost(post.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No related posts yet.') }}</li>\n }\n </ul>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"addRelatedPost()\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add related post') }}\n </button>\n }\n <mat-checkbox\n [checked]=\"applyVisibilityToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyVisibilityToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply visibility to related posts') }}\n </mat-checkbox>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-version-links\"\n aria-labelledby=\"post-version-links-heading\">\n <h2 id=\"post-version-links-heading\">\n {{ t.gettext('Post versions') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext('Save the post first to manage version links.')\n }}\n </p>\n }\n @if (postVersionLinksLoading()) {\n <p role=\"status\">{{ t.gettext('Loading post versions...') }}</p>\n }\n @if (postVersionLinksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ postVersionLinksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPostVersionLinks(currentPost())\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Public post version') }}</h3>\n @if (postVersionLinks().publicVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPublicVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('public')\">\n {{ t.gettext('Replace public version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('public')\">\n {{ t.gettext('Remove public version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No public post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPublicVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPublicVersion()\n \"\n (click)=\"selectVersionPost('public')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add public post version') }}\n </button>\n }\n </div>\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Private post version') }}</h3>\n @if (postVersionLinks().privateVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPrivateVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('private')\">\n {{ t.gettext('Replace private version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('private')\">\n {{ t.gettext('Remove private version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No private post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPrivateVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPrivateVersion()\n \"\n (click)=\"selectVersionPost('private')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add private post version') }}\n </button>\n }\n </div>\n </section>\n </mat-card>\n\n @if (!mediaAttachment()) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"application-hook\"\n aria-labelledby=\"application-hook-heading\">\n <h2 id=\"application-hook-heading\">\n {{ t.gettext('Hooks') }}\n </h2>\n @if (applicationHooksLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading application hooks...') }}\n </p>\n } @else if (applicationHooksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ applicationHooksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadApplicationHooks()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <mat-checkbox\n [checked]=\"useAsApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"setUseAsApplicationHook($event.checked)\">\n {{ t.gettext('Use as application hook') }}\n </mat-checkbox>\n @if (useAsApplicationHook()) {\n <mat-form-field\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Application Hook') }}</mat-label>\n <mat-select\n [value]=\"selectedApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (selectionChange)=\"\n selectedApplicationHook.set($event.value)\n \">\n @for (\n applicationHook of applicationHooks();\n track applicationHook.id\n ) {\n <mat-option\n [value]=\"applicationHook.hook\"\n [disabled]=\"\n applicationHookInUseByAnotherPost(applicationHook)\n \">\n {{ applicationHookLabel(applicationHook.hook) }}\n @if (\n applicationHookInUseByAnotherPost(applicationHook)\n ) {\n {{\n t.gettext('(in use by post # [[ postId ]])', {\n postId: applicationHook.contentId,\n })\n }}\n }\n </mat-option>\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No application hooks configured')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-permissions\"\n aria-labelledby=\"post-permissions-heading\">\n <h2 id=\"post-permissions-heading\">\n {{ t.gettext('Permissions') }}\n </h2>\n @if (permissionSource(); as source) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Permissions are inherited from # [[ postId ]] - [[ title ]]. Edit the source post to change them.',\n { postId: source.id, title: source.title }\n )\n }}\n </p>\n }\n @if (permissionsLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading assigned permissions...') }}\n </p>\n } @else if (permissionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPermissions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul>\n @for (permission of permissions(); track permission.id) {\n <li>\n <span>{{ permission.name }} ({{ permission.code }})</span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove permission [[ name ]]', {\n name: permission.name,\n })\n \"\n [disabled]=\"\n busy() || dialogOpen() || !!permissionSource()\n \"\n (click)=\"removePermission(permission.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No permissions assigned.') }}</li>\n }\n </ul>\n @if (permissionOptionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionOptionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || permissionOptionsLoading()\n \"\n (click)=\"loadPermissionOptions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <div class=\"permission-assignment\">\n <mat-form-field\n class=\"w-full\"\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Permission') }}</mat-label>\n <mat-select\n [value]=\"selectedPermissionId()\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n permissionOptionsLoading() ||\n !!permissionSource()\n \"\n (selectionChange)=\"\n selectedPermissionId.set($event.value)\n \">\n @for (\n permission of availablePermissions();\n track permission.id\n ) {\n <mat-option [value]=\"permission.id\"\n >{{ permission.name }} ({{\n permission.code\n }})</mat-option\n >\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No permissions available')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n !selectedPermissionId() ||\n !!permissionSource()\n \"\n (click)=\"addPermission()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Assign permission') }}\n </button>\n </div>\n }\n }\n @if (!permissionSource()) {\n <mat-checkbox\n [checked]=\"applyPermissionsToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyPermissionsToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply permissions to related posts') }}\n </mat-checkbox>\n }\n </section>\n </mat-card>\n </div>\n </mat-tab>\n </mat-tab-group>\n } @else if (currentPost(); as post) {\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <h1 class=\"post-title\">{{ post.title }}</h1>\n @if (post.excerpt) {\n <p class=\"post-excerpt\">{{ post.excerpt }}</p>\n }\n @if (post.type === contentType.JsonTable) {\n <taon-spreadsheet-table\n [data]=\"jsonTableData()\"\n [validationError]=\"jsonTableValidationError()\"\n [readOnly]=\"true\" />\n } @else {\n <div\n class=\"post-body\"\n [innerHTML]=\"resolvedBody()\"></div>\n }\n @if (mediaAttachment(); as media) {\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">{{ t.gettext('Loading media...') }}</p>\n } @else if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n } @else if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [poster]=\"thumbnailPreviewUrl() || null\"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support video playback.') }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support audio playback.') }}\n </audio>\n } @else if (media.type === 'attachment' && mediaPreviewUrl()) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n }\n }\n @if (mode() === 'view-clean') {\n <taon-cms-comments\n [contentId]=\"post.id\"\n class=\"w-full\" />\n }\n } @else {\n <p role=\"status\">{{ t.gettext('No post is loaded.') }}</p>\n }\n</article>\n", styles: [":host{display:block}:host ::ng-deep .NgxEditor .ProseMirror table{width:100%;border-collapse:collapse;table-layout:fixed;margin:12px 0}:host ::ng-deep .NgxEditor .ProseMirror td,:host ::ng-deep .NgxEditor .ProseMirror th{position:relative;min-width:60px;border:1px solid #aaa;padding:8px;vertical-align:top}:host ::ng-deep .NgxEditor .ProseMirror td>p,:host ::ng-deep .NgxEditor .ProseMirror th>p{margin:0}:host ::ng-deep .NgxEditor .ProseMirror .selectedCell{background:#3f51b51f}mat-expansion-panel{border-radius:0!important}.post-actions{display:flex;align-items:center;flex-wrap:wrap;gap:1rem;padding:1rem;border-bottom:1px solid rgba(136,136,136,.2)}.post-status{margin-right:auto;text-transform:capitalize;opacity:.7}.buttons{display:flex;flex-wrap:wrap;gap:.5rem}.post-article{max-width:760px;margin:0 auto;padding:clamp(1.5rem,5vw,4rem) 1.5rem;overflow-wrap:anywhere}.post-settings{display:grid;gap:1rem;margin-top:1rem}.post-setting-card{display:block;padding:20px}.post-title{margin:0 0 1.5rem;font-size:clamp(2rem,5vw,3.5rem);font-weight:700;line-height:1.15;letter-spacing:-.03em}.post-excerpt{margin:0 0 2rem;font-size:1.35rem;line-height:1.6;opacity:.7;white-space:pre-wrap}.post-body{font-family:Georgia,Times New Roman,serif;font-size:1.2rem;line-height:1.8;white-space:pre-wrap}.editable{min-height:1.2em;outline:none}.editable:focus{box-shadow:-3px 0 #8885}.editable:empty:before{content:attr(data-placeholder);opacity:.4;pointer-events:none}.post-content-editor{border:1px solid rgba(136,136,136,.3333333333);border-radius:4px;overflow:hidden}.raw-content{display:block;box-sizing:border-box;width:100%;min-height:20rem;padding:1rem;resize:vertical;border:0;color:inherit;background:transparent;font-family:monospace}ngx-editor{display:block;min-height:20rem}.content-preview{min-height:12rem;padding:1rem}.slug-field{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;margin-top:3rem;padding-top:1rem;border-top:1px solid rgba(136,136,136,.2);font-size:.875rem}.slug-field input{flex:1;min-width:160px;padding:.5rem;color:inherit;background:transparent;border:1px solid rgba(136,136,136,.3333333333);border-radius:4px}.error{padding:1rem;color:#b3261e}.post-type-selector{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1.5rem}.post-type-accordion{display:block;margin-bottom:1rem}.post-type-accordion mat-panel-title{align-items:center;gap:.5rem}.media-attachment,.post-thumbnail,.post-taxonomy,.parent-post,.related-posts,.post-version-links,.application-hook,.post-permissions{margin-top:1.5rem;font-size:.875rem}.media-attachment h2,.post-thumbnail h2,.post-taxonomy h2,.parent-post h2,.related-posts h2,.post-version-links h2,.application-hook h2,.post-permissions h2{font-size:1rem}.media-attachment .thumbnail-preview,.post-thumbnail .thumbnail-preview,.post-taxonomy .thumbnail-preview,.parent-post .thumbnail-preview,.related-posts .thumbnail-preview,.post-version-links .thumbnail-preview,.application-hook .thumbnail-preview,.post-permissions .thumbnail-preview{display:block;width:min(100%,480px);max-height:320px;object-fit:contain;border-radius:12px;margin-bottom:.75rem}.media-attachment .post-taxonomy mat-form-field,.post-thumbnail .post-taxonomy mat-form-field,.post-taxonomy .post-taxonomy mat-form-field,.parent-post .post-taxonomy mat-form-field,.related-posts .post-taxonomy mat-form-field,.post-version-links .post-taxonomy mat-form-field,.application-hook .post-taxonomy mat-form-field,.post-permissions .post-taxonomy mat-form-field{display:block;margin-top:.75rem}.related-posts ul,.related-child-files ul,.post-permissions ul{padding:0;list-style:none}.related-posts mat-form-field,.related-child-files mat-form-field,.post-permissions mat-form-field{display:block;width:min(100%,28rem);margin-top:.75rem}.related-posts .application-hook,.related-child-files .application-hook,.post-permissions .application-hook{display:grid;align-items:start;gap:.75rem}.related-posts .application-hook mat-form-field,.related-child-files .application-hook mat-form-field,.post-permissions .application-hook mat-form-field{width:min(100%,28rem)}.related-posts li,.related-child-files li,.post-permissions li{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.related-child-files{margin-top:1.5rem;font-size:.875rem}.related-child-files li{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem 0;border-bottom:1px solid rgba(136,136,136,.2)}.related-post-title{flex:1}[cdkDragHandle]{cursor:grab}.related-child-file{flex:1;min-width:0}.related-child-file-heading,.related-child-file-progress,.related-child-progress{display:flex;align-items:center;gap:.75rem}.related-child-file-heading{justify-content:space-between;overflow-wrap:anywhere}.related-child-file-progress{margin-top:.5rem}.related-child-file-progress mat-progress-bar{flex:1}.related-child-progress{margin:.75rem 0}.related-child-progress mat-progress-bar{flex:1}.post-version-link{margin-top:1rem;padding-top:.75rem;border-top:1px solid rgba(136,136,136,.2)}.post-version-link h3{font-size:.95rem}video{max-width:100%;max-height:400px!important}.required-slugs-field{width:100%;margin-top:1rem}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$3.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i4$1.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i4$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i8.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i8.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: NgxEditorModule }, { kind: "component", type: i10.NgxEditorComponent, selector: "ngx-editor", inputs: ["editor", "outputFormat", "placeholder"], outputs: ["focusOut", "focusIn"] }, { kind: "component", type: i10.MenuComponent, selector: "ngx-editor-menu", inputs: ["toolbar", "colorPresets", "disabled", "editor", "customMenuRef", "dropdownPlacement"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i11.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "directive", type: i11.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i11.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i12.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$2.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$2.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i15.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: TaonCmsEditableDirective, selector: "[taonCmsEditable]", inputs: ["taonCmsEditableFormat", "taonCmsEditableSingleLine", "taonCmsEditable"], outputs: ["taonCmsEditableChange"] }, { kind: "component", type: TaonCmsCommentsComponent, selector: "taon-cms-comments", inputs: ["contentId"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i16.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i17.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i17.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i17.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: TaonCmsPostThumbnailComponent, selector: "taon-cms-post-thumbnail", inputs: ["post"] }, { kind: "component", type: TaonSpreadsheetTableComponent, selector: "taon-spreadsheet-table", inputs: ["data", "readOnly", "disabled", "validationError"], outputs: ["dataChange", "validationErrorChange"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10613
11902
  }
10614
11903
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsPostComponent, decorators: [{
10615
11904
  type: Component,
@@ -10635,13 +11924,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
10635
11924
  MatDividerModule,
10636
11925
  DragDropModule,
10637
11926
  TaonCmsPostThumbnailComponent,
11927
+ TaonSpreadsheetTableComponent,
11928
+ JsonPipe,
10638
11929
  ], providers: [
10639
11930
  TaonCmsContentApiService,
10640
11931
  TaonCmsContentConfigService,
10641
11932
  TaonPermissionApiService,
10642
11933
  TaonCmsCategoryApiService,
10643
11934
  TaonCmsTagApiService,
10644
- ], template: "@if (mode() !== 'view-clean') {\n <ng-template #actionButtonsTemplate>\n <div class=\"buttons\">\n @if (editing()) {\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !canSave()\"\n (click)=\"save()\">\n {{ mode() === 'add' ? t.gettext('Add') : t.gettext('Save') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"cancel()\">\n {{ t.gettext('Cancel') }}\n </button>\n } @else if (currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"edit()\">\n <mat-icon>edit</mat-icon> {{ t.gettext('Edit') }}\n </button>\n }\n @if (mode() !== 'add' && currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"revert()\">\n <mat-icon>history</mat-icon> {{ t.gettext('Revert') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"delete()\">\n <mat-icon>delete</mat-icon> {{ t.gettext('Delete') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"duplicate()\">\n <mat-icon>content_copy</mat-icon> {{ t.gettext('Duplicate') }}\n </button>\n }\n </div>\n </ng-template>\n <header class=\"post-actions\">\n @if (visibilitySource(); as source) {\n <span\n class=\"post-status\"\n tabindex=\"0\"\n [matTooltip]=\"visibilityInheritedTooltip()\"\n [attr.aria-label]=\"visibilityInheritedTooltip()\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"true\">\n {{ statusLabel() }}\n </button>\n </span>\n } @else {\n <button\n class=\"post-status\"\n mat-button\n type=\"button\"\n [disabled]=\"statusControlDisabled()\"\n (click)=\"selectStatus()\">\n {{ statusLabel() }}\n </button>\n }\n <ng-container [ngTemplateOutlet]=\"actionButtonsTemplate\" />\n @if (busy()) {\n <span role=\"status\">{{\n uploading()\n ? t.gettext('Uploading... ') + uploadProgress() + '%'\n : t.gettext('Saving...')\n }}</span>\n }\n </header>\n @if (error()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ error() }}\n </p>\n }\n}\n\n<article\n class=\"post-article\"\n [attr.aria-busy]=\"busy()\">\n @if (editing()) {\n <mat-tab-group animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('CONTENT')\">\n <mat-accordion class=\"post-type-accordion\">\n <mat-expansion-panel #postTypePanel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>{{ currentPostTypeIcon() }}</mat-icon>\n {{\n t.gettext('Current post type: [[ postType ]]', {\n postType: currentPostType().label,\n })\n }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <mat-radio-group\n class=\"post-type-selector\"\n [attr.aria-label]=\"t.gettext('Post type')\"\n [value]=\"draft().type\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"updateDraft('type', $event.value)\">\n @for (type of postTypes; track type.value) {\n <mat-radio-button\n [value]=\"type.value\"\n (click)=\"postTypePanel.close()\"\n >{{ type.label }}</mat-radio-button\n >\n }\n </mat-radio-group>\n </mat-expansion-panel>\n </mat-accordion>\n <div\n class=\"post-title editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post title')\"\n aria-required=\"true\"\n [attr.data-placeholder]=\"placeholderTitle()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditableSingleLine]=\"true\"\n [taonCmsEditable]=\"draft().title\"\n (taonCmsEditableChange)=\"updateDraft('title', $event)\"></div>\n @if (subtitleEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('excerpt')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <div\n class=\"post-excerpt editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post excerpt')\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"placeholderShortIntro()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditable]=\"draft().excerpt\"\n (taonCmsEditableChange)=\"updateDraft('excerpt', $event)\"></div>\n @if (descriptionEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('body')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <section class=\"post-content-editor\">\n <mat-tab-group\n [selectedIndex]=\"contentTab()\"\n (selectedIndexChange)=\"setContentTab($event)\"\n [preserveContent]=\"true\"\n animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('Editor')\">\n @if (contentEditor(); as editor) {\n <ng-template #customMenu>\n <button\n type=\"button\"\n class=\"NgxEditor__MenuItem\"\n title=\"Table\"\n aria-label=\"Table actions\"\n [disabled]=\"busy() || dialogOpen()\"\n [matMenuTriggerFor]=\"tableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\"\n (mousedown)=\"$event.preventDefault()\">\n <mat-icon>table_chart</mat-icon>\n </button>\n\n <mat-menu #tableMenu=\"matMenu\">\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"insertTable($event)\">\n <mat-icon>add</mat-icon>\n <span>Insert table</span>\n </button>\n\n <mat-divider />\n\n @for (item of tableCommands; track item.label) {\n <button\n mat-menu-item\n type=\"button\"\n [disabled]=\"!canRunTableCommand(item.command)\"\n (click)=\"runTableCommand($event, item.command)\">\n {{ item.label }}\n </button>\n }\n </mat-menu>\n </ng-template>\n\n <ngx-editor-menu\n [editor]=\"editor\"\n [customMenuRef]=\"customMenu\"\n [toolbar]=\"contentToolbar\"\n [disabled]=\"busy() || dialogOpen()\" />\n <ngx-editor\n [editor]=\"editor\"\n [placeholder]=\"placeholderTellYourStory()\" />\n }\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Raw')\">\n <textarea\n #rawContent\n class=\"raw-content\"\n [attr.aria-label]=\"t.gettext('Post body HTML')\"\n [value]=\"draft().body\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateRawContent($event)\"\n spellcheck=\"false\"></textarea>\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Preview')\">\n <div\n class=\"post-body content-preview\"\n [innerHTML]=\"resolvedBody()\"></div>\n </mat-tab>\n </mat-tab-group>\n @if (contentTab() !== 2) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !contentEditor()\"\n [matMenuTriggerFor]=\"variableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\">\n {{ t.gettext('Insert variable') }}\n </button>\n }\n <mat-menu #variableMenu=\"matMenu\">\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"siteVariableMenu\">\n {{ t.gettext('Site') }}\n </button>\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"postVariableMenu\">\n {{ t.gettext('Post') }}\n </button>\n </mat-menu>\n <mat-menu #siteVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('frontendUrl')\">\n {{ t.gettext('Frontend URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('site.name')\">\n {{ t.gettext('Site name') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('currentYear')\">\n {{ t.gettext('Current year') }}\n </button>\n </mat-menu>\n <mat-menu #postVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('post.title')\">\n {{ t.gettext('Post title') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.url')\">\n {{ t.gettext('Post URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.id')\">\n {{ t.gettext('Post ID') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.thumbnailUrl')\">\n {{ t.gettext('Post Thumbnail URL') }}\n </button>\n </mat-menu>\n </section>\n <label class=\"slug-field\">\n <span>{{ t.gettext('URL slug') }}</span>\n <input\n [attr.aria-label]=\"t.gettext('URL slug')\"\n [value]=\"draft().slug\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateSlug($event)\"\n [placeholder]=\"t.gettext('my-post')\" />\n </label>\n </mat-tab>\n <mat-tab [label]=\"t.gettext('SETTINGS')\">\n <div class=\"post-settings\">\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-child-files\"\n aria-labelledby=\"related-child-files-heading\">\n <h2 id=\"related-child-files-heading\">\n {{ t.gettext('Create related children from files') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Please save the post before creating children.'\n )\n }}\n </p>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Child title prefix') }}</mat-label>\n <input\n matInput\n [value]=\"childTitlePrefix()\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (input)=\"updateChildTitlePrefix($event)\" />\n </mat-form-field>\n <input\n #relatedChildFilesInput\n hidden\n type=\"file\"\n multiple\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (change)=\"relatedChildFilesSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"relatedChildFilesInput.click()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Choose files') }}\n </button>\n @if (childBatchError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ childBatchError() }}\n </p>\n }\n @if (childFiles().length) {\n <div class=\"related-child-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"childOverallProgress()\"\n [attr.aria-label]=\"\n t.gettext('Overall child creation progress')\n \" />\n <span>\n {{ childOverallProgress() }}%\n {{ t.gettext('overall progress') }}\n </span>\n </div>\n <ul>\n @for (file of childFiles(); track file.id; let index =\n $index) {\n <li>\n <div class=\"related-child-file\">\n <div class=\"related-child-file-heading\">\n <span>{{ file.file.name }}</span>\n <span>{{ childTitlePreviews()[index] }}</span>\n </div>\n <div class=\"related-child-file-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"file.progress\"\n [attr.aria-label]=\"\n t.gettext('Progress for [[ fileName ]]', {\n fileName: file.file.name,\n })\n \" />\n <span>{{ file.progress }}%</span>\n </div>\n @if (file.status === 'processing') {\n <p role=\"status\">{{ t.gettext('Processing...') }}</p>\n } @else if (file.status === 'success') {\n <p role=\"status\">{{ t.gettext('Child created.') }}</p>\n @if (file.error) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error }}\n </p>\n }\n } @else if (file.status === 'error') {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error }}\n </p>\n } @else {\n <p>{{ t.gettext('Waiting to upload.') }}</p>\n }\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove file [[ fileName ]]', {\n fileName: file.file.name,\n })\n \"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"removeRelatedChildFile(file.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"!canUploadRelatedChildren()\"\n (click)=\"uploadAndCreateChildren()\">\n {{ t.gettext('Upload and Create Children') }}\n </button>\n </section>\n </mat-card>\n @if (mediaAttachment(); as media) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"media-attachment\"\n aria-labelledby=\"media-attachment-heading\">\n <h2 id=\"media-attachment-heading\">{{ media.heading }}</h2>\n @if (uploading()) {\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"uploadProgress()\"\n [attr.aria-label]=\"t.gettext('Media upload progress')\" />\n }\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading media preview...') }}\n </p>\n }\n @if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n }\n @if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support video playback.'\n )\n }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support audio playback.'\n )\n }}\n </audio>\n } @else if (\n media.type === 'attachment' && mediaPreviewUrl()\n ) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ t.gettext('Replace') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeMedia()\">\n {{ t.gettext('Remove') }}\n </button>\n } @else {\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ media.uploadLabel }}\n </button>\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-thumbnail\"\n aria-labelledby=\"thumbnail-heading\">\n <h2 id=\"thumbnail-heading\">{{ t.gettext('Thumbnail') }}</h2>\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n } @else {\n <p>{{ t.gettext('No thumbnail selected.') }}</p>\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <input\n #thumbnailInput\n hidden\n type=\"file\"\n accept=\"image/*\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"thumbnailSelected($event)\" />\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"thumbnailInput.click()\">\n {{ t.gettext('Upload Thumbnail') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"applyDefaultThumbnail()\">\n {{ t.gettext('Apply Default Placeholder') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-taxonomy\"\n aria-labelledby=\"category-heading\">\n <h2 id=\"category-heading\">{{ t.gettext('Category / Tags') }}</h2>\n @if (taxonomyLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading categories and tags...') }}\n </p>\n }\n @if (taxonomyError()) {\n <p role=\"alert\">{{ taxonomyError() }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy()\"\n (click)=\"loadTaxonomy()\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Category') }}</mat-label>\n <mat-select\n [value]=\"draft().categoryId\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"updateDraft('categoryId', $event.value)\">\n <mat-option [value]=\"null\">{{\n t.gettext('None')\n }}</mat-option>\n @for (category of categories(); track category.id) {\n <mat-option [value]=\"category.id\">{{\n category.name\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <h3>{{ t.gettext('Tags') }}</h3>\n <mat-chip-set [attr.aria-label]=\"t.gettext('Selected tags')\">\n @for (tag of selectedTags(); track tag.id) {\n <mat-chip\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (removed)=\"removeTag(tag.id)\">\n {{ tag.name }}\n <button\n matChipRemove\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n [attr.aria-label]=\"\n t.gettext('Remove tag [[ name ]]', { name: tag.name })\n \">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip>\n }\n </mat-chip-set>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Search tags') }}</mat-label>\n <input\n matInput\n [value]=\"tagSearch()\"\n (input)=\"filterTags($event)\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Add tag') }}</mat-label>\n <mat-select\n [value]=\"null\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"addTag($event.value)\">\n @for (tag of filteredTags(); track tag.id) {\n <mat-option [value]=\"tag.id\">{{ tag.name }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('category')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add category') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('tag')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add tag') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"parent-post\"\n aria-labelledby=\"parent-post-heading\">\n <h2 id=\"parent-post-heading\">{{ t.gettext('Parent Post') }}</h2>\n @if (parentPostLoading()) {\n <p role=\"status\">{{ t.gettext('Loading parent post...') }}</p>\n } @else if (parentPostError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ parentPostError() }}\n </p>\n } @else if (parentPost(); as parent) {\n <p>\n <taon-cms-post-thumbnail [post]=\"parent\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, parent)\">\n {{ parent.title }}\n </a>\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeParentPost()\">\n <mat-icon>link_off</mat-icon>\n {{ t.gettext('Remove from parent') }}\n </button>\n } @else {\n <p>{{ t.gettext('No parent added') }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !currentPost()\"\n (click)=\"addAsChildOf()\">\n <mat-icon>account_tree</mat-icon>\n @if (currentPost()) {\n {{ t.gettext('Add as a child') }}\n } @else {\n {{\n t.gettext(\n 'Add as a child (disabled \u2014 please save the post first)'\n )\n }}\n }\n </button>\n }\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-posts\"\n aria-labelledby=\"related-posts-heading\">\n <h2 id=\"related-posts-heading\">\n {{ t.gettext('Related posts') }}\n </h2>\n @if (relatedPostsLoading()) {\n <p role=\"status\">{{ t.gettext('Loading related posts...') }}</p>\n } @else if (relatedPostsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ relatedPostsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadRelatedPosts()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul\n cdkDropList\n [cdkDropListData]=\"relatedPosts()\"\n [cdkDropListDisabled]=\"busy() || dialogOpen()\"\n (cdkDropListDropped)=\"reorderRelatedPosts($event)\">\n @for (post of relatedPosts(); track post.id) {\n <li cdkDrag [cdkDragData]=\"post\">\n <button\n mat-icon-button\n cdkDragHandle\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n [attr.aria-label]=\"\n t.gettext('Reorder related post [[ title ]]', {\n title: post.title,\n })\n \">\n <mat-icon>drag_indicator</mat-icon>\n </button>\n <taon-cms-post-thumbnail [post]=\"post\" />\n <span class=\"related-post-title\">\n #{{ post.id }} -\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, post)\">\n {{ post.title }}\n </a>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove related post [[ title ]]', {\n title: post.title,\n })\n \"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeRelatedPost(post.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No related posts yet.') }}</li>\n }\n </ul>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"addRelatedPost()\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add related post') }}\n </button>\n }\n <mat-checkbox\n [checked]=\"applyVisibilityToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyVisibilityToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply visibility to related posts') }}\n </mat-checkbox>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-version-links\"\n aria-labelledby=\"post-version-links-heading\">\n <h2 id=\"post-version-links-heading\">\n {{ t.gettext('Post versions') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext('Save the post first to manage version links.')\n }}\n </p>\n }\n @if (postVersionLinksLoading()) {\n <p role=\"status\">{{ t.gettext('Loading post versions...') }}</p>\n }\n @if (postVersionLinksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ postVersionLinksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPostVersionLinks(currentPost())\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Public post version') }}</h3>\n @if (postVersionLinks().publicVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPublicVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('public')\">\n {{ t.gettext('Replace public version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('public')\">\n {{ t.gettext('Remove public version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No public post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPublicVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPublicVersion()\n \"\n (click)=\"selectVersionPost('public')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add public post version') }}\n </button>\n }\n </div>\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Private post version') }}</h3>\n @if (postVersionLinks().privateVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPrivateVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('private')\">\n {{ t.gettext('Replace private version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('private')\">\n {{ t.gettext('Remove private version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No private post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPrivateVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPrivateVersion()\n \"\n (click)=\"selectVersionPost('private')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add private post version') }}\n </button>\n }\n </div>\n </section>\n </mat-card>\n\n @if (!mediaAttachment()) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"application-hook\"\n aria-labelledby=\"application-hook-heading\">\n <h2 id=\"application-hook-heading\">\n {{ t.gettext('Hooks') }}\n </h2>\n @if (applicationHooksLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading application hooks...') }}\n </p>\n } @else if (applicationHooksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ applicationHooksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadApplicationHooks()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <mat-checkbox\n [checked]=\"useAsApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"setUseAsApplicationHook($event.checked)\">\n {{ t.gettext('Use as application hook') }}\n </mat-checkbox>\n @if (useAsApplicationHook()) {\n <mat-form-field\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Application Hook') }}</mat-label>\n <mat-select\n [value]=\"selectedApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (selectionChange)=\"\n selectedApplicationHook.set($event.value)\n \">\n @for (\n applicationHook of applicationHooks();\n track applicationHook.id\n ) {\n <mat-option\n [value]=\"applicationHook.hook\"\n [disabled]=\"\n applicationHookInUseByAnotherPost(applicationHook)\n \">\n {{ applicationHookLabel(applicationHook.hook) }}\n @if (\n applicationHookInUseByAnotherPost(applicationHook)\n ) {\n {{\n t.gettext('(in use by post # [[ postId ]])', {\n postId: applicationHook.contentId,\n })\n }}\n }\n </mat-option>\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No application hooks configured')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-permissions\"\n aria-labelledby=\"post-permissions-heading\">\n <h2 id=\"post-permissions-heading\">\n {{ t.gettext('Permissions') }}\n </h2>\n @if (permissionSource(); as source) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Permissions are inherited from # [[ postId ]] - [[ title ]]. Edit the source post to change them.',\n { postId: source.id, title: source.title }\n )\n }}\n </p>\n }\n @if (permissionsLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading assigned permissions...') }}\n </p>\n } @else if (permissionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPermissions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul>\n @for (permission of permissions(); track permission.id) {\n <li>\n <span>{{ permission.name }} ({{ permission.code }})</span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove permission [[ name ]]', {\n name: permission.name,\n })\n \"\n [disabled]=\"\n busy() || dialogOpen() || !!permissionSource()\n \"\n (click)=\"removePermission(permission.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No permissions assigned.') }}</li>\n }\n </ul>\n @if (permissionOptionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionOptionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || permissionOptionsLoading()\n \"\n (click)=\"loadPermissionOptions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <div class=\"permission-assignment\">\n <mat-form-field\n class=\"w-full\"\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Permission') }}</mat-label>\n <mat-select\n [value]=\"selectedPermissionId()\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n permissionOptionsLoading() ||\n !!permissionSource()\n \"\n (selectionChange)=\"\n selectedPermissionId.set($event.value)\n \">\n @for (\n permission of availablePermissions();\n track permission.id\n ) {\n <mat-option [value]=\"permission.id\"\n >{{ permission.name }} ({{\n permission.code\n }})</mat-option\n >\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No permissions available')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n !selectedPermissionId() ||\n !!permissionSource()\n \"\n (click)=\"addPermission()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Assign permission') }}\n </button>\n </div>\n }\n }\n @if (!permissionSource()) {\n <mat-checkbox\n [checked]=\"applyPermissionsToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyPermissionsToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply permissions to related posts') }}\n </mat-checkbox>\n }\n </section>\n </mat-card>\n </div>\n </mat-tab>\n </mat-tab-group>\n } @else if (currentPost(); as post) {\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <h1 class=\"post-title\">{{ post.title }}</h1>\n @if (post.excerpt) {\n <p class=\"post-excerpt\">{{ post.excerpt }}</p>\n }\n <div\n class=\"post-body\"\n [innerHTML]=\"resolvedBody()\"></div>\n @if (mediaAttachment(); as media) {\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">{{ t.gettext('Loading media...') }}</p>\n } @else if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n } @else if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [poster]=\"thumbnailPreviewUrl() || null\"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support video playback.') }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support audio playback.') }}\n </audio>\n } @else if (media.type === 'attachment' && mediaPreviewUrl()) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n }\n }\n @if (mode() === 'view-clean') {\n <taon-cms-comments\n [contentId]=\"post.id\"\n class=\"w-full\" />\n }\n } @else {\n <p role=\"status\">{{ t.gettext('No post is loaded.') }}</p>\n }\n</article>\n", styles: [":host{display:block}:host ::ng-deep .NgxEditor .ProseMirror table{width:100%;border-collapse:collapse;table-layout:fixed;margin:12px 0}:host ::ng-deep .NgxEditor .ProseMirror td,:host ::ng-deep .NgxEditor .ProseMirror th{position:relative;min-width:60px;border:1px solid #aaa;padding:8px;vertical-align:top}:host ::ng-deep .NgxEditor .ProseMirror td>p,:host ::ng-deep .NgxEditor .ProseMirror th>p{margin:0}:host ::ng-deep .NgxEditor .ProseMirror .selectedCell{background:#3f51b51f}mat-expansion-panel{border-radius:0!important}.post-actions{display:flex;align-items:center;flex-wrap:wrap;gap:1rem;padding:1rem;border-bottom:1px solid rgba(136,136,136,.2)}.post-status{margin-right:auto;text-transform:capitalize;opacity:.7}.buttons{display:flex;flex-wrap:wrap;gap:.5rem}.post-article{max-width:760px;margin:0 auto;padding:clamp(1.5rem,5vw,4rem) 1.5rem;overflow-wrap:anywhere}.post-settings{display:grid;gap:1rem;margin-top:1rem}.post-setting-card{display:block;padding:20px}.post-title{margin:0 0 1.5rem;font-size:clamp(2rem,5vw,3.5rem);font-weight:700;line-height:1.15;letter-spacing:-.03em}.post-excerpt{margin:0 0 2rem;font-size:1.35rem;line-height:1.6;opacity:.7;white-space:pre-wrap}.post-body{font-family:Georgia,Times New Roman,serif;font-size:1.2rem;line-height:1.8;white-space:pre-wrap}.editable{min-height:1.2em;outline:none}.editable:focus{box-shadow:-3px 0 #8885}.editable:empty:before{content:attr(data-placeholder);opacity:.4;pointer-events:none}.post-content-editor{border:1px solid rgba(136,136,136,.3333333333);border-radius:4px;overflow:hidden}.raw-content{display:block;box-sizing:border-box;width:100%;min-height:20rem;padding:1rem;resize:vertical;border:0;color:inherit;background:transparent;font-family:monospace}ngx-editor{display:block;min-height:20rem}.content-preview{min-height:12rem;padding:1rem}.slug-field{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;margin-top:3rem;padding-top:1rem;border-top:1px solid rgba(136,136,136,.2);font-size:.875rem}.slug-field input{flex:1;min-width:160px;padding:.5rem;color:inherit;background:transparent;border:1px solid rgba(136,136,136,.3333333333);border-radius:4px}.error{padding:1rem;color:#b3261e}.post-type-selector{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1.5rem}.post-type-accordion{display:block;margin-bottom:1rem}.post-type-accordion mat-panel-title{align-items:center;gap:.5rem}.media-attachment,.post-thumbnail,.post-taxonomy,.parent-post,.related-posts,.post-version-links,.application-hook,.post-permissions{margin-top:1.5rem;font-size:.875rem}.media-attachment h2,.post-thumbnail h2,.post-taxonomy h2,.parent-post h2,.related-posts h2,.post-version-links h2,.application-hook h2,.post-permissions h2{font-size:1rem}.media-attachment .thumbnail-preview,.post-thumbnail .thumbnail-preview,.post-taxonomy .thumbnail-preview,.parent-post .thumbnail-preview,.related-posts .thumbnail-preview,.post-version-links .thumbnail-preview,.application-hook .thumbnail-preview,.post-permissions .thumbnail-preview{display:block;width:min(100%,480px);max-height:320px;object-fit:contain;border-radius:12px;margin-bottom:.75rem}.media-attachment .post-taxonomy mat-form-field,.post-thumbnail .post-taxonomy mat-form-field,.post-taxonomy .post-taxonomy mat-form-field,.parent-post .post-taxonomy mat-form-field,.related-posts .post-taxonomy mat-form-field,.post-version-links .post-taxonomy mat-form-field,.application-hook .post-taxonomy mat-form-field,.post-permissions .post-taxonomy mat-form-field{display:block;margin-top:.75rem}.related-posts ul,.related-child-files ul,.post-permissions ul{padding:0;list-style:none}.related-posts mat-form-field,.related-child-files mat-form-field,.post-permissions mat-form-field{display:block;width:min(100%,28rem);margin-top:.75rem}.related-posts .application-hook,.related-child-files .application-hook,.post-permissions .application-hook{display:grid;align-items:start;gap:.75rem}.related-posts .application-hook mat-form-field,.related-child-files .application-hook mat-form-field,.post-permissions .application-hook mat-form-field{width:min(100%,28rem)}.related-posts li,.related-child-files li,.post-permissions li{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.related-child-files{margin-top:1.5rem;font-size:.875rem}.related-child-files li{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem 0;border-bottom:1px solid rgba(136,136,136,.2)}.related-post-title{flex:1}[cdkDragHandle]{cursor:grab}.related-child-file{flex:1;min-width:0}.related-child-file-heading,.related-child-file-progress,.related-child-progress{display:flex;align-items:center;gap:.75rem}.related-child-file-heading{justify-content:space-between;overflow-wrap:anywhere}.related-child-file-progress{margin-top:.5rem}.related-child-file-progress mat-progress-bar{flex:1}.related-child-progress{margin:.75rem 0}.related-child-progress mat-progress-bar{flex:1}.post-version-link{margin-top:1rem;padding-top:.75rem;border-top:1px solid rgba(136,136,136,.2)}.post-version-link h3{font-size:.95rem}video{max-width:100%;max-height:400px!important}\n"] }]
11935
+ ], template: "@if (mode() !== 'view-clean') {\n <ng-template #actionButtonsTemplate>\n <div class=\"buttons\">\n @if (editing()) {\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !canSave()\"\n (click)=\"save()\">\n {{ mode() === 'add' ? t.gettext('Add') : t.gettext('Save') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"cancel()\">\n {{ t.gettext('Cancel') }}\n </button>\n } @else if (currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"edit()\">\n <mat-icon>edit</mat-icon> {{ t.gettext('Edit') }}\n </button>\n }\n @if (mode() !== 'add' && currentPost()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"revert()\">\n <mat-icon>history</mat-icon> {{ t.gettext('Revert') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"delete()\">\n <mat-icon>delete</mat-icon> {{ t.gettext('Delete') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"duplicate()\">\n <mat-icon>content_copy</mat-icon> {{ t.gettext('Duplicate') }}\n </button>\n }\n </div>\n </ng-template>\n <header class=\"post-actions\">\n @if (visibilitySource(); as source) {\n <span\n class=\"post-status\"\n tabindex=\"0\"\n [matTooltip]=\"visibilityInheritedTooltip()\"\n [attr.aria-label]=\"visibilityInheritedTooltip()\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"true\">\n {{ statusLabel() }}\n </button>\n </span>\n } @else {\n <button\n class=\"post-status\"\n mat-button\n type=\"button\"\n [disabled]=\"statusControlDisabled()\"\n (click)=\"selectStatus()\">\n {{ statusLabel() }}\n </button>\n }\n <ng-container [ngTemplateOutlet]=\"actionButtonsTemplate\" />\n @if (busy()) {\n <span role=\"status\">{{\n uploading()\n ? t.gettext('Uploading... ') + uploadProgress() + '%'\n : t.gettext('Saving...')\n }}</span>\n }\n </header>\n @if (error()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ error() }}\n </p>\n }\n}\n\n<article\n class=\"post-article\"\n [attr.aria-busy]=\"busy()\">\n @if (editing()) {\n <mat-tab-group animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('CONTENT')\">\n <mat-accordion class=\"post-type-accordion\">\n <mat-expansion-panel #postTypePanel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n <mat-icon>{{ currentPostTypeIcon() }}</mat-icon>\n {{\n t.gettext('Current post type: [[ postType ]]', {\n postType: currentPostType().label,\n })\n }}\n </mat-panel-title>\n </mat-expansion-panel-header>\n <mat-radio-group\n class=\"post-type-selector\"\n [attr.aria-label]=\"t.gettext('Post type')\"\n [value]=\"draft().type\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"updateDraft('type', $event.value)\">\n @for (type of postTypes; track type.value) {\n <mat-radio-button\n [value]=\"type.value\"\n (click)=\"postTypePanel.close()\"\n >{{ type.label }}</mat-radio-button\n >\n }\n </mat-radio-group>\n </mat-expansion-panel>\n </mat-accordion>\n <div\n class=\"post-title editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post title')\"\n aria-required=\"true\"\n [attr.data-placeholder]=\"placeholderTitle()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditableSingleLine]=\"true\"\n [taonCmsEditable]=\"draft().title\"\n (taonCmsEditableChange)=\"updateDraft('title', $event)\"></div>\n @if (subtitleEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('excerpt')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <div\n class=\"post-excerpt editable\"\n contenteditable=\"true\"\n role=\"textbox\"\n [attr.aria-label]=\"t.gettext('Post excerpt')\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"placeholderShortIntro()\"\n [attr.contenteditable]=\"!busy() && !dialogOpen()\"\n [taonCmsEditable]=\"draft().excerpt\"\n (taonCmsEditableChange)=\"updateDraft('excerpt', $event)\"></div>\n @if (descriptionEmpty()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"generateLoremIpsum('body')\">\n {{ t.gettext('Generate Lorem Ipsum') }}\n </button>\n }\n <section class=\"post-content-editor\">\n @if (draft().type === contentType.JsonTable) {\n <taon-spreadsheet-table\n [data]=\"jsonTableData()\"\n [disabled]=\"busy() || dialogOpen()\"\n [validationError]=\"jsonTableValidationError()\"\n (dataChange)=\"setJsonTableData($event)\"\n (validationErrorChange)=\"jsonTableValidationError.set($event)\" />\n } @else {\n <mat-tab-group\n [selectedIndex]=\"contentTab()\"\n (selectedIndexChange)=\"setContentTab($event)\"\n [preserveContent]=\"true\"\n animationDuration=\"0ms\">\n <mat-tab [label]=\"t.gettext('Editor')\">\n @if (contentEditor(); as editor) {\n <ng-template #customMenu>\n <button\n type=\"button\"\n class=\"NgxEditor__MenuItem\"\n title=\"Table\"\n aria-label=\"Table actions\"\n [disabled]=\"busy() || dialogOpen()\"\n [matMenuTriggerFor]=\"tableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\"\n (mousedown)=\"$event.preventDefault()\">\n <mat-icon>table_chart</mat-icon>\n </button>\n\n <mat-menu #tableMenu=\"matMenu\">\n <button\n mat-menu-item\n type=\"button\"\n (click)=\"insertTable($event)\">\n <mat-icon>add</mat-icon>\n <span>Insert table</span>\n </button>\n\n <mat-divider />\n\n @for (item of tableCommands; track item.label) {\n <button\n mat-menu-item\n type=\"button\"\n [disabled]=\"!canRunTableCommand(item.command)\"\n (click)=\"runTableCommand($event, item.command)\">\n {{ item.label }}\n </button>\n }\n </mat-menu>\n </ng-template>\n\n <ngx-editor-menu\n [editor]=\"editor\"\n [customMenuRef]=\"customMenu\"\n [toolbar]=\"contentToolbar\"\n [disabled]=\"busy() || dialogOpen()\" />\n <ngx-editor\n [editor]=\"editor\"\n [placeholder]=\"placeholderTellYourStory()\" />\n }\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Raw')\">\n <textarea\n #rawContent\n class=\"raw-content\"\n [attr.aria-label]=\"t.gettext('Post body HTML')\"\n [value]=\"draft().body\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateRawContent($event)\"\n spellcheck=\"false\"></textarea>\n </mat-tab>\n <mat-tab [label]=\"t.gettext('Preview')\">\n <div\n class=\"post-body content-preview\"\n [innerHTML]=\"resolvedBody()\"></div>\n </mat-tab>\n </mat-tab-group>\n @if (contentTab() !== 2) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !contentEditor()\"\n [matMenuTriggerFor]=\"variableMenu\"\n [matMenuTriggerRestoreFocus]=\"false\">\n {{ t.gettext('Insert variable') }}\n </button>\n }\n <mat-menu #variableMenu=\"matMenu\">\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"siteVariableMenu\">\n {{ t.gettext('Site') }}\n </button>\n <button\n mat-menu-item\n [matMenuTriggerFor]=\"postVariableMenu\">\n {{ t.gettext('Post') }}\n </button>\n </mat-menu>\n <mat-menu #siteVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('frontendUrl')\">\n {{ t.gettext('Frontend URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('site.name')\">\n {{ t.gettext('Site name') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('currentYear')\">\n {{ t.gettext('Current year') }}\n </button>\n </mat-menu>\n <mat-menu #postVariableMenu=\"matMenu\">\n <button\n mat-menu-item\n (click)=\"insertVariable('post.title')\">\n {{ t.gettext('Post title') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.url')\">\n {{ t.gettext('Post URL') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.id')\">\n {{ t.gettext('Post ID') }}\n </button>\n <button\n mat-menu-item\n (click)=\"insertVariable('post.thumbnailUrl')\">\n {{ t.gettext('Post Thumbnail URL') }}\n </button>\n </mat-menu>\n }\n </section>\n <label class=\"slug-field\">\n <span>{{ t.gettext('URL slug') }}</span>\n <input\n [attr.aria-label]=\"t.gettext('URL slug')\"\n [value]=\"draft().slug\"\n [disabled]=\"busy() || dialogOpen()\"\n (input)=\"updateSlug($event)\"\n [placeholder]=\"t.gettext('my-post')\" />\n </label>\n @if (requiredSlugs.length) {\n <mat-form-field\n class=\"required-slugs-field\"\n appearance=\"outline\">\n <mat-label>{{ t.gettext('Required Slugs') }}</mat-label>\n <mat-select\n [value]=\"selectedRequiredSlug()\"\n [disabled]=\"busy() || dialogOpen()\"\n (selectionChange)=\"selectRequiredSlug($event.value)\">\n @for (slug of requiredSlugs; track slug) {\n <mat-option [value]=\"slug\">{{ slug }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n </mat-tab>\n <mat-tab [label]=\"t.gettext('SETTINGS')\">\n <div class=\"post-settings\">\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-child-files\"\n aria-labelledby=\"related-child-files-heading\">\n <h2 id=\"related-child-files-heading\">\n {{ t.gettext('Create related children from files') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Please save the post before creating children.'\n )\n }}\n </p>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Child title prefix') }}</mat-label>\n <input\n matInput\n [value]=\"childTitlePrefix()\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (input)=\"updateChildTitlePrefix($event)\" />\n </mat-form-field>\n <input\n #relatedChildFilesInput\n hidden\n type=\"file\"\n multiple\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (change)=\"relatedChildFilesSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"relatedChildFilesInput.click()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Choose files') }}\n </button>\n @if (childBatchError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ childBatchError() }}\n </p>\n }\n @if (childFiles().length) {\n <div class=\"related-child-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"childOverallProgress()\"\n [attr.aria-label]=\"\n t.gettext('Overall child creation progress')\n \" />\n <span>\n {{ childOverallProgress() }}%\n {{ t.gettext('overall progress') }}\n </span>\n </div>\n <ul>\n @for (file of childFiles(); track file.id; let index =\n $index) {\n <li>\n <div class=\"related-child-file\">\n <div class=\"related-child-file-heading\">\n <span>{{ file.file.name }}</span>\n <span>{{ childTitlePreviews()[index] }}</span>\n </div>\n <div class=\"related-child-file-progress\">\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"file.progress\"\n [attr.aria-label]=\"\n t.gettext('Progress for [[ fileName ]]', {\n fileName: file.file.name,\n })\n \" />\n <span>{{ file.progress }}%</span>\n </div>\n @if (file.status === 'processing') {\n <p role=\"status\">{{ t.gettext('Processing...') }}</p>\n } @else if (file.status === 'success') {\n <p role=\"status\">{{ t.gettext('Child created.') }}</p>\n @if (file.error) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error | json }}\n </p>\n }\n } @else if (file.status === 'error') {\n <p\n class=\"error\"\n role=\"alert\">\n {{ file.error | json }}\n </p>\n } @else {\n <p>{{ t.gettext('Waiting to upload.') }}</p>\n }\n </div>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove file [[ fileName ]]', {\n fileName: file.file.name,\n })\n \"\n [disabled]=\"!currentPost() || busy() || dialogOpen()\"\n (click)=\"removeRelatedChildFile(file.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n }\n </ul>\n }\n <button\n mat-flat-button\n type=\"button\"\n [disabled]=\"!canUploadRelatedChildren()\"\n (click)=\"uploadAndCreateChildren()\">\n {{ t.gettext('Upload and Create Children') }}\n </button>\n @if (childUploadRunning()) {\n <button\n mat-button\n type=\"button\"\n (click)=\"cancelRelatedChildUploads()\">\n {{ t.gettext('Cancel upload') }}\n </button>\n }\n </section>\n </mat-card>\n @if (mediaAttachment(); as media) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"media-attachment\"\n aria-labelledby=\"media-attachment-heading\">\n <h2 id=\"media-attachment-heading\">{{ media.heading }}</h2>\n @if (uploading()) {\n <mat-progress-bar\n mode=\"determinate\"\n [value]=\"uploadProgress()\"\n [attr.aria-label]=\"t.gettext('Media upload progress')\" />\n <button\n mat-button\n type=\"button\"\n (click)=\"cancelMediaUpload()\">\n {{ t.gettext('Cancel upload') }}\n </button>\n }\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading media preview...') }}\n </p>\n }\n @if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n }\n @if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support video playback.'\n )\n }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [src]=\"mediaPreviewUrl()\">\n {{\n t.gettext(\n 'Your browser does not support audio playback.'\n )\n }}\n </audio>\n } @else if (\n media.type === 'attachment' && mediaPreviewUrl()\n ) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ t.gettext('Replace') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeMedia()\">\n {{ t.gettext('Remove') }}\n </button>\n } @else {\n <input\n #mediaInput\n hidden\n type=\"file\"\n [accept]=\"media.accept\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"fileSelected($event)\" />\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"mediaInput.click()\">\n {{ media.uploadLabel }}\n </button>\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-thumbnail\"\n aria-labelledby=\"thumbnail-heading\">\n <h2 id=\"thumbnail-heading\">{{ t.gettext('Thumbnail') }}</h2>\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n } @else {\n <p>{{ t.gettext('No thumbnail selected.') }}</p>\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <input\n #thumbnailInput\n hidden\n type=\"file\"\n accept=\"image/*\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"thumbnailSelected($event)\" />\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"thumbnailInput.click()\">\n {{ t.gettext('Upload Thumbnail') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"applyDefaultThumbnail()\">\n {{ t.gettext('Apply Default Placeholder') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-taxonomy\"\n aria-labelledby=\"category-heading\">\n <h2 id=\"category-heading\">{{ t.gettext('Category / Tags') }}</h2>\n @if (taxonomyLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading categories and tags...') }}\n </p>\n }\n @if (taxonomyError()) {\n <p role=\"alert\">{{ taxonomyError() }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy()\"\n (click)=\"loadTaxonomy()\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Category') }}</mat-label>\n <mat-select\n [value]=\"draft().categoryId\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"updateDraft('categoryId', $event.value)\">\n <mat-option [value]=\"null\">{{\n t.gettext('None')\n }}</mat-option>\n @for (category of categories(); track category.id) {\n <mat-option [value]=\"category.id\">{{\n category.name\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <h3>{{ t.gettext('Tags') }}</h3>\n <mat-chip-set [attr.aria-label]=\"t.gettext('Selected tags')\">\n @for (tag of selectedTags(); track tag.id) {\n <mat-chip\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (removed)=\"removeTag(tag.id)\">\n {{ tag.name }}\n <button\n matChipRemove\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n [attr.aria-label]=\"\n t.gettext('Remove tag [[ name ]]', { name: tag.name })\n \">\n <mat-icon>cancel</mat-icon>\n </button>\n </mat-chip>\n }\n </mat-chip-set>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Search tags') }}</mat-label>\n <input\n matInput\n [value]=\"tagSearch()\"\n (input)=\"filterTags($event)\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\" />\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>{{ t.gettext('Add tag') }}</mat-label>\n <mat-select\n [value]=\"null\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n taxonomyLoading() ||\n !!taxonomyError()\n \"\n (selectionChange)=\"addTag($event.value)\">\n @for (tag of filteredTags(); track tag.id) {\n <mat-option [value]=\"tag.id\">{{ tag.name }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <div class=\"buttons\">\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('category')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add category') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || taxonomyLoading()\"\n (click)=\"createTaxonomy('tag')\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add tag') }}\n </button>\n </div>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"parent-post\"\n aria-labelledby=\"parent-post-heading\">\n <h2 id=\"parent-post-heading\">{{ t.gettext('Parent Post') }}</h2>\n @if (parentPostLoading()) {\n <p role=\"status\">{{ t.gettext('Loading parent post...') }}</p>\n } @else if (parentPostError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ parentPostError() }}\n </p>\n } @else if (parentPost(); as parent) {\n <p>\n <taon-cms-post-thumbnail [post]=\"parent\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, parent)\">\n {{ parent.title }}\n </a>\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeParentPost()\">\n <mat-icon>link_off</mat-icon>\n {{ t.gettext('Remove from parent') }}\n </button>\n } @else {\n <p>{{ t.gettext('No parent added') }}</p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen() || !currentPost()\"\n (click)=\"addAsChildOf()\">\n <mat-icon>account_tree</mat-icon>\n @if (currentPost()) {\n {{ t.gettext('Add as a child') }}\n } @else {\n {{\n t.gettext(\n 'Add as a child (disabled \u2014 please save the post first)'\n )\n }}\n }\n </button>\n }\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"related-posts\"\n aria-labelledby=\"related-posts-heading\">\n <h2 id=\"related-posts-heading\">\n {{ t.gettext('Related posts') }}\n </h2>\n @if (relatedPostsLoading()) {\n <p role=\"status\">{{ t.gettext('Loading related posts...') }}</p>\n } @else if (relatedPostsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ relatedPostsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadRelatedPosts()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul\n cdkDropList\n [cdkDropListData]=\"relatedPosts()\"\n [cdkDropListDisabled]=\"busy() || dialogOpen()\"\n (cdkDropListDropped)=\"reorderRelatedPosts($event)\">\n @for (post of relatedPosts(); track post.id) {\n <li cdkDrag [cdkDragData]=\"post\">\n <button\n mat-icon-button\n cdkDragHandle\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n [attr.aria-label]=\"\n t.gettext('Reorder related post [[ title ]]', {\n title: post.title,\n })\n \">\n <mat-icon>drag_indicator</mat-icon>\n </button>\n <taon-cms-post-thumbnail [post]=\"post\" />\n <span class=\"related-post-title\">\n #{{ post.id }} -\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, post)\">\n {{ post.title }}\n </a>\n </span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove related post [[ title ]]', {\n title: post.title,\n })\n \"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeRelatedPost(post.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No related posts yet.') }}</li>\n }\n </ul>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"addRelatedPost()\">\n <mat-icon>add</mat-icon> {{ t.gettext('Add related post') }}\n </button>\n }\n <mat-checkbox\n [checked]=\"applyVisibilityToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyVisibilityToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply visibility to related posts') }}\n </mat-checkbox>\n </section>\n </mat-card>\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-version-links\"\n aria-labelledby=\"post-version-links-heading\">\n <h2 id=\"post-version-links-heading\">\n {{ t.gettext('Post versions') }}\n </h2>\n @if (!currentPost()) {\n <p role=\"note\">\n {{\n t.gettext('Save the post first to manage version links.')\n }}\n </p>\n }\n @if (postVersionLinksLoading()) {\n <p role=\"status\">{{ t.gettext('Loading post versions...') }}</p>\n }\n @if (postVersionLinksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ postVersionLinksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPostVersionLinks(currentPost())\">\n {{ t.gettext('Try again') }}\n </button>\n }\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Public post version') }}</h3>\n @if (postVersionLinks().publicVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPublicVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('public')\">\n {{ t.gettext('Replace public version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('public')\">\n {{ t.gettext('Remove public version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No public post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPublicVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPublicVersion()\n \"\n (click)=\"selectVersionPost('public')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add public post version') }}\n </button>\n }\n </div>\n <div class=\"post-version-link\">\n <h3>{{ t.gettext('Private post version') }}</h3>\n @if (postVersionLinks().privateVersionPost; as versionPost) {\n <p>\n <taon-cms-post-thumbnail [post]=\"versionPost\" />\n <a\n href=\"#\"\n (click)=\"requestPostEditor($event, versionPost)\">\n {{ versionPost.title }}\n </a>\n [{{ versionPost.slug }}]\n </p>\n @if (canEditPrivateVersion()) {\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"selectVersionPost('private')\">\n {{ t.gettext('Replace private version') }}\n </button>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"removeVersionPost('private')\">\n {{ t.gettext('Remove private version') }}\n </button>\n } @else {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n } @else {\n @if (!postVersionLinksLoading() && !postVersionLinksError()) {\n <p>{{ t.gettext('No private post version linked.') }}</p>\n }\n @if (\n currentPost() &&\n !postVersionLinksLoading() &&\n !postVersionLinksError() &&\n !canEditPrivateVersion()\n ) {\n <p>\n {{\n t.gettext(\n 'Version editing is disabled for the current post status.'\n )\n }}\n </p>\n }\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || !canEditPrivateVersion()\n \"\n (click)=\"selectVersionPost('private')\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Add private post version') }}\n </button>\n }\n </div>\n </section>\n </mat-card>\n\n @if (!mediaAttachment()) {\n <mat-card class=\"post-setting-card\">\n <section\n class=\"application-hook\"\n aria-labelledby=\"application-hook-heading\">\n <h2 id=\"application-hook-heading\">\n {{ t.gettext('Hooks') }}\n </h2>\n @if (applicationHooksLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading application hooks...') }}\n </p>\n } @else if (applicationHooksError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ applicationHooksError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadApplicationHooks()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <mat-checkbox\n [checked]=\"useAsApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"setUseAsApplicationHook($event.checked)\">\n {{ t.gettext('Use as application hook') }}\n </mat-checkbox>\n @if (useAsApplicationHook()) {\n <mat-form-field\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Application Hook') }}</mat-label>\n <mat-select\n [value]=\"selectedApplicationHook()\"\n [disabled]=\"busy() || dialogOpen()\"\n (selectionChange)=\"\n selectedApplicationHook.set($event.value)\n \">\n @for (\n applicationHook of applicationHooks();\n track applicationHook.id\n ) {\n <mat-option\n [value]=\"applicationHook.hook\"\n [disabled]=\"\n applicationHookInUseByAnotherPost(applicationHook)\n \">\n {{ applicationHookLabel(applicationHook.hook) }}\n @if (\n applicationHookInUseByAnotherPost(applicationHook)\n ) {\n {{\n t.gettext('(in use by post # [[ postId ]])', {\n postId: applicationHook.contentId,\n })\n }}\n }\n </mat-option>\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No application hooks configured')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n }\n </section>\n </mat-card>\n }\n <mat-card class=\"post-setting-card\">\n <section\n class=\"post-permissions\"\n aria-labelledby=\"post-permissions-heading\">\n <h2 id=\"post-permissions-heading\">\n {{ t.gettext('Permissions') }}\n </h2>\n @if (permissionSource(); as source) {\n <p role=\"note\">\n {{\n t.gettext(\n 'Permissions are inherited from # [[ postId ]] - [[ title ]]. Edit the source post to change them.',\n { postId: source.id, title: source.title }\n )\n }}\n </p>\n }\n @if (permissionsLoading()) {\n <p role=\"status\">\n {{ t.gettext('Loading assigned permissions...') }}\n </p>\n } @else if (permissionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"busy() || dialogOpen()\"\n (click)=\"loadPermissions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <ul>\n @for (permission of permissions(); track permission.id) {\n <li>\n <span>{{ permission.name }} ({{ permission.code }})</span>\n <button\n mat-icon-button\n type=\"button\"\n [attr.aria-label]=\"\n t.gettext('Remove permission [[ name ]]', {\n name: permission.name,\n })\n \"\n [disabled]=\"\n busy() || dialogOpen() || !!permissionSource()\n \"\n (click)=\"removePermission(permission.id)\">\n <mat-icon>close</mat-icon>\n </button>\n </li>\n } @empty {\n <li>{{ t.gettext('No permissions assigned.') }}</li>\n }\n </ul>\n @if (permissionOptionsError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ permissionOptionsError() }}\n </p>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() || dialogOpen() || permissionOptionsLoading()\n \"\n (click)=\"loadPermissionOptions()\">\n {{ t.gettext('Try again') }}\n </button>\n } @else {\n <div class=\"permission-assignment\">\n <mat-form-field\n class=\"w-full\"\n appearance=\"outline\"\n subscriptSizing=\"dynamic\">\n <mat-label>{{ t.gettext('Permission') }}</mat-label>\n <mat-select\n [value]=\"selectedPermissionId()\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n permissionOptionsLoading() ||\n !!permissionSource()\n \"\n (selectionChange)=\"\n selectedPermissionId.set($event.value)\n \">\n @for (\n permission of availablePermissions();\n track permission.id\n ) {\n <mat-option [value]=\"permission.id\"\n >{{ permission.name }} ({{\n permission.code\n }})</mat-option\n >\n } @empty {\n <mat-option [disabled]=\"true\">{{\n t.gettext('No permissions available')\n }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button\n mat-button\n type=\"button\"\n [disabled]=\"\n busy() ||\n dialogOpen() ||\n !selectedPermissionId() ||\n !!permissionSource()\n \"\n (click)=\"addPermission()\">\n <mat-icon>add</mat-icon>\n {{ t.gettext('Assign permission') }}\n </button>\n </div>\n }\n }\n @if (!permissionSource()) {\n <mat-checkbox\n [checked]=\"applyPermissionsToRelatedPosts()\"\n [disabled]=\"busy() || dialogOpen()\"\n (change)=\"applyPermissionsToRelatedPosts.set($event.checked)\">\n {{ t.gettext('Apply permissions to related posts') }}\n </mat-checkbox>\n }\n </section>\n </mat-card>\n </div>\n </mat-tab>\n </mat-tab-group>\n } @else if (currentPost(); as post) {\n @if (thumbnailPreviewUrl()) {\n <img\n class=\"thumbnail-preview\"\n [src]=\"thumbnailPreviewUrl()\"\n [alt]=\"t.gettext('Post thumbnail')\" />\n }\n @if (thumbnailError()) {\n <p role=\"alert\">{{ thumbnailError() }}</p>\n }\n <h1 class=\"post-title\">{{ post.title }}</h1>\n @if (post.excerpt) {\n <p class=\"post-excerpt\">{{ post.excerpt }}</p>\n }\n @if (post.type === contentType.JsonTable) {\n <taon-spreadsheet-table\n [data]=\"jsonTableData()\"\n [validationError]=\"jsonTableValidationError()\"\n [readOnly]=\"true\" />\n } @else {\n <div\n class=\"post-body\"\n [innerHTML]=\"resolvedBody()\"></div>\n }\n @if (mediaAttachment(); as media) {\n @if (draft()[media.key]; as key) {\n @if (mediaPreviewLoading()) {\n <p role=\"status\">{{ t.gettext('Loading media...') }}</p>\n } @else if (mediaPreviewError()) {\n <p\n class=\"error\"\n role=\"alert\">\n {{ mediaPreviewError() }}\n </p>\n } @else if (media.type === 'video' && mediaPreviewUrl()) {\n <video\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [poster]=\"thumbnailPreviewUrl() || null\"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support video playback.') }}\n </video>\n } @else if (media.type === 'audio' && mediaPreviewUrl()) {\n <audio\n controls\n preload=\"metadata\"\n [attr.controlsList]=\"\n currentPost()?.status === 'archived' ||\n currentPost()?.status === 'draft'\n ? 'nodownload noremoteplayback'\n : null\n \"\n [src]=\"mediaPreviewUrl()\">\n {{ t.gettext('Your browser does not support audio playback.') }}\n </audio>\n } @else if (media.type === 'attachment' && mediaPreviewUrl()) {\n <p>\n {{\n t.gettext('Attached file: [[ fileName ]]', {\n fileName: mediaFileName(key),\n })\n }}\n <a\n [href]=\"mediaPreviewUrl()\"\n target=\"_blank\"\n rel=\"noopener\"\n >{{ t.gettext('Download or open') }}</a\n >\n </p>\n }\n }\n }\n @if (mode() === 'view-clean') {\n <taon-cms-comments\n [contentId]=\"post.id\"\n class=\"w-full\" />\n }\n } @else {\n <p role=\"status\">{{ t.gettext('No post is loaded.') }}</p>\n }\n</article>\n", styles: [":host{display:block}:host ::ng-deep .NgxEditor .ProseMirror table{width:100%;border-collapse:collapse;table-layout:fixed;margin:12px 0}:host ::ng-deep .NgxEditor .ProseMirror td,:host ::ng-deep .NgxEditor .ProseMirror th{position:relative;min-width:60px;border:1px solid #aaa;padding:8px;vertical-align:top}:host ::ng-deep .NgxEditor .ProseMirror td>p,:host ::ng-deep .NgxEditor .ProseMirror th>p{margin:0}:host ::ng-deep .NgxEditor .ProseMirror .selectedCell{background:#3f51b51f}mat-expansion-panel{border-radius:0!important}.post-actions{display:flex;align-items:center;flex-wrap:wrap;gap:1rem;padding:1rem;border-bottom:1px solid rgba(136,136,136,.2)}.post-status{margin-right:auto;text-transform:capitalize;opacity:.7}.buttons{display:flex;flex-wrap:wrap;gap:.5rem}.post-article{max-width:760px;margin:0 auto;padding:clamp(1.5rem,5vw,4rem) 1.5rem;overflow-wrap:anywhere}.post-settings{display:grid;gap:1rem;margin-top:1rem}.post-setting-card{display:block;padding:20px}.post-title{margin:0 0 1.5rem;font-size:clamp(2rem,5vw,3.5rem);font-weight:700;line-height:1.15;letter-spacing:-.03em}.post-excerpt{margin:0 0 2rem;font-size:1.35rem;line-height:1.6;opacity:.7;white-space:pre-wrap}.post-body{font-family:Georgia,Times New Roman,serif;font-size:1.2rem;line-height:1.8;white-space:pre-wrap}.editable{min-height:1.2em;outline:none}.editable:focus{box-shadow:-3px 0 #8885}.editable:empty:before{content:attr(data-placeholder);opacity:.4;pointer-events:none}.post-content-editor{border:1px solid rgba(136,136,136,.3333333333);border-radius:4px;overflow:hidden}.raw-content{display:block;box-sizing:border-box;width:100%;min-height:20rem;padding:1rem;resize:vertical;border:0;color:inherit;background:transparent;font-family:monospace}ngx-editor{display:block;min-height:20rem}.content-preview{min-height:12rem;padding:1rem}.slug-field{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;margin-top:3rem;padding-top:1rem;border-top:1px solid rgba(136,136,136,.2);font-size:.875rem}.slug-field input{flex:1;min-width:160px;padding:.5rem;color:inherit;background:transparent;border:1px solid rgba(136,136,136,.3333333333);border-radius:4px}.error{padding:1rem;color:#b3261e}.post-type-selector{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:1.5rem}.post-type-accordion{display:block;margin-bottom:1rem}.post-type-accordion mat-panel-title{align-items:center;gap:.5rem}.media-attachment,.post-thumbnail,.post-taxonomy,.parent-post,.related-posts,.post-version-links,.application-hook,.post-permissions{margin-top:1.5rem;font-size:.875rem}.media-attachment h2,.post-thumbnail h2,.post-taxonomy h2,.parent-post h2,.related-posts h2,.post-version-links h2,.application-hook h2,.post-permissions h2{font-size:1rem}.media-attachment .thumbnail-preview,.post-thumbnail .thumbnail-preview,.post-taxonomy .thumbnail-preview,.parent-post .thumbnail-preview,.related-posts .thumbnail-preview,.post-version-links .thumbnail-preview,.application-hook .thumbnail-preview,.post-permissions .thumbnail-preview{display:block;width:min(100%,480px);max-height:320px;object-fit:contain;border-radius:12px;margin-bottom:.75rem}.media-attachment .post-taxonomy mat-form-field,.post-thumbnail .post-taxonomy mat-form-field,.post-taxonomy .post-taxonomy mat-form-field,.parent-post .post-taxonomy mat-form-field,.related-posts .post-taxonomy mat-form-field,.post-version-links .post-taxonomy mat-form-field,.application-hook .post-taxonomy mat-form-field,.post-permissions .post-taxonomy mat-form-field{display:block;margin-top:.75rem}.related-posts ul,.related-child-files ul,.post-permissions ul{padding:0;list-style:none}.related-posts mat-form-field,.related-child-files mat-form-field,.post-permissions mat-form-field{display:block;width:min(100%,28rem);margin-top:.75rem}.related-posts .application-hook,.related-child-files .application-hook,.post-permissions .application-hook{display:grid;align-items:start;gap:.75rem}.related-posts .application-hook mat-form-field,.related-child-files .application-hook mat-form-field,.post-permissions .application-hook mat-form-field{width:min(100%,28rem)}.related-posts li,.related-child-files li,.post-permissions li{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.related-child-files{margin-top:1.5rem;font-size:.875rem}.related-child-files li{display:flex;align-items:flex-start;gap:.5rem;padding:.75rem 0;border-bottom:1px solid rgba(136,136,136,.2)}.related-post-title{flex:1}[cdkDragHandle]{cursor:grab}.related-child-file{flex:1;min-width:0}.related-child-file-heading,.related-child-file-progress,.related-child-progress{display:flex;align-items:center;gap:.75rem}.related-child-file-heading{justify-content:space-between;overflow-wrap:anywhere}.related-child-file-progress{margin-top:.5rem}.related-child-file-progress mat-progress-bar{flex:1}.related-child-progress{margin:.75rem 0}.related-child-progress mat-progress-bar{flex:1}.post-version-link{margin-top:1rem;padding-top:.75rem;border-top:1px solid rgba(136,136,136,.2)}.post-version-link h3{font-size:.95rem}video{max-width:100%;max-height:400px!important}.required-slugs-field{width:100%;margin-top:1rem}\n"] }]
10645
11936
  }], ctorParameters: () => [], propDecorators: { post: [{ type: i0.Input, args: [{ isSignal: true, alias: "post", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }, { type: i0.Output, args: ["modeChange"] }], changed: [{ type: i0.Output, args: ["changed"] }], deleted: [{ type: i0.Output, args: ["deleted"] }], editPostRequested: [{ type: i0.Output, args: ["editPostRequested"] }], duplicated: [{ type: i0.Output, args: ["duplicated"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], actionButtonsTemplate: [{ type: i0.Output, args: ["actionButtonsTemplate"] }], actionButtonsTemplateRef: [{
10646
11937
  type: ViewChild,
10647
11938
  args: ['actionButtonsTemplate']
@@ -11288,6 +12579,8 @@ var index$1 = /*#__PURE__*/Object.freeze({
11288
12579
  get CmsContext_1793664000000_postVersionLinks () { return CmsContext_1793664000000_postVersionLinks; },
11289
12580
  MIGRATIONS_CLASSES_FOR_CmsContext: MIGRATIONS_CLASSES_FOR_CmsContext,
11290
12581
  PROJECT_NPM_NAME: PROJECT_NPM_NAME,
12582
+ TAON_CMS_MAX_UPLOAD_BYTES: TAON_CMS_MAX_UPLOAD_BYTES,
12583
+ TAON_SPREADSHEET_MAX_BYTES: TAON_SPREADSHEET_MAX_BYTES,
11291
12584
  TaonCmsAbstractContext: TaonCmsAbstractContext,
11292
12585
  TaonCmsApplicationHook: TaonCmsApplicationHook,
11293
12586
  get TaonCmsApplicationHookEntity () { return TaonCmsApplicationHookEntity; },
@@ -11433,6 +12726,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
11433
12726
  TaonCmsWebsiteFeaturesComponent: TaonCmsWebsiteFeaturesComponent,
11434
12727
  TaonCmsWebsiteFeaturesRoutes: TaonCmsWebsiteFeaturesRoutes,
11435
12728
  TaonRelatedPostChooserComponent: TaonRelatedPostChooserComponent,
12729
+ TaonSpreadsheetTableComponent: TaonSpreadsheetTableComponent,
11436
12730
  TaonTaonCmsWebsiteFeatureConfig: TaonTaonCmsWebsiteFeatureConfig,
11437
12731
  contentError: contentError,
11438
12732
  default: index,
@@ -11440,13 +12734,20 @@ var index$1 = /*#__PURE__*/Object.freeze({
11440
12734
  defaultThumbnailUrl: defaultThumbnailUrl,
11441
12735
  extractDocumentHtml: extractDocumentHtml,
11442
12736
  generateVideoThumbnail: generateVideoThumbnail,
12737
+ importSpreadsheetJson: importSpreadsheetJson,
12738
+ inferSpreadsheetData: inferSpreadsheetData,
12739
+ normalizeSpreadsheetKey: normalizeSpreadsheetKey,
12740
+ parseSpreadsheetCsv: parseSpreadsheetCsv,
12741
+ parseSpreadsheetDocument: parseSpreadsheetDocument,
11443
12742
  requireContentId: requireContentId,
11444
12743
  requireContentVersion: requireContentVersion,
11445
12744
  resolveTemplateVariables: resolveTemplateVariables,
12745
+ serializeSpreadsheetCsv: serializeSpreadsheetCsv,
11446
12746
  startCli: startCli,
11447
12747
  trackCmsUpload: trackCmsUpload,
11448
12748
  validateCategoryHierarchy: validateCategoryHierarchy,
11449
12749
  validateContentInput: validateContentInput,
12750
+ validateSpreadsheetData: validateSpreadsheetData,
11450
12751
  validateTaxonomy: validateTaxonomy,
11451
12752
  visiblePixelRatio: visiblePixelRatio
11452
12753
  });
@@ -11455,5 +12756,5 @@ var index$1 = /*#__PURE__*/Object.freeze({
11455
12756
  * Generated bundle index. Do not edit.
11456
12757
  */
11457
12758
 
11458
- export { APP_ID, BUILD_BASE_HREF, BUILD_FRAMEWORK_CLI_NAME, CURRENT_PACKAGE_TAON_VERSION, CURRENT_PACKAGE_VERSION, CmsContext_1792506757808_applicaitonHooks, CmsContext_1793664000000_postVersionLinks, MIGRATIONS_CLASSES_FOR_CmsContext, PROJECT_NPM_NAME, TaonCmsAbstractContext, TaonCmsApplicationHook, TaonCmsApplicationHookEntity, TaonCmsAssetPreviewComponent, TaonCmsAssetsBackofficeComponent, TaonCmsAssetsBackofficeRoutes, TaonCmsBackofficeComponent, TaonCmsBackofficeRoutes, TaonCmsBlockedUsersBackofficeComponent, TaonCmsCategoriesBackofficeRoutes, TaonCmsCategoryAbstractContext, TaonCmsCategoryApiService, TaonCmsCategoryController, TaonCmsCategoryDefaultsValues, TaonCmsCategoryEntity, TaonCmsCategoryMiddleware, TaonCmsCategoryProvider, TaonCmsCategoryRepository, TaonCmsCategoryState, TaonCmsCategorySubscriber, TaonCmsCategoryUtils, TaonCmsCommentAbstractContext, TaonCmsCommentApiService, TaonCmsCommentBlockedPhraseEntity, TaonCmsCommentBlockedUserEntity, TaonCmsCommentController, TaonCmsCommentDefaultsValues, TaonCmsCommentEntity, TaonCmsCommentMiddleware, TaonCmsCommentProvider, TaonCmsCommentRepository, TaonCmsCommentSpamReportEntity, TaonCmsCommentState, TaonCmsCommentSubscriber, TaonCmsCommentUtils, TaonCmsCommentVoteEntity, TaonCmsCommentsAllPreviewBackofficeComponent, TaonCmsCommentsAllPreviewBackofficeRoutes, TaonCmsCommentsBackofficeComponent, TaonCmsCommentsBackofficeRoutes, TaonCmsCommentsBlockedUsersRoutes, TaonCmsCommentsComponent, TaonCmsCommentsFiltersSettingsComponent, TaonCmsCommentsFiltersSettingsRoutes, TaonCmsContentAbstractContext, TaonCmsContentApiService, TaonCmsContentAssetAbstractContext, TaonCmsContentAssetApiService, TaonCmsContentAssetController, TaonCmsContentAssetDefaultsValues, TaonCmsContentAssetEntity, TaonCmsContentAssetMiddleware, TaonCmsContentAssetProvider, TaonCmsContentAssetRepository, TaonCmsContentAssetState, TaonCmsContentAssetSubscriber, TaonCmsContentAssetUtils, TaonCmsContentConfigService, TaonCmsContentController, TaonCmsContentDefaultsValues, TaonCmsContentEntity, TaonCmsContentMiddleware, TaonCmsContentPermissionAbstractContext, TaonCmsContentPermissionApiService, TaonCmsContentPermissionController, TaonCmsContentPermissionDefaultsValues, TaonCmsContentPermissionEntity, TaonCmsContentPermissionMiddleware, TaonCmsContentPermissionProvider, TaonCmsContentPermissionRepository, TaonCmsContentPermissionState, TaonCmsContentPermissionSubscriber, TaonCmsContentPermissionUtils, TaonCmsContentProvider, TaonCmsContentRepository, TaonCmsContentRevisionAbstractContext, TaonCmsContentRevisionApiService, TaonCmsContentRevisionController, TaonCmsContentRevisionDefaultsValues, TaonCmsContentRevisionEntity, TaonCmsContentRevisionMiddleware, TaonCmsContentRevisionProvider, TaonCmsContentRevisionRepository, TaonCmsContentRevisionState, TaonCmsContentRevisionSubscriber, TaonCmsContentRevisionUtils, TaonCmsContentState, TaonCmsContentStorageController, TaonCmsContentSubscriber, TaonCmsContentTagAbstractContext, TaonCmsContentTagApiService, TaonCmsContentTagController, TaonCmsContentTagDefaultsValues, TaonCmsContentTagEntity, TaonCmsContentTagMiddleware, TaonCmsContentTagProvider, TaonCmsContentTagRepository, TaonCmsContentTagState, TaonCmsContentTagSubscriber, TaonCmsContentTagUtils, TaonCmsContentType, TaonCmsContentUtils, TaonCmsCourseBackofficeComponent, TaonCmsCourseBackofficeRoutes, TaonCmsCourseContentsComponent, TaonCmsCourseCoverComponent, TaonCmsEditableDirective, TaonCmsPostChoiceDialogComponent, TaonCmsPostComponent, TaonCmsPostDetailsPageComponent, TaonCmsPostRoutes, TaonCmsPostTableActions, TaonCmsPostThumbnailComponent, TaonCmsPostsBackofficeComponent, TaonCmsPostsBackofficeRoutes, TaonCmsRevisionChooserComponent, TaonCmsTagAbstractContext, TaonCmsTagApiService, TaonCmsTagController, TaonCmsTagDefaultsValues, TaonCmsTagEntity, TaonCmsTagMiddleware, TaonCmsTagProvider, TaonCmsTagRepository, TaonCmsTagState, TaonCmsTagSubscriber, TaonCmsTagUtils, TaonCmsTagsBackofficeRoutes, TaonCmsTaxonomyBackofficeComponent, TaonCmsTaxonomyQuickCreateDialogComponent, TaonCmsWebsiteFeatureAbstractContext, TaonCmsWebsiteFeatureApiService, TaonCmsWebsiteFeatureConfigService, TaonCmsWebsiteFeatureController, TaonCmsWebsiteFeatureDefaultsValues, TaonCmsWebsiteFeatureErrors, TaonCmsWebsiteFeatureKvRepository, TaonCmsWebsiteFeatureModels, TaonCmsWebsiteFeatureProvider, TaonCmsWebsiteFeatureTranslationErorsMap, TaonCmsWebsiteFeatureUtils, TaonCmsWebsiteFeatureValidators, TaonCmsWebsiteFeaturesComponent, TaonCmsWebsiteFeaturesRoutes, TaonRelatedPostChooserComponent, TaonTaonCmsWebsiteFeatureConfig, contentError, defaultThumbnailKey, defaultThumbnailUrl, extractDocumentHtml, generateVideoThumbnail, requireContentId, requireContentVersion, resolveTemplateVariables, startCli, trackCmsUpload, validateCategoryHierarchy, validateContentInput, validateTaxonomy, visiblePixelRatio };
12759
+ export { APP_ID, BUILD_BASE_HREF, BUILD_FRAMEWORK_CLI_NAME, CURRENT_PACKAGE_TAON_VERSION, CURRENT_PACKAGE_VERSION, CmsContext_1792506757808_applicaitonHooks, CmsContext_1793664000000_postVersionLinks, MIGRATIONS_CLASSES_FOR_CmsContext, PROJECT_NPM_NAME, TAON_CMS_MAX_UPLOAD_BYTES, TAON_SPREADSHEET_MAX_BYTES, TaonCmsAbstractContext, TaonCmsApplicationHook, TaonCmsApplicationHookEntity, TaonCmsAssetPreviewComponent, TaonCmsAssetsBackofficeComponent, TaonCmsAssetsBackofficeRoutes, TaonCmsBackofficeComponent, TaonCmsBackofficeRoutes, TaonCmsBlockedUsersBackofficeComponent, TaonCmsCategoriesBackofficeRoutes, TaonCmsCategoryAbstractContext, TaonCmsCategoryApiService, TaonCmsCategoryController, TaonCmsCategoryDefaultsValues, TaonCmsCategoryEntity, TaonCmsCategoryMiddleware, TaonCmsCategoryProvider, TaonCmsCategoryRepository, TaonCmsCategoryState, TaonCmsCategorySubscriber, TaonCmsCategoryUtils, TaonCmsCommentAbstractContext, TaonCmsCommentApiService, TaonCmsCommentBlockedPhraseEntity, TaonCmsCommentBlockedUserEntity, TaonCmsCommentController, TaonCmsCommentDefaultsValues, TaonCmsCommentEntity, TaonCmsCommentMiddleware, TaonCmsCommentProvider, TaonCmsCommentRepository, TaonCmsCommentSpamReportEntity, TaonCmsCommentState, TaonCmsCommentSubscriber, TaonCmsCommentUtils, TaonCmsCommentVoteEntity, TaonCmsCommentsAllPreviewBackofficeComponent, TaonCmsCommentsAllPreviewBackofficeRoutes, TaonCmsCommentsBackofficeComponent, TaonCmsCommentsBackofficeRoutes, TaonCmsCommentsBlockedUsersRoutes, TaonCmsCommentsComponent, TaonCmsCommentsFiltersSettingsComponent, TaonCmsCommentsFiltersSettingsRoutes, TaonCmsContentAbstractContext, TaonCmsContentApiService, TaonCmsContentAssetAbstractContext, TaonCmsContentAssetApiService, TaonCmsContentAssetController, TaonCmsContentAssetDefaultsValues, TaonCmsContentAssetEntity, TaonCmsContentAssetMiddleware, TaonCmsContentAssetProvider, TaonCmsContentAssetRepository, TaonCmsContentAssetState, TaonCmsContentAssetSubscriber, TaonCmsContentAssetUtils, TaonCmsContentConfigService, TaonCmsContentController, TaonCmsContentDefaultsValues, TaonCmsContentEntity, TaonCmsContentMiddleware, TaonCmsContentPermissionAbstractContext, TaonCmsContentPermissionApiService, TaonCmsContentPermissionController, TaonCmsContentPermissionDefaultsValues, TaonCmsContentPermissionEntity, TaonCmsContentPermissionMiddleware, TaonCmsContentPermissionProvider, TaonCmsContentPermissionRepository, TaonCmsContentPermissionState, TaonCmsContentPermissionSubscriber, TaonCmsContentPermissionUtils, TaonCmsContentProvider, TaonCmsContentRepository, TaonCmsContentRevisionAbstractContext, TaonCmsContentRevisionApiService, TaonCmsContentRevisionController, TaonCmsContentRevisionDefaultsValues, TaonCmsContentRevisionEntity, TaonCmsContentRevisionMiddleware, TaonCmsContentRevisionProvider, TaonCmsContentRevisionRepository, TaonCmsContentRevisionState, TaonCmsContentRevisionSubscriber, TaonCmsContentRevisionUtils, TaonCmsContentState, TaonCmsContentStorageController, TaonCmsContentSubscriber, TaonCmsContentTagAbstractContext, TaonCmsContentTagApiService, TaonCmsContentTagController, TaonCmsContentTagDefaultsValues, TaonCmsContentTagEntity, TaonCmsContentTagMiddleware, TaonCmsContentTagProvider, TaonCmsContentTagRepository, TaonCmsContentTagState, TaonCmsContentTagSubscriber, TaonCmsContentTagUtils, TaonCmsContentType, TaonCmsContentUtils, TaonCmsCourseBackofficeComponent, TaonCmsCourseBackofficeRoutes, TaonCmsCourseContentsComponent, TaonCmsCourseCoverComponent, TaonCmsEditableDirective, TaonCmsPostChoiceDialogComponent, TaonCmsPostComponent, TaonCmsPostDetailsPageComponent, TaonCmsPostRoutes, TaonCmsPostTableActions, TaonCmsPostThumbnailComponent, TaonCmsPostsBackofficeComponent, TaonCmsPostsBackofficeRoutes, TaonCmsRevisionChooserComponent, TaonCmsTagAbstractContext, TaonCmsTagApiService, TaonCmsTagController, TaonCmsTagDefaultsValues, TaonCmsTagEntity, TaonCmsTagMiddleware, TaonCmsTagProvider, TaonCmsTagRepository, TaonCmsTagState, TaonCmsTagSubscriber, TaonCmsTagUtils, TaonCmsTagsBackofficeRoutes, TaonCmsTaxonomyBackofficeComponent, TaonCmsTaxonomyQuickCreateDialogComponent, TaonCmsWebsiteFeatureAbstractContext, TaonCmsWebsiteFeatureApiService, TaonCmsWebsiteFeatureConfigService, TaonCmsWebsiteFeatureController, TaonCmsWebsiteFeatureDefaultsValues, TaonCmsWebsiteFeatureErrors, TaonCmsWebsiteFeatureKvRepository, TaonCmsWebsiteFeatureModels, TaonCmsWebsiteFeatureProvider, TaonCmsWebsiteFeatureTranslationErorsMap, TaonCmsWebsiteFeatureUtils, TaonCmsWebsiteFeatureValidators, TaonCmsWebsiteFeaturesComponent, TaonCmsWebsiteFeaturesRoutes, TaonRelatedPostChooserComponent, TaonSpreadsheetTableComponent, TaonTaonCmsWebsiteFeatureConfig, contentError, defaultThumbnailKey, defaultThumbnailUrl, extractDocumentHtml, generateVideoThumbnail, importSpreadsheetJson, inferSpreadsheetData, normalizeSpreadsheetKey, parseSpreadsheetCsv, parseSpreadsheetDocument, requireContentId, requireContentVersion, resolveTemplateVariables, serializeSpreadsheetCsv, startCli, trackCmsUpload, validateCategoryHierarchy, validateContentInput, validateSpreadsheetData, validateTaxonomy, visiblePixelRatio };
11459
12760
  //# sourceMappingURL=cms-websql.mjs.map