@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__NS__getValue, ___NS__deburr, ___NS__kebabCase, ___NS__first
6
6
  import { BaseCommandLineFeature } from 'tnp-helpers/websql-prod';
7
7
  import { Translation } from '@taon-dev/i18n/websql-prod';
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__NS__methodsInterceptors } from 'ng2-rest/websql-prod';
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-prod';
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__NS__methodsInterceptors } from 'ng2-rest/websql-prod';
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) {
@@ -1536,6 +1537,389 @@ TaonCmsContentAssetEntity = __decorate([
1536
1537
  })
1537
1538
  ], TaonCmsContentAssetEntity);
1538
1539
 
1540
+ const dangerousKeys = new Set(['__proto__', 'prototype', 'constructor']);
1541
+ const supportedTypes = new Set([
1542
+ 'string',
1543
+ 'number',
1544
+ 'boolean',
1545
+ 'date',
1546
+ 'json',
1547
+ ]);
1548
+ const maximumColumns = 200;
1549
+ const maximumRows = 10_000;
1550
+ const TAON_SPREADSHEET_MAX_BYTES = 10_000_000;
1551
+ function normalizeSpreadsheetKey(header) {
1552
+ const words = header
1553
+ .normalize('NFKD')
1554
+ .replace(/[\u0300-\u036f]/g, '')
1555
+ .match(/[A-Za-z0-9]+/g);
1556
+ if (!words?.length) {
1557
+ return '';
1558
+ }
1559
+ return words
1560
+ .map((word, index) => index === 0
1561
+ ? word.charAt(0).toLowerCase() + word.slice(1)
1562
+ : word.charAt(0).toUpperCase() + word.slice(1))
1563
+ .join('');
1564
+ }
1565
+ function validateSpreadsheetData(value) {
1566
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1567
+ throw new Error('Table data must be an object.');
1568
+ }
1569
+ const source = value;
1570
+ if (source.version !== 1 ||
1571
+ !Array.isArray(source.columns) ||
1572
+ !Array.isArray(source.rows)) {
1573
+ throw new Error('Table data must use the supported version 1 format.');
1574
+ }
1575
+ if (source.columns.length > maximumColumns) {
1576
+ throw new Error(`A table cannot have more than ${maximumColumns} columns.`);
1577
+ }
1578
+ if (source.rows.length > maximumRows) {
1579
+ throw new Error(`A table cannot have more than ${maximumRows} rows.`);
1580
+ }
1581
+ const keys = new Set();
1582
+ const columns = source.columns.map((column, index) => {
1583
+ if (!column || typeof column !== 'object') {
1584
+ throw new Error(`Column ${index + 1} is invalid.`);
1585
+ }
1586
+ const candidate = column;
1587
+ if (typeof candidate.key !== 'string' ||
1588
+ !/^[a-z][A-Za-z0-9]*$/.test(candidate.key) ||
1589
+ dangerousKeys.has(candidate.key)) {
1590
+ throw new Error(`Column ${index + 1} has an invalid key.`);
1591
+ }
1592
+ if (keys.has(candidate.key)) {
1593
+ throw new Error(`The column key "${candidate.key}" is duplicated.`);
1594
+ }
1595
+ if (!supportedTypes.has(candidate.type)) {
1596
+ throw new Error(`Column "${candidate.key}" has an unsupported type.`);
1597
+ }
1598
+ if (candidate.label !== undefined && typeof candidate.label !== 'string') {
1599
+ throw new Error(`Column "${candidate.key}" has an invalid label.`);
1600
+ }
1601
+ if (candidate.width !== undefined &&
1602
+ (!Number.isFinite(candidate.width) ||
1603
+ candidate.width < 40 ||
1604
+ candidate.width > 1000)) {
1605
+ throw new Error(`Column "${candidate.key}" has an invalid width.`);
1606
+ }
1607
+ keys.add(candidate.key);
1608
+ return {
1609
+ key: candidate.key,
1610
+ ...(candidate.label !== undefined ? { label: candidate.label } : {}),
1611
+ type: candidate.type,
1612
+ ...(candidate.width !== undefined ? { width: candidate.width } : {}),
1613
+ };
1614
+ });
1615
+ const rows = source.rows.map((row, rowIndex) => {
1616
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
1617
+ throw new Error(`Row ${rowIndex + 1} must be an object.`);
1618
+ }
1619
+ const result = {};
1620
+ for (const key of Object.keys(row)) {
1621
+ if (dangerousKeys.has(key)) {
1622
+ throw new Error(`Row ${rowIndex + 1} contains a dangerous key.`);
1623
+ }
1624
+ if (!keys.has(key)) {
1625
+ throw new Error(`Row ${rowIndex + 1} contains unknown column "${key}".`);
1626
+ }
1627
+ const cell = row[key];
1628
+ const column = columns.find(candidate => candidate.key === key);
1629
+ if (cell !== null && cell !== undefined) {
1630
+ const validType = column.type === 'string'
1631
+ ? typeof cell === 'string'
1632
+ : column.type === 'number'
1633
+ ? typeof cell === 'number' && Number.isFinite(cell)
1634
+ : column.type === 'boolean'
1635
+ ? typeof cell === 'boolean'
1636
+ : column.type === 'date'
1637
+ ? typeof cell === 'string' && isValidDateOnly(cell)
1638
+ : true;
1639
+ if (!validType) {
1640
+ throw new Error(`Row ${rowIndex + 1}, column "${key}" does not match type "${column.type}".`);
1641
+ }
1642
+ }
1643
+ if (column.type === 'json' && cell !== null && cell !== undefined) {
1644
+ assertSafeJson(cell, `Row ${rowIndex + 1}, column "${key}"`);
1645
+ }
1646
+ result[key] = cell;
1647
+ }
1648
+ return result;
1649
+ });
1650
+ return { version: 1, columns, rows };
1651
+ }
1652
+ function parseSpreadsheetDocument(body) {
1653
+ if (!body?.trim()) {
1654
+ return { version: 1, columns: [], rows: [] };
1655
+ }
1656
+ let parsed;
1657
+ try {
1658
+ parsed = JSON.parse(body);
1659
+ }
1660
+ catch {
1661
+ throw new Error('The saved JSON Table content is not valid JSON.');
1662
+ }
1663
+ if (parsed &&
1664
+ typeof parsed === 'object' &&
1665
+ !Array.isArray(parsed) &&
1666
+ parsed.version === 1) {
1667
+ return validateSpreadsheetData(parsed);
1668
+ }
1669
+ if (!Array.isArray(parsed)) {
1670
+ throw new Error('JSON Table content must be a versioned table or an array.');
1671
+ }
1672
+ return inferSpreadsheetData(parsed);
1673
+ }
1674
+ function importSpreadsheetJson(text) {
1675
+ let parsed;
1676
+ try {
1677
+ parsed = JSON.parse(text.replace(/^\uFEFF/, ''));
1678
+ }
1679
+ catch {
1680
+ throw new Error('The selected JSON file is not valid JSON.');
1681
+ }
1682
+ if (parsed &&
1683
+ typeof parsed === 'object' &&
1684
+ !Array.isArray(parsed) &&
1685
+ parsed.version === 1) {
1686
+ return validateSpreadsheetData(parsed);
1687
+ }
1688
+ if (!Array.isArray(parsed)) {
1689
+ throw new Error('JSON import must contain an array of row objects.');
1690
+ }
1691
+ return inferSpreadsheetData(parsed);
1692
+ }
1693
+ function inferSpreadsheetData(rows) {
1694
+ if (rows.length > maximumRows) {
1695
+ throw new Error(`A table cannot have more than ${maximumRows} rows.`);
1696
+ }
1697
+ if (rows.some(row => !row || typeof row !== 'object' || Array.isArray(row))) {
1698
+ throw new Error('Every imported row must be an object.');
1699
+ }
1700
+ const headers = [...new Set(rows.flatMap(row => Object.keys(row)))];
1701
+ if (headers.length > maximumColumns) {
1702
+ throw new Error(`A table cannot have more than ${maximumColumns} columns.`);
1703
+ }
1704
+ const usedKeys = new Set();
1705
+ const columns = headers.map(header => {
1706
+ let key = normalizeSpreadsheetKey(header);
1707
+ if (!key || !/^[A-Za-z]/.test(key)) {
1708
+ key = `column${key ? key.charAt(0).toUpperCase() + key.slice(1) : ''}`;
1709
+ }
1710
+ const base = key;
1711
+ let suffix = 2;
1712
+ while (usedKeys.has(key) || dangerousKeys.has(key)) {
1713
+ key = `${base}${suffix++}`;
1714
+ }
1715
+ usedKeys.add(key);
1716
+ const values = rows.map(row => row[header]);
1717
+ return {
1718
+ key,
1719
+ ...(header !== key ? { label: header } : {}),
1720
+ type: inferType(values),
1721
+ };
1722
+ });
1723
+ const keyByHeader = new Map(headers.map((header, index) => [header, columns[index].key]));
1724
+ const normalizedRows = rows.map(row => {
1725
+ const result = {};
1726
+ for (const header of headers) {
1727
+ const key = keyByHeader.get(header);
1728
+ result[key] = row[header] ?? null;
1729
+ }
1730
+ return result;
1731
+ });
1732
+ return validateSpreadsheetData({
1733
+ version: 1,
1734
+ columns,
1735
+ rows: normalizedRows,
1736
+ });
1737
+ }
1738
+ function parseSpreadsheetCsv(text, delimiter) {
1739
+ const source = text.replace(/^\uFEFF/, '');
1740
+ const separator = delimiter ?? detectDelimiter(source);
1741
+ const records = [];
1742
+ let record = [];
1743
+ let field = '';
1744
+ let quoted = false;
1745
+ for (let index = 0; index < source.length; index += 1) {
1746
+ const char = source[index];
1747
+ if (quoted) {
1748
+ if (char === '"' && source[index + 1] === '"') {
1749
+ field += '"';
1750
+ index += 1;
1751
+ }
1752
+ else if (char === '"') {
1753
+ quoted = false;
1754
+ }
1755
+ else {
1756
+ field += char;
1757
+ }
1758
+ }
1759
+ else if (char === '"' && field.length === 0) {
1760
+ quoted = true;
1761
+ }
1762
+ else if (char === separator) {
1763
+ record.push(field);
1764
+ field = '';
1765
+ }
1766
+ else if (char === '\r' || char === '\n') {
1767
+ record.push(field);
1768
+ records.push(record);
1769
+ record = [];
1770
+ field = '';
1771
+ if (char === '\r' && source[index + 1] === '\n') {
1772
+ index += 1;
1773
+ }
1774
+ }
1775
+ else {
1776
+ field += char;
1777
+ }
1778
+ }
1779
+ if (quoted) {
1780
+ throw new Error('The CSV file contains an unterminated quoted value.');
1781
+ }
1782
+ if (field.length || record.length) {
1783
+ record.push(field);
1784
+ records.push(record);
1785
+ }
1786
+ const nonEmptyRecords = records.filter(row => row.some(cell => cell !== ''));
1787
+ if (!nonEmptyRecords.length) {
1788
+ return { version: 1, columns: [], rows: [] };
1789
+ }
1790
+ const headers = nonEmptyRecords[0].map(header => header.trim());
1791
+ if (!headers.length || headers.some(header => !header)) {
1792
+ throw new Error('CSV header cells must not be empty.');
1793
+ }
1794
+ if (new Set(headers).size !== headers.length) {
1795
+ throw new Error('CSV headers must be unique before import.');
1796
+ }
1797
+ const rows = nonEmptyRecords.slice(1).map((cells, rowIndex) => {
1798
+ if (cells.length > headers.length) {
1799
+ throw new Error(`CSV row ${rowIndex + 2} has more cells than the header.`);
1800
+ }
1801
+ const result = {};
1802
+ headers.forEach((header, index) => {
1803
+ result[header] = parseCsvValue(cells[index] ?? '');
1804
+ });
1805
+ return result;
1806
+ });
1807
+ const inferred = inferSpreadsheetData(rows.length
1808
+ ? rows
1809
+ : [Object.fromEntries(headers.map(header => [header, '']))]);
1810
+ return {
1811
+ version: 1,
1812
+ columns: inferred.columns,
1813
+ rows: rows.length ? inferred.rows : [],
1814
+ };
1815
+ }
1816
+ function serializeSpreadsheetCsv(data) {
1817
+ const escape = (value) => {
1818
+ let text = value === null || value === undefined
1819
+ ? ''
1820
+ : typeof value === 'string'
1821
+ ? value
1822
+ : JSON.stringify(value);
1823
+ if (/^[\t\r\n ]*[=+\-@]/.test(text)) {
1824
+ text = `'${text}`;
1825
+ }
1826
+ return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
1827
+ };
1828
+ const header = data.columns.map(column => escape(column.label || column.key));
1829
+ const rows = data.rows.map(row => data.columns.map(column => escape(row[column.key] ?? '')).join(','));
1830
+ return [header.join(','), ...rows].join('\r\n');
1831
+ }
1832
+ function inferType(values) {
1833
+ const present = values.filter(value => value !== null && value !== undefined && value !== '');
1834
+ if (present.length &&
1835
+ present.every(value => typeof value === 'string' && isValidDateOnly(value))) {
1836
+ return 'date';
1837
+ }
1838
+ if (present.length && present.every(value => typeof value === 'boolean')) {
1839
+ return 'boolean';
1840
+ }
1841
+ if (present.length &&
1842
+ present.every(value => typeof value === 'number' && Number.isFinite(value))) {
1843
+ return 'number';
1844
+ }
1845
+ if (present.length && present.every(value => typeof value === 'object')) {
1846
+ return 'json';
1847
+ }
1848
+ return 'string';
1849
+ }
1850
+ function detectDelimiter(text) {
1851
+ const firstLine = text.split(/\r\n|\n|\r/, 1)[0] ?? '';
1852
+ const candidates = [',', ';', '\t'];
1853
+ return candidates.reduce((selected, candidate) => countUnquoted(firstLine, candidate) > countUnquoted(firstLine, selected)
1854
+ ? candidate
1855
+ : selected);
1856
+ }
1857
+ function countUnquoted(line, delimiter) {
1858
+ let quoted = false;
1859
+ let count = 0;
1860
+ for (let index = 0; index < line.length; index += 1) {
1861
+ if (line[index] === '"' && line[index + 1] === '"' && quoted) {
1862
+ index += 1;
1863
+ }
1864
+ else if (line[index] === '"') {
1865
+ quoted = !quoted;
1866
+ }
1867
+ else if (line[index] === delimiter && !quoted) {
1868
+ count += 1;
1869
+ }
1870
+ }
1871
+ return count;
1872
+ }
1873
+ function parseCsvValue(value) {
1874
+ if (!value) {
1875
+ return null;
1876
+ }
1877
+ if (/^(true|false)$/i.test(value)) {
1878
+ return value.toLowerCase() === 'true';
1879
+ }
1880
+ if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) {
1881
+ const number = Number(value);
1882
+ if (Number.isFinite(number)) {
1883
+ return number;
1884
+ }
1885
+ }
1886
+ return value;
1887
+ }
1888
+ function isValidDateOnly(value) {
1889
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
1890
+ return false;
1891
+ }
1892
+ const date = new Date(`${value}T00:00:00.000Z`);
1893
+ return (!Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value);
1894
+ }
1895
+ function assertSafeJson(value, path) {
1896
+ if (value === null ||
1897
+ typeof value === 'string' ||
1898
+ typeof value === 'boolean') {
1899
+ return;
1900
+ }
1901
+ if (typeof value === 'number') {
1902
+ if (!Number.isFinite(value)) {
1903
+ throw new Error(`${path} contains a non-finite number.`);
1904
+ }
1905
+ return;
1906
+ }
1907
+ if (Array.isArray(value)) {
1908
+ value.forEach((item, index) => assertSafeJson(item, `${path}[${index}]`));
1909
+ return;
1910
+ }
1911
+ if (typeof value === 'object') {
1912
+ for (const [key, nestedValue] of Object.entries(value)) {
1913
+ if (dangerousKeys.has(key)) {
1914
+ throw new Error(`${path} contains a dangerous JSON key.`);
1915
+ }
1916
+ assertSafeJson(nestedValue, `${path}.${key}`);
1917
+ }
1918
+ return;
1919
+ }
1920
+ throw new Error(`${path} contains a value that cannot be serialized as JSON.`);
1921
+ }
1922
+
1539
1923
  //#endregion
1540
1924
  let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRepository {
1541
1925
  constructor() {
@@ -2055,7 +2439,7 @@ let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRe
2055
2439
  if (content.status !== 'archived') {
2056
2440
  contentError('Only archived posts can be permanently deleted.', 409);
2057
2441
  }
2058
- const repository = manager.getRepository(this.target);
2442
+ const repository = manager.getRepository(TaonCmsContentEntity);
2059
2443
  const assetRepository = manager.getRepository(TaonCmsContentAssetEntity);
2060
2444
  const assetKeys = [
2061
2445
  content.videoKey,
@@ -2660,6 +3044,21 @@ let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRe
2660
3044
  if (title === undefined || slug === undefined) {
2661
3045
  contentError('title and slug are required.');
2662
3046
  }
3047
+ if (type === TaonCmsContentType.JsonTable) {
3048
+ try {
3049
+ const body = input.body === undefined ? (current?.body ?? null) : input.body;
3050
+ if (typeof body === 'string' && body.length > TAON_SPREADSHEET_MAX_BYTES) {
3051
+ contentError('JSON Table content exceeds the 10 MB limit.');
3052
+ }
3053
+ parseSpreadsheetDocument(body);
3054
+ }
3055
+ catch (error) {
3056
+ if (error instanceof Error) {
3057
+ contentError(`JSON Table content is invalid: ${error.message}`);
3058
+ }
3059
+ throw error;
3060
+ }
3061
+ }
2663
3062
  const publishedAt = input.publishedAt === undefined
2664
3063
  ? (current?.publishedAt ?? null)
2665
3064
  : input.publishedAt;
@@ -3996,6 +4395,10 @@ let TaonCmsContentStorageController = class TaonCmsContentStorageController exte
3996
4395
  /* */
3997
4396
  /* */
3998
4397
  /* */
4398
+ /* */
4399
+ /* */
4400
+ /* */
4401
+ /* */
3999
4402
  return (void 0);
4000
4403
  }
4001
4404
  async handleUploadFiles(files, queryParams, req, res) {
@@ -4036,6 +4439,50 @@ let TaonCmsContentStorageController = class TaonCmsContentStorageController exte
4036
4439
  /* */
4037
4440
  return (void 0);
4038
4441
  }
4442
+ /**
4443
+ * Access is checked in beforeEachRequest (CMS managers only).
4444
+ * The declared size is fixed at start and enforced for every part.
4445
+ */
4446
+ async authorizeMultipartUpload(options) {
4447
+ /* */
4448
+ /* */
4449
+ /* */
4450
+ /* */
4451
+ /* */
4452
+ /* */
4453
+ /* */
4454
+ /* */
4455
+ /* */
4456
+ /* */
4457
+ /* */
4458
+ return (void 0);
4459
+ }
4460
+ async handleCompletedMultipartUpload(object, queryParams, req) {
4461
+ /* */
4462
+ /* */
4463
+ /* */
4464
+ /* */
4465
+ /* */
4466
+ return (void 0);
4467
+ }
4468
+ async withDownloadUrl(object, req) {
4469
+ /* */
4470
+ /* */
4471
+ /* */
4472
+ /* */
4473
+ /* */
4474
+ /* */
4475
+ /* */
4476
+ /* */
4477
+ /* */
4478
+ /* */
4479
+ /* */
4480
+ /* */
4481
+ /* */
4482
+ /* */
4483
+ /* */
4484
+ return (void 0);
4485
+ }
4039
4486
  afterFileUploadHook() { }
4040
4487
  async isMediaObjectAvailable(key) {
4041
4488
  /* */
@@ -4199,6 +4646,7 @@ function defaultThumbnailUrl(key) {
4199
4646
  video: '<rect x="248" y="132" width="144" height="100" rx="16"/><path d="m308 158 40 24-40 24z"/>',
4200
4647
  audio: '<path d="M280 164v40m20-60v80m20-98v116m20-98v80m20-60v40"/>',
4201
4648
  attachment: '<path d="M276 124h60l32 32v84h-92z M336 124v36h32m-70 30h48m-48 22h36"/>',
4649
+ 'json-table': '<path d="M252 130h136v100H252z M252 164h136m-92-34v100m46-100v100m-90-66h136m-136 33h136"/>',
4202
4650
  };
4203
4651
  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>`;
4204
4652
  return `data:image/svg+xml,${encodeURIComponent(svg)}`;
@@ -4373,6 +4821,8 @@ let TaonCmsContentController = class TaonCmsContentController extends TaonBaseCr
4373
4821
  /* */
4374
4822
  /* */
4375
4823
  /* */
4824
+ /* */
4825
+ /* */
4376
4826
  return (void 0);
4377
4827
  }
4378
4828
  //#endregion
@@ -4524,6 +4974,57 @@ let TaonCmsContentController = class TaonCmsContentController extends TaonBaseCr
4524
4974
  //#endregion
4525
4975
  }
4526
4976
  //#endregion
4977
+ getJsonTableBySlug(slug) {
4978
+ //#region @websqlFunc
4979
+ return async (req, _res, { t }) => {
4980
+ const post = await this.requireJsonTablePost(slug, req, t, false);
4981
+ return this.jsonTableRows(post.body, t);
4982
+ };
4983
+ //#endregion
4984
+ }
4985
+ getPublicJsonTableBySlug(slug) {
4986
+ //#region @websqlFunc
4987
+ return async (req, _res, { t }) => {
4988
+ const post = await this.requireJsonTablePost(slug, req, t, true);
4989
+ return this.jsonTableRows(post.body, t);
4990
+ };
4991
+ //#endregion
4992
+ }
4993
+ async requireJsonTablePost(slug, req, t, publicOnly) {
4994
+ //#region @websqlFunc
4995
+ const normalizedSlug = typeof slug === 'string' ? slug.trim() : '';
4996
+ if (!normalizedSlug || normalizedSlug.length > 255) {
4997
+ contentError(t.gettext('The post slug is invalid.'), 400);
4998
+ }
4999
+ const post = await this.taonCmsContentRepository.getContentBySlugForMedia(normalizedSlug);
5000
+ if (!post) {
5001
+ contentError(t.gettext('The post was not found.'), 404);
5002
+ }
5003
+ if (post.type !== TaonCmsContentType.JsonTable) {
5004
+ contentError(t.gettext('The post is not a JSON Table.'), 400);
5005
+ }
5006
+ await this.requireReadAccess(post, publicOnly, req, t);
5007
+ return post;
5008
+ //#endregion
5009
+ }
5010
+ jsonTableRows(body, t) {
5011
+ //#region @websqlFunc
5012
+ if (body && body.length > TAON_SPREADSHEET_MAX_BYTES) {
5013
+ contentError('JSON Table content exceeds the 10 MB limit.', 400);
5014
+ }
5015
+ try {
5016
+ return parseSpreadsheetDocument(body).rows;
5017
+ }
5018
+ catch (error) {
5019
+ if (error instanceof Error) {
5020
+ contentError(t.gettext('JSON Table content is invalid: [[ error ]]', {
5021
+ error: error.message,
5022
+ }), 400);
5023
+ }
5024
+ throw error;
5025
+ }
5026
+ //#endregion
5027
+ }
4527
5028
  getPublicBySlug(slug) {
4528
5029
  //#region @websqlFunc
4529
5030
  return async (req, _res, { t }) => {
@@ -4858,6 +5359,20 @@ __decorate([
4858
5359
  __metadata("design:paramtypes", [String]),
4859
5360
  __metadata("design:returntype", Object)
4860
5361
  ], TaonCmsContentController.prototype, "getBySlug", null);
5362
+ __decorate([
5363
+ GET(),
5364
+ __param(0, Query('slug')),
5365
+ __metadata("design:type", Function),
5366
+ __metadata("design:paramtypes", [String]),
5367
+ __metadata("design:returntype", Object)
5368
+ ], TaonCmsContentController.prototype, "getJsonTableBySlug", null);
5369
+ __decorate([
5370
+ GET(),
5371
+ __param(0, Query('slug')),
5372
+ __metadata("design:type", Function),
5373
+ __metadata("design:paramtypes", [String]),
5374
+ __metadata("design:returntype", Object)
5375
+ ], TaonCmsContentController.prototype, "getPublicJsonTableBySlug", null);
4861
5376
  __decorate([
4862
5377
  GET(),
4863
5378
  __param(0, Query('slug')),
@@ -5040,6 +5555,8 @@ TaonCmsContentController = __decorate([
5040
5555
  allowedMethods: [
5041
5556
  'getAll',
5042
5557
  'getBy',
5558
+ 'getJsonTableBySlug',
5559
+ 'getPublicJsonTableBySlug',
5043
5560
  'getPublicBySlug',
5044
5561
  'getPublicVideoChildrenBySlug',
5045
5562
  'createContent',
@@ -5090,6 +5607,13 @@ TaonCmsContentMiddleware = __decorate([
5090
5607
 
5091
5608
  //#endregion
5092
5609
  let TaonCmsContentProvider = class TaonCmsContentProvider extends TaonBaseProvider {
5610
+ constructor() {
5611
+ super(...arguments);
5612
+ /**
5613
+ * Slugs offered in the post editor "Required Slugs" selector.
5614
+ */
5615
+ this.requiredSlugs = [];
5616
+ }
5093
5617
  async _() {
5094
5618
  if (!this.siteName) {
5095
5619
  throw new Error(`Please provide siteName in TaonCmsContentProvider`);
@@ -5450,80 +5974,20 @@ const TaonCmsAbstractContext = createContext(() => ({
5450
5974
  }));
5451
5975
  // THIS FILE IS GENERATED - DO NOT MODIFY
5452
5976
 
5453
- const progressCallbacks = new WeakMap();
5454
- // Keep Taon's middleware, retries and response decoding; replace only the
5455
- // final Fetch transport for uploads that request browser upload events.
5456
- function trackCmsUpload(url, body, onProgress) {
5457
- const key = `cms-upload-progress-POST-${new URL(url).pathname}`;
5458
- if (!Resource__NS__methodsInterceptors.has(key)) {
5459
- Resource__NS__methodsInterceptors.set(key, {
5460
- intercept: ({ req, next }) => {
5461
- if (!(req.body instanceof FormData)) {
5462
- return next.handle(req);
5463
- }
5464
- const uploadBody = req.body;
5465
- const callback = progressCallbacks.get(uploadBody);
5466
- if (!callback) {
5467
- return next.handle(req);
5468
- }
5469
- return new Observable(subscriber => {
5470
- const xhr = new XMLHttpRequest();
5471
- xhr.open(req.method || 'POST', req.url);
5472
- xhr.withCredentials = req.credentials === 'include';
5473
- new Headers(req.headers).forEach((value, name) => {
5474
- // The browser must supply the multipart boundary.
5475
- if (name.toLowerCase() !== 'content-type') {
5476
- xhr.setRequestHeader(name, value);
5477
- }
5478
- });
5479
- xhr.upload.onprogress = event => {
5480
- if (event.lengthComputable && event.total > 0) {
5481
- callback(Math.min(100, Math.floor((event.loaded * 100) / event.total)));
5482
- }
5483
- };
5484
- xhr.onload = () => {
5485
- const headers = new Headers();
5486
- for (const line of xhr
5487
- .getAllResponseHeaders()
5488
- .trim()
5489
- .split(/[\r\n]+/)) {
5490
- const separator = line.indexOf(':');
5491
- if (separator > 0) {
5492
- headers.append(line.slice(0, separator), line.slice(separator + 1).trim());
5493
- }
5494
- }
5495
- subscriber.next(new Response([204, 205, 304].includes(xhr.status) ? null : xhr.responseText, {
5496
- status: xhr.status,
5497
- statusText: xhr.statusText,
5498
- headers,
5499
- }));
5500
- subscriber.complete();
5501
- };
5502
- xhr.onerror = () => subscriber.error(new Error('Media upload network error.'));
5503
- xhr.ontimeout = () => subscriber.error(new Error('Media upload timed out.'));
5504
- xhr.onabort = () => subscriber.error(new Error('Media upload was cancelled.'));
5505
- const abort = () => xhr.abort();
5506
- req.signal?.addEventListener('abort', abort, { once: true });
5507
- if (req.signal?.aborted) {
5508
- subscriber.error(new Error('Media upload was cancelled.'));
5509
- }
5510
- else {
5511
- callback(0);
5512
- xhr.send(uploadBody);
5513
- }
5514
- return () => {
5515
- req.signal?.removeEventListener('abort', abort);
5516
- if (xhr.readyState !== XMLHttpRequest.DONE) {
5517
- xhr.abort();
5518
- }
5519
- };
5520
- });
5521
- },
5522
- });
5523
- }
5524
- progressCallbacks.set(body, onProgress);
5525
- return () => progressCallbacks.delete(body);
5526
- }
5977
+ const TaonCmsContentDefaultsValues = {
5978
+ description: '',
5979
+ version: 0,
5980
+ id: void 0,
5981
+ type: TaonCmsContentType.Normal,
5982
+ videoKey: null,
5983
+ audioKey: null,
5984
+ attachmentKey: null,
5985
+ thumbnailKey: null,
5986
+ };
5987
+ /**
5988
+ * Strict maximum size of a single CMS media upload (1 GB).
5989
+ */
5990
+ const TAON_CMS_MAX_UPLOAD_BYTES = 1_000_000_000;
5527
5991
 
5528
5992
  //#region imports
5529
5993
  //#endregion
@@ -5556,23 +6020,33 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5556
6020
  }
5557
6021
  //#endregion
5558
6022
  async getPublicBySlug(slug) {
5559
- const response = await this.taonCmsContentController
5560
- .getPublicBySlug(slug)
5561
- .request();
6023
+ const response = await this.taonCmsContentController.getPublicBySlug(slug).request();
5562
6024
  if (response.statusCode >= 400 || !response.body?.json) {
5563
6025
  throw new Error(response.responseText || 'The post could not be loaded.');
5564
6026
  }
5565
6027
  return response.body.json;
5566
6028
  }
5567
- async getPublicVideoChildrenBySlug(slug) {
5568
- const response = await this.taonCmsContentController
5569
- .getPublicVideoChildrenBySlug(slug)
5570
- .request();
6029
+ async getJsonTableBySlug(slug) {
6030
+ const response = await this.taonCmsContentController.getJsonTableBySlug(slug).request();
5571
6031
  if (response.statusCode >= 400 || !Array.isArray(response.body?.json)) {
5572
- throw new Error(response.responseText || 'Public video children could not be loaded.');
6032
+ throw new Error(response.responseText || 'The JSON Table could not be loaded.');
5573
6033
  }
5574
6034
  return response.body.json;
5575
6035
  }
6036
+ getPublicJsonTableBySlug(slug) {
6037
+ return this.taonCmsContentController.getPublicJsonTableBySlug(slug)
6038
+ .request().observable.pipe(map(c => c.body.json), catchError(error => {
6039
+ console.error(`Failed to load JSON table "${slug}":`, error);
6040
+ return of([]);
6041
+ }));
6042
+ }
6043
+ getPublicVideoChildrenBySlug(slug) {
6044
+ return this.taonCmsContentController.getPublicVideoChildrenBySlug(slug)
6045
+ .request().observable.pipe(map(c => c.body.json), catchError(error => {
6046
+ console.error(`Failed to load public video children for "${slug}":`, error);
6047
+ return of([]);
6048
+ }));
6049
+ }
5576
6050
  //#region API / get video link by slug
5577
6051
  /**
5578
6052
  * Get link to private video
@@ -5635,7 +6109,7 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5635
6109
  //#region API / get public content by hook
5636
6110
  getPublicContentByHook(hook) {
5637
6111
  return this.taonCmsContentController.getPublicContentByHook(hook)
5638
- .request().observable.pipe(map(res => res.body.text), catchError(() => of('---')));
6112
+ .request().observable.pipe(map(res => (res.body.text || '')?.replace(/\\n/g, '\n')), catchError(() => of('---')));
5639
6113
  }
5640
6114
  //#endregion
5641
6115
  //#region API / list application hooks
@@ -5645,26 +6119,21 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5645
6119
  }
5646
6120
  //#endregion
5647
6121
  //#region API / upload media
5648
- async uploadMedia(file, onProgress) {
5649
- const formData = new FormData();
5650
- formData.append('file', file);
5651
- const stopTracking = onProgress
5652
- ? trackCmsUpload(this.taonCmsContentStorageController.getThisControllerMethodFullUrl('uploadFormDataToServer'), formData, onProgress)
5653
- : undefined;
5654
- try {
5655
- const response = await this.taonCmsContentStorageController
5656
- .uploadFormDataToServer(formData)
5657
- .request();
5658
- const objects = response.body.json;
5659
- if (!Array.isArray(objects) || objects.length !== 1 || !objects[0]?.key) {
5660
- throw new Error('The upload did not return a media key.');
5661
- }
5662
- onProgress?.(100);
5663
- return objects[0];
5664
- }
5665
- finally {
5666
- stopTracking?.();
6122
+ async uploadMedia(file, onProgress, signal) {
6123
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
6124
+ throw new Error(`The file "${file.name}" is too large. The maximum upload size is 1 GB.`);
6125
+ }
6126
+ const objects = await this.taonCmsContentStorageController.uploadFileInChunks(file, {
6127
+ fileName: file.name,
6128
+ contentType: file.type || 'application/octet-stream',
6129
+ signal,
6130
+ onProgress,
6131
+ });
6132
+ if (!Array.isArray(objects) || objects.length !== 1 || !objects[0]?.key) {
6133
+ throw new Error('The upload did not return a media key.');
5667
6134
  }
6135
+ onProgress?.(100);
6136
+ return objects[0];
5668
6137
  }
5669
6138
  //#endregion
5670
6139
  async deleteUnattachedMedia(key) {
@@ -5733,10 +6202,7 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5733
6202
  }
5734
6203
  //#endregion
5735
6204
  permanentlyDeleteContent(id, expectedVersion) {
5736
- return this.taonCmsContentController
5737
- .permanentlyDeleteContent(id, expectedVersion)
5738
- .request()
5739
- .observable.pipe(map(res => res.body.json));
6205
+ return this.taonCmsContentController.permanentlyDeleteContent(id, expectedVersion).request().observable.pipe(map(res => res.body.json));
5740
6206
  }
5741
6207
  //#region API / restore content
5742
6208
  restoreContent(id, input) {
@@ -5761,7 +6227,8 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5761
6227
  }
5762
6228
  //#endregion
5763
6229
  async getParentPost(id) {
5764
- return (await this.taonCmsContentController.getParentPost(id).request()).body.json;
6230
+ return (await this.taonCmsContentController.getParentPost(id).request())
6231
+ .body.json;
5765
6232
  }
5766
6233
  async getVisibilityConfiguration(id) {
5767
6234
  return (await this.taonCmsContentController
@@ -5769,7 +6236,8 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5769
6236
  .request()).body.json;
5770
6237
  }
5771
6238
  async getPostVersions(id) {
5772
- return (await this.taonCmsContentController.getPostVersions(id).request()).body.json;
6239
+ return (await this.taonCmsContentController.getPostVersions(id).request())
6240
+ .body.json;
5773
6241
  }
5774
6242
  //#region API / list permissions
5775
6243
  listPermissions(id) {
@@ -5794,16 +6262,10 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5794
6262
  }
5795
6263
  //#endregion
5796
6264
  linkVersionPost(id, versionPostId, kind, expectedVersion) {
5797
- return this.taonCmsContentController
5798
- .linkVersionPost(id, versionPostId, kind, expectedVersion)
5799
- .request()
5800
- .observable.pipe(map(res => res.body.json));
6265
+ return this.taonCmsContentController.linkVersionPost(id, versionPostId, kind, expectedVersion).request().observable.pipe(map(res => res.body.json));
5801
6266
  }
5802
6267
  removeVersionPost(id, kind, expectedVersion) {
5803
- return this.taonCmsContentController
5804
- .removeVersionPost(id, kind, expectedVersion)
5805
- .request()
5806
- .observable.pipe(map(res => res.body.json));
6268
+ return this.taonCmsContentController.removeVersionPost(id, kind, expectedVersion).request().observable.pipe(map(res => res.body.json));
5807
6269
  }
5808
6270
  //#region API / get all entites
5809
6271
  /**
@@ -6366,6 +6828,13 @@ class TaonCmsContentConfigService extends TaonBaseAngularService {
6366
6828
  get siteName() {
6367
6829
  return this.taonCmsContentProvider.siteName;
6368
6830
  }
6831
+ get requiredSlugs() {
6832
+ return [
6833
+ ...new Set((this.taonCmsContentProvider.requiredSlugs ?? [])
6834
+ .map(slug => String(slug ?? '').trim())
6835
+ .filter(Boolean)),
6836
+ ];
6837
+ }
6369
6838
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
6370
6839
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService }); }
6371
6840
  }
@@ -6373,17 +6842,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
6373
6842
  type: Injectable
6374
6843
  }] });
6375
6844
 
6376
- const TaonCmsContentDefaultsValues = {
6377
- description: '',
6378
- version: 0,
6379
- id: void 0,
6380
- type: TaonCmsContentType.Normal,
6381
- videoKey: null,
6382
- audioKey: null,
6383
- attachmentKey: null,
6384
- thumbnailKey: null,
6385
- };
6386
-
6387
6845
  /**
6388
6846
  * Resolves allowlisted values as HTML text/attribute data, not markup.
6389
6847
  * The returned HTML must still pass through the renderer's HTML sanitizer.
@@ -6412,6 +6870,81 @@ function resolveTemplateVariables(content, context) {
6412
6870
  });
6413
6871
  }
6414
6872
 
6873
+ const progressCallbacks = new WeakMap();
6874
+ // Keep Taon's middleware, retries and response decoding; replace only the
6875
+ // final Fetch transport for uploads that request browser upload events.
6876
+ function trackCmsUpload(url, body, onProgress) {
6877
+ const key = `cms-upload-progress-POST-${new URL(url).pathname}`;
6878
+ if (!Resource__NS__methodsInterceptors.has(key)) {
6879
+ Resource__NS__methodsInterceptors.set(key, {
6880
+ intercept: ({ req, next }) => {
6881
+ if (!(req.body instanceof FormData)) {
6882
+ return next.handle(req);
6883
+ }
6884
+ const uploadBody = req.body;
6885
+ const callback = progressCallbacks.get(uploadBody);
6886
+ if (!callback) {
6887
+ return next.handle(req);
6888
+ }
6889
+ return new Observable(subscriber => {
6890
+ const xhr = new XMLHttpRequest();
6891
+ xhr.open(req.method || 'POST', req.url);
6892
+ xhr.withCredentials = req.credentials === 'include';
6893
+ new Headers(req.headers).forEach((value, name) => {
6894
+ // The browser must supply the multipart boundary.
6895
+ if (name.toLowerCase() !== 'content-type') {
6896
+ xhr.setRequestHeader(name, value);
6897
+ }
6898
+ });
6899
+ xhr.upload.onprogress = event => {
6900
+ if (event.lengthComputable && event.total > 0) {
6901
+ callback(Math.min(100, Math.floor((event.loaded * 100) / event.total)));
6902
+ }
6903
+ };
6904
+ xhr.onload = () => {
6905
+ const headers = new Headers();
6906
+ for (const line of xhr
6907
+ .getAllResponseHeaders()
6908
+ .trim()
6909
+ .split(/[\r\n]+/)) {
6910
+ const separator = line.indexOf(':');
6911
+ if (separator > 0) {
6912
+ headers.append(line.slice(0, separator), line.slice(separator + 1).trim());
6913
+ }
6914
+ }
6915
+ subscriber.next(new Response([204, 205, 304].includes(xhr.status) ? null : xhr.responseText, {
6916
+ status: xhr.status,
6917
+ statusText: xhr.statusText,
6918
+ headers,
6919
+ }));
6920
+ subscriber.complete();
6921
+ };
6922
+ xhr.onerror = () => subscriber.error(new Error('Media upload network error.'));
6923
+ xhr.ontimeout = () => subscriber.error(new Error('Media upload timed out.'));
6924
+ xhr.onabort = () => subscriber.error(new Error('Media upload was cancelled.'));
6925
+ const abort = () => xhr.abort();
6926
+ req.signal?.addEventListener('abort', abort, { once: true });
6927
+ if (req.signal?.aborted) {
6928
+ subscriber.error(new Error('Media upload was cancelled.'));
6929
+ }
6930
+ else {
6931
+ callback(0);
6932
+ xhr.send(uploadBody);
6933
+ }
6934
+ return () => {
6935
+ req.signal?.removeEventListener('abort', abort);
6936
+ if (xhr.readyState !== XMLHttpRequest.DONE) {
6937
+ xhr.abort();
6938
+ }
6939
+ };
6940
+ });
6941
+ },
6942
+ });
6943
+ }
6944
+ progressCallbacks.set(body, onProgress);
6945
+ return () => progressCallbacks.delete(body);
6946
+ }
6947
+
6415
6948
  class TaonCmsContentAssetApiService extends TaonBaseAngularService {
6416
6949
  constructor() {
6417
6950
  super(...arguments);
@@ -6990,6 +7523,606 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
6990
7523
  }]
6991
7524
  }] });
6992
7525
 
7526
+ class TaonSpreadsheetTableComponent {
7527
+ constructor() {
7528
+ this.data = model({
7529
+ version: 1,
7530
+ columns: [],
7531
+ rows: [],
7532
+ }, ...(ngDevMode ? [{ debugName: "data" }] : []));
7533
+ this.readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
7534
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
7535
+ this.validationError = model('', ...(ngDevMode ? [{ debugName: "validationError" }] : []));
7536
+ this.view = signal('table', ...(ngDevMode ? [{ debugName: "view" }] : []));
7537
+ this.activeCell = signal(null, ...(ngDevMode ? [{ debugName: "activeCell" }] : []));
7538
+ this.pendingImport = signal(null, ...(ngDevMode ? [{ debugName: "pendingImport" }] : []));
7539
+ this.importFormat = signal('', ...(ngDevMode ? [{ debugName: "importFormat" }] : []));
7540
+ this.importMode = signal('replace', ...(ngDevMode ? [{ debugName: "importMode" }] : []));
7541
+ this.columnTypes = [
7542
+ 'string',
7543
+ 'number',
7544
+ 'boolean',
7545
+ 'date',
7546
+ 'json',
7547
+ ];
7548
+ this.jsonPreview = computed(() => JSON.stringify(this.data().rows, null, 2), ...(ngDevMode ? [{ debugName: "jsonPreview" }] : []));
7549
+ this.undoStack = [];
7550
+ this.redoStack = [];
7551
+ }
7552
+ addColumn() {
7553
+ if (!this.canEdit()) {
7554
+ return;
7555
+ }
7556
+ const data = this.data();
7557
+ let number = data.columns.length + 1;
7558
+ let key = `column${number}`;
7559
+ while (data.columns.some(column => column.key === key)) {
7560
+ number += 1;
7561
+ key = `column${number}`;
7562
+ }
7563
+ this.commit({
7564
+ ...data,
7565
+ columns: [
7566
+ ...data.columns,
7567
+ { key, label: `Column ${number}`, type: 'string' },
7568
+ ],
7569
+ rows: data.rows.map(row => ({ ...row, [key]: null })),
7570
+ });
7571
+ }
7572
+ addRow() {
7573
+ if (!this.canEdit()) {
7574
+ return;
7575
+ }
7576
+ const row = {};
7577
+ for (const column of this.data().columns) {
7578
+ row[column.key] = null;
7579
+ }
7580
+ this.commit({ ...this.data(), rows: [...this.data().rows, row] });
7581
+ }
7582
+ deleteRow(rowIndex) {
7583
+ if (!this.canEdit() ||
7584
+ !globalThis.confirm('Delete this row? This cannot be undone.')) {
7585
+ return;
7586
+ }
7587
+ this.commit({
7588
+ ...this.data(),
7589
+ rows: this.data().rows.filter((_, index) => index !== rowIndex),
7590
+ });
7591
+ }
7592
+ duplicateRow(rowIndex) {
7593
+ if (!this.canEdit()) {
7594
+ return;
7595
+ }
7596
+ const rows = [...this.data().rows];
7597
+ rows.splice(rowIndex + 1, 0, { ...rows[rowIndex] });
7598
+ this.commit({ ...this.data(), rows });
7599
+ }
7600
+ deleteColumn(columnIndex) {
7601
+ if (!this.canEdit() ||
7602
+ !globalThis.confirm('Delete this column and its cell values? This cannot be undone.')) {
7603
+ return;
7604
+ }
7605
+ const column = this.data().columns[columnIndex];
7606
+ const rows = this.data().rows.map(row => {
7607
+ const { [column.key]: _removed, ...remaining } = row;
7608
+ return remaining;
7609
+ });
7610
+ this.commit({
7611
+ ...this.data(),
7612
+ columns: this.data().columns.filter((_, index) => index !== columnIndex),
7613
+ rows,
7614
+ });
7615
+ }
7616
+ renameColumn(columnIndex, field, value) {
7617
+ if (!this.canEdit()) {
7618
+ return;
7619
+ }
7620
+ const columns = this.data().columns.map(column => ({ ...column }));
7621
+ const previousKey = columns[columnIndex].key;
7622
+ const nextValue = field === 'key' ? value.trim() : value;
7623
+ const nextKey = field === 'key' ? nextValue : previousKey;
7624
+ if (field === 'key' &&
7625
+ (!/^[a-z][A-Za-z0-9]*$/.test(nextKey) ||
7626
+ ['__proto__', 'prototype', 'constructor'].includes(nextKey) ||
7627
+ columns.some((column, index) => index !== columnIndex && column.key === nextKey))) {
7628
+ this.validationError.set('Column keys must be unique, safe camelCase identifiers.');
7629
+ return;
7630
+ }
7631
+ columns[columnIndex] = { ...columns[columnIndex], [field]: nextValue };
7632
+ const rows = field === 'key' && previousKey !== nextKey
7633
+ ? this.data().rows.map(row => {
7634
+ const { [previousKey]: value, ...remaining } = row;
7635
+ return { ...remaining, [nextKey]: value };
7636
+ })
7637
+ : this.data().rows;
7638
+ this.commit({ ...this.data(), columns, rows });
7639
+ }
7640
+ setColumnType(columnIndex, type) {
7641
+ if (!this.canEdit()) {
7642
+ return;
7643
+ }
7644
+ const columns = this.data().columns.map(column => ({ ...column }));
7645
+ columns[columnIndex] = { ...columns[columnIndex], type };
7646
+ this.commit({ ...this.data(), columns });
7647
+ }
7648
+ setCell(rowIndex, column, event) {
7649
+ if (!this.canEdit()) {
7650
+ return;
7651
+ }
7652
+ const target = event.target;
7653
+ let value = target.value;
7654
+ if (column.type === 'number') {
7655
+ value = target.value === '' ? null : Number(target.value);
7656
+ if (value !== null && !Number.isFinite(value)) {
7657
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter a valid number.`);
7658
+ return;
7659
+ }
7660
+ }
7661
+ else if (column.type === 'boolean') {
7662
+ value = target.checked;
7663
+ }
7664
+ else if (column.type === 'date') {
7665
+ value = target.value
7666
+ ? new Date(`${target.value}T00:00:00.000Z`).toISOString().slice(0, 10)
7667
+ : null;
7668
+ }
7669
+ else if (column.type === 'json') {
7670
+ if (!target.value.trim()) {
7671
+ value = null;
7672
+ }
7673
+ else {
7674
+ try {
7675
+ value = JSON.parse(target.value);
7676
+ }
7677
+ catch {
7678
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter valid JSON.`);
7679
+ return;
7680
+ }
7681
+ }
7682
+ }
7683
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: value } : row);
7684
+ this.commit({ ...this.data(), rows });
7685
+ }
7686
+ onCellKeydown(event, row, column) {
7687
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
7688
+ const target = event.target;
7689
+ if (target.tagName === 'TEXTAREA') {
7690
+ return;
7691
+ }
7692
+ if (target instanceof HTMLInputElement &&
7693
+ target.type !== 'checkbox' &&
7694
+ (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
7695
+ const atBoundary = event.key === 'ArrowLeft'
7696
+ ? target.selectionStart === 0
7697
+ : target.selectionStart === target.value.length;
7698
+ if (!atBoundary) {
7699
+ return;
7700
+ }
7701
+ }
7702
+ event.preventDefault();
7703
+ const next = {
7704
+ row: row +
7705
+ (event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0),
7706
+ column: column +
7707
+ (event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0),
7708
+ };
7709
+ this.focusCell(next.row, next.column);
7710
+ return;
7711
+ }
7712
+ if (event.target.tagName === 'TEXTAREA' &&
7713
+ event.key === 'Enter' &&
7714
+ !(event.ctrlKey || event.metaKey)) {
7715
+ return;
7716
+ }
7717
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'z') {
7718
+ event.preventDefault();
7719
+ event.shiftKey ? this.redo() : this.undo();
7720
+ return;
7721
+ }
7722
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'y') {
7723
+ event.preventDefault();
7724
+ this.redo();
7725
+ return;
7726
+ }
7727
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') {
7728
+ event.preventDefault();
7729
+ const value = this.cellValue(row, this.data().columns[column]);
7730
+ void (navigator.clipboard?.writeText(value) ??
7731
+ Promise.reject(new Error('Clipboard access is unavailable.'))).catch(() => {
7732
+ this.validationError.set('Could not access the clipboard.');
7733
+ });
7734
+ return;
7735
+ }
7736
+ if (event.key === 'Escape') {
7737
+ event.target.blur();
7738
+ return;
7739
+ }
7740
+ if (event.key === 'Tab' || event.key === 'Enter') {
7741
+ event.preventDefault();
7742
+ const direction = event.shiftKey ? -1 : 1;
7743
+ let next = { row, column };
7744
+ if (event.key === 'Tab') {
7745
+ let nextColumn = column + direction;
7746
+ let nextRow = row;
7747
+ if (nextColumn >= this.data().columns.length) {
7748
+ nextColumn = 0;
7749
+ nextRow += 1;
7750
+ }
7751
+ else if (nextColumn < 0) {
7752
+ nextColumn = this.data().columns.length - 1;
7753
+ nextRow -= 1;
7754
+ }
7755
+ next = { row: nextRow, column: nextColumn };
7756
+ }
7757
+ else {
7758
+ next = { row: row + direction, column };
7759
+ }
7760
+ if (event.key === 'Tab' &&
7761
+ !event.shiftKey &&
7762
+ next.row === this.data().rows.length) {
7763
+ this.addRow();
7764
+ }
7765
+ this.activeCell.set(next);
7766
+ this.focusCell(next.row, next.column);
7767
+ }
7768
+ else if (event.key === 'Backspace' || event.key === 'Delete') {
7769
+ if (!['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
7770
+ event.preventDefault();
7771
+ this.clearCell(row, column);
7772
+ }
7773
+ }
7774
+ }
7775
+ onCellPaste(event, rowIndex, columnIndex) {
7776
+ if (!this.canEdit()) {
7777
+ return;
7778
+ }
7779
+ const text = event.clipboardData?.getData('text/plain');
7780
+ if (!text || (!text.includes('\t') && !/[\r\n]/.test(text))) {
7781
+ return;
7782
+ }
7783
+ event.preventDefault();
7784
+ try {
7785
+ const matrix = parseClipboardMatrix(text);
7786
+ const columns = [...this.data().columns];
7787
+ const pastedWidth = Math.max(...matrix.map(row => row.length));
7788
+ while (columns.length < columnIndex + pastedWidth &&
7789
+ columns.length < 200) {
7790
+ let number = columns.length + 1;
7791
+ let key = `column${number}`;
7792
+ while (columns.some(column => column.key === key)) {
7793
+ key = `column${++number}`;
7794
+ }
7795
+ columns.push({ key, label: `Column ${number}`, type: 'string' });
7796
+ }
7797
+ if (columns.length < columnIndex + pastedWidth) {
7798
+ throw new Error('Pasted data exceeds the 200-column table limit.');
7799
+ }
7800
+ const rows = [...this.data().rows];
7801
+ while (rows.length < rowIndex + matrix.length && rows.length < 10_000) {
7802
+ const newRow = {};
7803
+ for (const column of columns) {
7804
+ newRow[column.key] = null;
7805
+ }
7806
+ rows.push(newRow);
7807
+ }
7808
+ if (rows.length < rowIndex + matrix.length) {
7809
+ throw new Error('Pasted data exceeds the 10,000-row table limit.');
7810
+ }
7811
+ matrix.forEach((values, pastedRowIndex) => {
7812
+ values.forEach((rawValue, pastedColumnIndex) => {
7813
+ const targetColumn = columnIndex + pastedColumnIndex;
7814
+ const column = columns[targetColumn];
7815
+ rows[rowIndex + pastedRowIndex] = {
7816
+ ...rows[rowIndex + pastedRowIndex],
7817
+ [column.key]: parsePastedCell(rawValue, column),
7818
+ };
7819
+ });
7820
+ });
7821
+ this.commit({ version: 1, columns, rows });
7822
+ }
7823
+ catch (error) {
7824
+ this.validationError.set(error instanceof Error ? error.message : 'The pasted data is invalid.');
7825
+ }
7826
+ }
7827
+ onRowDrop(event) {
7828
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7829
+ return;
7830
+ }
7831
+ const rows = [...this.data().rows];
7832
+ moveItemInArray(rows, event.previousIndex, event.currentIndex);
7833
+ this.commit({ ...this.data(), rows });
7834
+ }
7835
+ onColumnDrop(event) {
7836
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7837
+ return;
7838
+ }
7839
+ const columns = [...this.data().columns];
7840
+ moveItemInArray(columns, event.previousIndex, event.currentIndex);
7841
+ this.commit({ ...this.data(), columns });
7842
+ }
7843
+ undo() {
7844
+ if (!this.canEdit()) {
7845
+ return;
7846
+ }
7847
+ const previous = this.undoStack.pop();
7848
+ if (!previous) {
7849
+ return;
7850
+ }
7851
+ this.redoStack.push(this.data());
7852
+ this.data.set(previous);
7853
+ this.validationError.set('');
7854
+ }
7855
+ redo() {
7856
+ if (!this.canEdit()) {
7857
+ return;
7858
+ }
7859
+ const next = this.redoStack.pop();
7860
+ if (!next) {
7861
+ return;
7862
+ }
7863
+ this.undoStack.push(this.data());
7864
+ this.data.set(next);
7865
+ this.validationError.set('');
7866
+ }
7867
+ async importFile(event) {
7868
+ const inputElement = event.target;
7869
+ const file = inputElement.files?.[0];
7870
+ inputElement.value = '';
7871
+ if (!file || !this.canEdit()) {
7872
+ return;
7873
+ }
7874
+ try {
7875
+ if (file.size > TAON_SPREADSHEET_MAX_BYTES) {
7876
+ throw new Error('Import files must be 10 MB or smaller.');
7877
+ }
7878
+ const text = await file.text();
7879
+ const imported = /\.json$/i.test(file.name)
7880
+ ? importSpreadsheetJson(text)
7881
+ : /\.csv$/i.test(file.name)
7882
+ ? parseSpreadsheetCsv(text)
7883
+ : (() => {
7884
+ throw new Error('Only JSON and CSV files are supported.');
7885
+ })();
7886
+ this.importFormat.set(/\.json$/i.test(file.name) ? 'JSON' : 'CSV');
7887
+ this.importMode.set('replace');
7888
+ this.pendingImport.set(imported);
7889
+ }
7890
+ catch (error) {
7891
+ this.validationError.set(error instanceof Error
7892
+ ? error.message
7893
+ : 'The selected file could not be imported.');
7894
+ }
7895
+ }
7896
+ applyImport() {
7897
+ const imported = this.pendingImport();
7898
+ if (!imported || !this.canEdit()) {
7899
+ return;
7900
+ }
7901
+ if (this.importMode() === 'replace' &&
7902
+ (this.data().rows.length > 0 || this.data().columns.length > 0) &&
7903
+ !globalThis.confirm('Replace the current table with the imported data?')) {
7904
+ return;
7905
+ }
7906
+ if (this.importMode() === 'append') {
7907
+ const currentColumns = this.data().columns;
7908
+ const compatible = currentColumns.length === imported.columns.length &&
7909
+ currentColumns.every((column, index) => column.key === imported.columns[index].key &&
7910
+ column.type === imported.columns[index].type);
7911
+ if (!compatible) {
7912
+ this.validationError.set('Append requires imported columns with the same keys, types, and order as this table.');
7913
+ return;
7914
+ }
7915
+ this.commit({
7916
+ ...this.data(),
7917
+ rows: [...this.data().rows, ...imported.rows],
7918
+ });
7919
+ }
7920
+ else {
7921
+ this.commit(imported);
7922
+ }
7923
+ this.pendingImport.set(null);
7924
+ }
7925
+ cancelImport() {
7926
+ this.pendingImport.set(null);
7927
+ }
7928
+ previewCell(row, column) {
7929
+ const value = row[column.key];
7930
+ return value === undefined
7931
+ ? ''
7932
+ : typeof value === 'string'
7933
+ ? value
7934
+ : JSON.stringify(value);
7935
+ }
7936
+ downloadJson(fullTable = false) {
7937
+ const content = JSON.stringify(fullTable ? this.data() : this.data().rows, null, 2);
7938
+ this.download(content, fullTable ? 'json-table.json' : 'table-data.json', 'application/json');
7939
+ }
7940
+ downloadCsv() {
7941
+ this.download(serializeSpreadsheetCsv(this.data()), 'table-data.csv', 'text/csv');
7942
+ }
7943
+ async copyJson() {
7944
+ try {
7945
+ if (!navigator.clipboard) {
7946
+ throw new Error('Clipboard access is unavailable.');
7947
+ }
7948
+ await navigator.clipboard.writeText(this.jsonPreview());
7949
+ this.validationError.set('');
7950
+ }
7951
+ catch {
7952
+ this.validationError.set('Could not access the clipboard. Copy the preview manually.');
7953
+ }
7954
+ }
7955
+ keyFromLabel(label) {
7956
+ return normalizeSpreadsheetKey(label);
7957
+ }
7958
+ setColumnWidth(columnIndex, value) {
7959
+ if (!this.canEdit()) {
7960
+ return;
7961
+ }
7962
+ const width = Number(value);
7963
+ if (!Number.isFinite(width) || width < 40 || width > 1000) {
7964
+ this.validationError.set('Column width must be between 40 and 1000 pixels.');
7965
+ return;
7966
+ }
7967
+ const columns = this.data().columns.map(column => ({ ...column }));
7968
+ columns[columnIndex] = { ...columns[columnIndex], width };
7969
+ this.commit({ ...this.data(), columns });
7970
+ }
7971
+ cellValue(rowIndex, column) {
7972
+ const value = this.data().rows[rowIndex]?.[column.key];
7973
+ if (column.type === 'json' && value !== null && value !== undefined) {
7974
+ return JSON.stringify(value);
7975
+ }
7976
+ return value === null || value === undefined ? '' : String(value);
7977
+ }
7978
+ trackColumn(_index, column) {
7979
+ return column.key;
7980
+ }
7981
+ clearCell(rowIndex, columnIndex) {
7982
+ const column = this.data().columns[columnIndex];
7983
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: null } : row);
7984
+ this.commit({ ...this.data(), rows });
7985
+ }
7986
+ focusCell(row, column) {
7987
+ if (row < 0 ||
7988
+ row >= this.data().rows.length ||
7989
+ column < 0 ||
7990
+ column >= this.data().columns.length) {
7991
+ return;
7992
+ }
7993
+ this.activeCell.set({ row, column });
7994
+ setTimeout(() => {
7995
+ document
7996
+ .querySelector(`[data-cell="${row}:${column}"]`)
7997
+ ?.focus();
7998
+ });
7999
+ }
8000
+ commit(value) {
8001
+ try {
8002
+ const validated = validateSpreadsheetData(value);
8003
+ this.undoStack.push(this.data());
8004
+ if (this.undoStack.length > 100) {
8005
+ this.undoStack.shift();
8006
+ }
8007
+ this.redoStack.length = 0;
8008
+ this.data.set(validated);
8009
+ this.validationError.set('');
8010
+ }
8011
+ catch (error) {
8012
+ this.validationError.set(error instanceof Error ? error.message : 'Table data is invalid.');
8013
+ }
8014
+ }
8015
+ canEdit() {
8016
+ return !this.readOnly() && !this.disabled();
8017
+ }
8018
+ download(content, fileName, contentType) {
8019
+ const url = URL.createObjectURL(new Blob([content], { type: contentType }));
8020
+ const anchor = document.createElement('a');
8021
+ anchor.href = url;
8022
+ anchor.download = fileName;
8023
+ anchor.click();
8024
+ URL.revokeObjectURL(url);
8025
+ }
8026
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8027
+ 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 }); }
8028
+ }
8029
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, decorators: [{
8030
+ type: Component,
8031
+ args: [{ selector: 'taon-spreadsheet-table', standalone: true, imports: [
8032
+ CdkDrag,
8033
+ CdkDragHandle,
8034
+ CdkDropList,
8035
+ MatButtonModule,
8036
+ MatIconModule,
8037
+ MatSelectModule,
8038
+ ], 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"] }]
8039
+ }], 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"] }] } });
8040
+ function parseClipboardMatrix(value) {
8041
+ const rows = [];
8042
+ let row = [];
8043
+ let cell = '';
8044
+ let quoted = false;
8045
+ for (let index = 0; index < value.length; index += 1) {
8046
+ const character = value[index];
8047
+ if (quoted) {
8048
+ if (character === '"' && value[index + 1] === '"') {
8049
+ cell += '"';
8050
+ index += 1;
8051
+ }
8052
+ else if (character === '"') {
8053
+ quoted = false;
8054
+ }
8055
+ else {
8056
+ cell += character;
8057
+ }
8058
+ }
8059
+ else if (character === '"' && !cell) {
8060
+ quoted = true;
8061
+ }
8062
+ else if (character === '\t') {
8063
+ row.push(cell);
8064
+ cell = '';
8065
+ }
8066
+ else if (character === '\r' || character === '\n') {
8067
+ row.push(cell);
8068
+ rows.push(row);
8069
+ row = [];
8070
+ cell = '';
8071
+ if (character === '\r' && value[index + 1] === '\n') {
8072
+ index += 1;
8073
+ }
8074
+ }
8075
+ else {
8076
+ cell += character;
8077
+ }
8078
+ }
8079
+ if (quoted) {
8080
+ throw new Error('Pasted data contains an unterminated quoted value.');
8081
+ }
8082
+ if (cell.length || row.length) {
8083
+ row.push(cell);
8084
+ rows.push(row);
8085
+ }
8086
+ return rows;
8087
+ }
8088
+ function parsePastedCell(value, column) {
8089
+ if (value === '') {
8090
+ return null;
8091
+ }
8092
+ if (column.type === 'number') {
8093
+ const parsed = Number(value);
8094
+ if (!Number.isFinite(parsed)) {
8095
+ throw new Error(`"${value}" is not a valid number for ${column.label || column.key}.`);
8096
+ }
8097
+ return parsed;
8098
+ }
8099
+ if (column.type === 'boolean') {
8100
+ if (/^(true|1|yes)$/i.test(value)) {
8101
+ return true;
8102
+ }
8103
+ if (/^(false|0|no)$/i.test(value)) {
8104
+ return false;
8105
+ }
8106
+ throw new Error(`"${value}" is not a valid boolean for ${column.label || column.key}.`);
8107
+ }
8108
+ if (column.type === 'date') {
8109
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
8110
+ Number.isNaN(Date.parse(`${value}T00:00:00.000Z`))) {
8111
+ throw new Error(`"${value}" is not a valid date for ${column.label || column.key}.`);
8112
+ }
8113
+ return value;
8114
+ }
8115
+ if (column.type === 'json') {
8116
+ try {
8117
+ return JSON.parse(value);
8118
+ }
8119
+ catch {
8120
+ throw new Error(`"${value}" is not valid JSON for ${column.label || column.key}.`);
8121
+ }
8122
+ }
8123
+ return value;
8124
+ }
8125
+
6993
8126
  //#region imports
6994
8127
  //#endregion
6995
8128
  class TaonCmsWebsiteFeatureApiService extends TaonBaseAngularService {
@@ -7153,7 +8286,7 @@ class TaonCmsWebsiteFeaturesComponent {
7153
8286
  }
7154
8287
  }
7155
8288
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7156
- 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 }); }
8289
+ 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 }); }
7157
8290
  }
7158
8291
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, decorators: [{
7159
8292
  type: Component,
@@ -7263,7 +8396,7 @@ const TaonCmsCommentsBackofficeRoutes = [
7263
8396
  */
7264
8397
  // export default TaonCmsCommentsBackofficeRoutes;
7265
8398
 
7266
- 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-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-BmZmONdL.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-CUcC-r1T.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-BngVW1ST.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ccy7Dcvv.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CjnsarRp.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DdQZA_Pt.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-CuYgfCFH.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BWTN9DLn.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-CBcBZFVK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-ClarunPV.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-YtHY1P_8.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-C8qKRpYs.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-CHBqahB1.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-btgJU4-b.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-Dv-cf9Kp.mjs')).default } });
8399
+ 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-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-BmZmONdL.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-CUcC-r1T.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-BngVW1ST.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ccy7Dcvv.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CjnsarRp.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DdQZA_Pt.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-CuYgfCFH.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BWTN9DLn.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-CBcBZFVK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-DfkiEwvY.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-DX25wx9y.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-BC9-axt9.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-BOO0IBXz.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-BcYSlBzW.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-mk6gw3C2.mjs')).default } });
7267
8400
  class TaonCmsPostChoiceDialogComponent {
7268
8401
  constructor() {
7269
8402
  this.t = t$f.for(this);
@@ -7666,7 +8799,7 @@ class TaonCmsCourseBackofficeComponent extends TaonCmsPostTableActions {
7666
8799
  });
7667
8800
  }
7668
8801
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
7669
- 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 }); }
8802
+ 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 }); }
7670
8803
  }
7671
8804
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, decorators: [{
7672
8805
  type: Component,
@@ -8323,7 +9456,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8323
9456
  args: ['paste', ['$event']]
8324
9457
  }] } });
8325
9458
 
8326
- 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-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-BmZmONdL.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-CUcC-r1T.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-BngVW1ST.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ccy7Dcvv.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CjnsarRp.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DdQZA_Pt.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-CuYgfCFH.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BWTN9DLn.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-CBcBZFVK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-ClarunPV.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-YtHY1P_8.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-C8qKRpYs.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-CHBqahB1.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-btgJU4-b.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-Dv-cf9Kp.mjs')).default } });
9459
+ 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-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-BmZmONdL.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-CUcC-r1T.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-BngVW1ST.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ccy7Dcvv.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CjnsarRp.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DdQZA_Pt.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-CuYgfCFH.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BWTN9DLn.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-CBcBZFVK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-DfkiEwvY.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-DX25wx9y.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-BC9-axt9.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-BOO0IBXz.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-BcYSlBzW.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-mk6gw3C2.mjs')).default } });
8327
9460
  class TaonCmsPostThumbnailComponent {
8328
9461
  constructor() {
8329
9462
  this.t = t$a.for(this);
@@ -8529,8 +9662,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8529
9662
 
8530
9663
  //#region imports
8531
9664
  //#endregion
8532
- 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-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-BmZmONdL.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-CUcC-r1T.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-BngVW1ST.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ccy7Dcvv.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CjnsarRp.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DdQZA_Pt.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-CuYgfCFH.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BWTN9DLn.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-CBcBZFVK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-ClarunPV.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-YtHY1P_8.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-C8qKRpYs.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-CHBqahB1.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-btgJU4-b.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-Dv-cf9Kp.mjs')).default } });
9665
+ 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-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-BmZmONdL.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-CUcC-r1T.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-BngVW1ST.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ccy7Dcvv.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-CjnsarRp.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DdQZA_Pt.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-CuYgfCFH.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BWTN9DLn.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-CBcBZFVK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.en-US.i18n.data-DfkiEwvY.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.pl-PL.i18n.data-DX25wx9y.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.html.de-DE.i18n.data-BC9-axt9.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.en-US.i18n.data-BOO0IBXz.mjs')).default, "pl-PL": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.pl-PL.i18n.data-BcYSlBzW.mjs')).default, "de-DE": async () => (await import('./cms-websql-prod-taon-cms-post.component.ts.de-DE.i18n.data-mk6gw3C2.mjs')).default } });
8533
9666
  class TaonCmsPostComponent {
9667
+ get requiredSlugs() {
9668
+ return this.contentConfig.requiredSlugs;
9669
+ }
8534
9670
  insertTable(event) {
8535
9671
  event.preventDefault();
8536
9672
  const editor = this.contentEditor();
@@ -8551,6 +9687,7 @@ class TaonCmsPostComponent {
8551
9687
  constructor() {
8552
9688
  //#region fields and getters
8553
9689
  this.t = t$7.for(this);
9690
+ this.contentType = TaonCmsContentType;
8554
9691
  this.post = input(null, ...(ngDevMode ? [{ debugName: "post" }] : []));
8555
9692
  this.mode = model('view', ...(ngDevMode ? [{ debugName: "mode" }] : []));
8556
9693
  this.changed = output();
@@ -8577,6 +9714,12 @@ class TaonCmsPostComponent {
8577
9714
  categoryId: null,
8578
9715
  tagIds: [],
8579
9716
  }, ...(ngDevMode ? [{ debugName: "draft" }] : []));
9717
+ this.jsonTableData = signal({
9718
+ version: 1,
9719
+ columns: [],
9720
+ rows: [],
9721
+ }, ...(ngDevMode ? [{ debugName: "jsonTableData" }] : []));
9722
+ this.jsonTableValidationError = signal('', ...(ngDevMode ? [{ debugName: "jsonTableValidationError" }] : []));
8580
9723
  this.postTypes = [
8581
9724
  {
8582
9725
  value: TaonCmsContentType.Normal,
@@ -8588,6 +9731,10 @@ class TaonCmsPostComponent {
8588
9731
  value: TaonCmsContentType.Attachment,
8589
9732
  label: this.t.gettext('Attachment post'),
8590
9733
  },
9734
+ {
9735
+ value: TaonCmsContentType.JsonTable,
9736
+ label: this.t.gettext('JSON Table post'),
9737
+ },
8591
9738
  ];
8592
9739
  this.currentPostType = computed(() => this.postTypes.find(type => type.value === this.draft().type) ??
8593
9740
  this.postTypes[0], ...(ngDevMode ? [{ debugName: "currentPostType" }] : []));
@@ -8599,6 +9746,8 @@ class TaonCmsPostComponent {
8599
9746
  return 'audiotrack';
8600
9747
  case TaonCmsContentType.Attachment:
8601
9748
  return 'attach_file';
9749
+ case TaonCmsContentType.JsonTable:
9750
+ return 'table_chart';
8602
9751
  default:
8603
9752
  return 'article';
8604
9753
  }
@@ -8659,6 +9808,9 @@ class TaonCmsPostComponent {
8659
9808
  this.stagedThumbnail = signal(null, ...(ngDevMode ? [{ debugName: "stagedThumbnail" }] : []));
8660
9809
  this.uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : []));
8661
9810
  this.uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
9811
+ this.mediaUploadAbort = null;
9812
+ this.childUploadAbort = null;
9813
+ this.childUploadRunning = signal(false, ...(ngDevMode ? [{ debugName: "childUploadRunning" }] : []));
8662
9814
  this.mediaPreviewUrl = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewUrl" }] : []));
8663
9815
  this.mediaPreviewLoading = signal(false, ...(ngDevMode ? [{ debugName: "mediaPreviewLoading" }] : []));
8664
9816
  this.mediaPreviewError = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewError" }] : []));
@@ -8771,7 +9923,9 @@ class TaonCmsPostComponent {
8771
9923
  !this.permissionsLoading() &&
8772
9924
  !this.permissionsError() &&
8773
9925
  !this.taxonomyLoading() &&
8774
- !this.taxonomyError(), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
9926
+ !this.taxonomyError() &&
9927
+ (this.draft().type !== TaonCmsContentType.JsonTable ||
9928
+ !this.jsonTableValidationError()), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
8775
9929
  this.statusLabel = computed(() => {
8776
9930
  const status = this.mode() === 'add'
8777
9931
  ? this.draftStatus()
@@ -8793,6 +9947,10 @@ class TaonCmsPostComponent {
8793
9947
  (this.mode() === 'edit' && !this.canSave()), ...(ngDevMode ? [{ debugName: "statusControlDisabled" }] : []));
8794
9948
  this.api = inject(TaonCmsContentApiService);
8795
9949
  this.contentConfig = inject(TaonCmsContentConfigService);
9950
+ this.selectedRequiredSlug = computed(() => {
9951
+ const slug = this.draft().slug;
9952
+ return this.requiredSlugs.includes(slug) ? slug : null;
9953
+ }, ...(ngDevMode ? [{ debugName: "selectedRequiredSlug" }] : []));
8796
9954
  this.contentTab = signal(0, ...(ngDevMode ? [{ debugName: "contentTab" }] : []));
8797
9955
  this.contentEditor = signal(null, ...(ngDevMode ? [{ debugName: "contentEditor" }] : []));
8798
9956
  this.rawContent = viewChild('rawContent', ...(ngDevMode ? [{ debugName: "rawContent" }] : []));
@@ -9003,7 +10161,10 @@ class TaonCmsPostComponent {
9003
10161
  });
9004
10162
  }
9005
10163
  //#endregion
9006
- ngOnDestroy() { }
10164
+ ngOnDestroy() {
10165
+ this.mediaUploadAbort?.abort();
10166
+ this.childUploadAbort?.abort();
10167
+ }
9007
10168
  //#region methods / load related posts
9008
10169
  loadTaxonomy(preserveSelectedTags = false) {
9009
10170
  this.taxonomyLoad?.unsubscribe();
@@ -9123,6 +10284,10 @@ class TaonCmsPostComponent {
9123
10284
  this.error.set(this.t.gettext('Please select an image for the thumbnail.'));
9124
10285
  return;
9125
10286
  }
10287
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
10288
+ this.error.set(this.fileTooLargeMessage(file));
10289
+ return;
10290
+ }
9126
10291
  this.busy.set(true);
9127
10292
  this.error.set('');
9128
10293
  try {
@@ -9706,6 +10871,33 @@ class TaonCmsPostComponent {
9706
10871
  //#endregion
9707
10872
  //#region methods / update draft
9708
10873
  updateDraft(field, value) {
10874
+ if (field === 'type' &&
10875
+ value === TaonCmsContentType.JsonTable &&
10876
+ this.draft().type !== TaonCmsContentType.JsonTable) {
10877
+ let tableData = {
10878
+ version: 1,
10879
+ columns: [],
10880
+ rows: [],
10881
+ };
10882
+ if (this.draft().body.trim()) {
10883
+ try {
10884
+ tableData = parseSpreadsheetDocument(this.draft().body);
10885
+ }
10886
+ catch {
10887
+ if (!globalThis.confirm('Changing the post to a JSON Table will replace its current body. Continue?')) {
10888
+ return;
10889
+ }
10890
+ }
10891
+ }
10892
+ this.jsonTableData.set(tableData);
10893
+ this.jsonTableValidationError.set('');
10894
+ this.draft.update(draft => ({
10895
+ ...draft,
10896
+ type: value,
10897
+ body: JSON.stringify(tableData),
10898
+ }));
10899
+ return;
10900
+ }
9709
10901
  if (field === 'title' && this.mode() === 'add') {
9710
10902
  const title = String(value);
9711
10903
  this.draft.update(draft => ({
@@ -9739,10 +10931,13 @@ class TaonCmsPostComponent {
9739
10931
  if (!this.currentPost() || this.busy() || !files.length) {
9740
10932
  return;
9741
10933
  }
9742
- this.childBatchError.set('');
10934
+ const oversized = files.filter(file => file.size > TAON_CMS_MAX_UPLOAD_BYTES);
10935
+ this.childBatchError.set(oversized.map(file => this.fileTooLargeMessage(file)).join(' '));
9743
10936
  this.childFiles.update(selected => [
9744
10937
  ...selected,
9745
- ...files.map(file => ({
10938
+ ...files
10939
+ .filter(file => file.size <= TAON_CMS_MAX_UPLOAD_BYTES)
10940
+ .map(file => ({
9746
10941
  id: ++this.childFileSequence,
9747
10942
  file,
9748
10943
  status: 'pending',
@@ -9773,10 +10968,16 @@ class TaonCmsPostComponent {
9773
10968
  const pendingFiles = this.childFiles()
9774
10969
  .map((file, index) => ({ file, index }))
9775
10970
  .filter(({ file }) => file.status !== 'success');
10971
+ const abort = new AbortController();
10972
+ this.childUploadAbort = abort;
10973
+ this.childUploadRunning.set(true);
9776
10974
  this.busy.set(true);
9777
10975
  this.childBatchError.set('');
9778
10976
  try {
9779
10977
  for (const { file: selectedFile, index } of pendingFiles) {
10978
+ if (abort.signal.aborted) {
10979
+ break;
10980
+ }
9780
10981
  const expectedTitle = this.childTitlePreviews()[index];
9781
10982
  this.updateRelatedChildFile(selectedFile.id, {
9782
10983
  status: 'processing',
@@ -9792,22 +10993,24 @@ class TaonCmsPostComponent {
9792
10993
  }
9793
10994
  let uploads = this.childFiles().find(file => file.id === selectedFile.id)?.uploads;
9794
10995
  if (!uploads?.source) {
10996
+ abort.signal.throwIfAborted();
9795
10997
  const source = await this.api.uploadMedia(selectedFile.file, percentage => this.updateRelatedChildFile(selectedFile.id, {
9796
10998
  progress: type === TaonCmsContentType.Video
9797
10999
  ? Math.round(percentage * 0.65)
9798
11000
  : Math.round(percentage * 0.8),
9799
- }));
11001
+ }), abort.signal);
9800
11002
  uploads = { ...uploads, source };
9801
11003
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9802
11004
  }
9803
11005
  if (type === TaonCmsContentType.Video && !uploads.thumbnail) {
9804
11006
  const thumbnailBlob = await generateVideoThumbnail(selectedFile.file);
9805
11007
  const extension = thumbnailBlob.type === 'image/webp' ? 'webp' : 'png';
11008
+ abort.signal.throwIfAborted();
9806
11009
  const thumbnail = await this.api.uploadMedia(new File([thumbnailBlob], `thumbnail.${extension}`, {
9807
11010
  type: thumbnailBlob.type,
9808
11011
  }), percentage => this.updateRelatedChildFile(selectedFile.id, {
9809
11012
  progress: 65 + Math.round(percentage * 0.25),
9810
- }));
11013
+ }), abort.signal);
9811
11014
  uploads = { ...uploads, thumbnail };
9812
11015
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9813
11016
  }
@@ -9824,6 +11027,8 @@ class TaonCmsPostComponent {
9824
11027
  if (!sourceUpload) {
9825
11028
  throw new Error('The uploaded source file is unavailable.');
9826
11029
  }
11030
+ // Cancellation is honoured only before the child post is created.
11031
+ abort.signal.throwIfAborted();
9827
11032
  const result = await this.api.createRelatedChild(parent.id, {
9828
11033
  titlePrefix,
9829
11034
  expectedTitle,
@@ -9877,7 +11082,10 @@ class TaonCmsPostComponent {
9877
11082
  ? await this.cleanupRelatedChildUploads(selectedFile.id, uploads)
9878
11083
  : []
9879
11084
  : [];
9880
- const reason = error instanceof Error ? error.message : String(error);
11085
+ const reason = abort.signal.aborted
11086
+ ? this.t.gettext('Upload cancelled. No child post was created.')
11087
+ : error?.body?.json?.message ||
11088
+ (error instanceof Error ? error.message : String(error));
9881
11089
  const verificationError = relatedPosts
9882
11090
  ? ''
9883
11091
  : `${this.t.gettext('The creation result could not be verified; retrying will not duplicate an existing child.')} `;
@@ -9891,6 +11099,10 @@ class TaonCmsPostComponent {
9891
11099
  }
9892
11100
  }
9893
11101
  finally {
11102
+ if (this.childUploadAbort === abort) {
11103
+ this.childUploadAbort = null;
11104
+ }
11105
+ this.childUploadRunning.set(false);
9894
11106
  this.busy.set(false);
9895
11107
  this.loadRelatedPosts();
9896
11108
  }
@@ -10028,7 +11240,14 @@ class TaonCmsPostComponent {
10028
11240
  input.value = '';
10029
11241
  return;
10030
11242
  }
11243
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
11244
+ input.value = '';
11245
+ this.error.set(this.fileTooLargeMessage(file));
11246
+ return;
11247
+ }
10031
11248
  const session = this.editorSession;
11249
+ const abort = new AbortController();
11250
+ this.mediaUploadAbort = abort;
10032
11251
  this.busy.set(true);
10033
11252
  this.uploading.set(true);
10034
11253
  this.uploadProgress.set(0);
@@ -10038,7 +11257,7 @@ class TaonCmsPostComponent {
10038
11257
  if (!this.destroyRef.destroyed) {
10039
11258
  this.uploadProgress.set(percentage);
10040
11259
  }
10041
- });
11260
+ }, abort.signal);
10042
11261
  if (!this.destroyRef.destroyed && this.editorSession === session) {
10043
11262
  const fileName = object.fileName || file.name;
10044
11263
  this.mediaFileNames.update(names => ({
@@ -10067,10 +11286,20 @@ class TaonCmsPostComponent {
10067
11286
  }
10068
11287
  }
10069
11288
  catch (error) {
10070
- console.error('[taon-cms-post] Unable to upload media', error);
10071
- this.error.set(this.t.gettext('The media could not be uploaded. Please try again.'));
11289
+ if (abort.signal.aborted) {
11290
+ if (!this.destroyRef.destroyed) {
11291
+ this.error.set(this.t.gettext('The media upload was cancelled.'));
11292
+ }
11293
+ }
11294
+ else {
11295
+ console.error('[taon-cms-post] Unable to upload media', error);
11296
+ this.error.set(`${this.t.gettext('The media could not be uploaded. Please try again.')}${this.uploadErrorDetails(error)}`);
11297
+ }
10072
11298
  }
10073
11299
  finally {
11300
+ if (this.mediaUploadAbort === abort) {
11301
+ this.mediaUploadAbort = null;
11302
+ }
10074
11303
  this.busy.set(false);
10075
11304
  this.uploading.set(false);
10076
11305
  this.uploadProgress.set(0);
@@ -10078,6 +11307,26 @@ class TaonCmsPostComponent {
10078
11307
  }
10079
11308
  }
10080
11309
  //#endregion
11310
+ //#region methods / cancel media upload
11311
+ cancelMediaUpload() {
11312
+ this.mediaUploadAbort?.abort();
11313
+ }
11314
+ //#endregion
11315
+ //#region methods / cancel related child uploads
11316
+ cancelRelatedChildUploads() {
11317
+ this.childUploadAbort?.abort();
11318
+ }
11319
+ //#endregion
11320
+ //#region methods / upload error helpers
11321
+ fileTooLargeMessage(file) {
11322
+ return this.t.gettext('The file "[[ fileName ]]" is too large. The maximum upload size is 1 GB.', { fileName: file.name });
11323
+ }
11324
+ uploadErrorDetails(error) {
11325
+ const message = error?.body?.json?.message ||
11326
+ (error instanceof Error ? error.message : '');
11327
+ return message ? ` (${message})` : '';
11328
+ }
11329
+ //#endregion
10081
11330
  //#region methods / remove media
10082
11331
  removeMedia() {
10083
11332
  const media = this.mediaAttachment();
@@ -10151,6 +11400,11 @@ class TaonCmsPostComponent {
10151
11400
  this.updateDraft('slug', event.target.value);
10152
11401
  }
10153
11402
  }
11403
+ selectRequiredSlug(slug) {
11404
+ if (slug && this.requiredSlugs.includes(slug)) {
11405
+ this.updateDraft('slug', slug);
11406
+ }
11407
+ }
10154
11408
  //#endregion
10155
11409
  //#region methods / edit
10156
11410
  edit() {
@@ -10541,6 +11795,13 @@ class TaonCmsPostComponent {
10541
11795
  categoryId: post?.categoryId ?? null,
10542
11796
  tagIds: [],
10543
11797
  });
11798
+ if ((post?.type ?? TaonCmsContentType.Normal) === TaonCmsContentType.JsonTable) {
11799
+ this.loadJsonTableData(post?.body ?? null);
11800
+ }
11801
+ else {
11802
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11803
+ this.jsonTableValidationError.set('');
11804
+ }
10544
11805
  if (post &&
10545
11806
  post.relatedPosts === undefined &&
10546
11807
  this.mode() !== 'view-clean') {
@@ -10554,6 +11815,34 @@ class TaonCmsPostComponent {
10554
11815
  }
10555
11816
  }
10556
11817
  //#endregion
11818
+ setJsonTableData(value) {
11819
+ try {
11820
+ const data = validateSpreadsheetData(value);
11821
+ this.jsonTableData.set(data);
11822
+ this.draft.update(draft => ({
11823
+ ...draft,
11824
+ body: JSON.stringify(data),
11825
+ }));
11826
+ }
11827
+ catch (error) {
11828
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11829
+ }
11830
+ }
11831
+ loadJsonTableData(body) {
11832
+ try {
11833
+ const data = parseSpreadsheetDocument(body);
11834
+ this.jsonTableData.set(data);
11835
+ this.draft.update(draft => ({
11836
+ ...draft,
11837
+ body: JSON.stringify(data),
11838
+ }));
11839
+ this.jsonTableValidationError.set('');
11840
+ }
11841
+ catch (error) {
11842
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11843
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11844
+ }
11845
+ }
10557
11846
  //#region methods / set application hook selection
10558
11847
  setApplicationHookSelection(post, hooks) {
10559
11848
  const assignedHook = post?.applicationHook !== undefined
@@ -10601,7 +11890,7 @@ class TaonCmsPostComponent {
10601
11890
  TaonPermissionApiService,
10602
11891
  TaonCmsCategoryApiService,
10603
11892
  TaonCmsTagApiService,
10604
- ], 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 }); }
11893
+ ], 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 }); }
10605
11894
  }
10606
11895
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsPostComponent, decorators: [{
10607
11896
  type: Component,
@@ -10627,13 +11916,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
10627
11916
  MatDividerModule,
10628
11917
  DragDropModule,
10629
11918
  TaonCmsPostThumbnailComponent,
11919
+ TaonSpreadsheetTableComponent,
11920
+ JsonPipe,
10630
11921
  ], providers: [
10631
11922
  TaonCmsContentApiService,
10632
11923
  TaonCmsContentConfigService,
10633
11924
  TaonPermissionApiService,
10634
11925
  TaonCmsCategoryApiService,
10635
11926
  TaonCmsTagApiService,
10636
- ], 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"] }]
11927
+ ], 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"] }]
10637
11928
  }], 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: [{
10638
11929
  type: ViewChild,
10639
11930
  args: ['actionButtonsTemplate']
@@ -11290,6 +12581,8 @@ var index$1 = /*#__PURE__*/Object.freeze({
11290
12581
  get CmsContext_1793664000000_postVersionLinks () { return CmsContext_1793664000000_postVersionLinks; },
11291
12582
  MIGRATIONS_CLASSES_FOR_CmsContext: MIGRATIONS_CLASSES_FOR_CmsContext,
11292
12583
  PROJECT_NPM_NAME: PROJECT_NPM_NAME,
12584
+ TAON_CMS_MAX_UPLOAD_BYTES: TAON_CMS_MAX_UPLOAD_BYTES,
12585
+ TAON_SPREADSHEET_MAX_BYTES: TAON_SPREADSHEET_MAX_BYTES,
11293
12586
  TaonCmsAbstractContext: TaonCmsAbstractContext,
11294
12587
  TaonCmsApplicationHook: TaonCmsApplicationHook,
11295
12588
  get TaonCmsApplicationHookEntity () { return TaonCmsApplicationHookEntity; },
@@ -11437,6 +12730,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
11437
12730
  TaonCmsWebsiteFeaturesComponent: TaonCmsWebsiteFeaturesComponent,
11438
12731
  TaonCmsWebsiteFeaturesRoutes: TaonCmsWebsiteFeaturesRoutes,
11439
12732
  TaonRelatedPostChooserComponent: TaonRelatedPostChooserComponent,
12733
+ TaonSpreadsheetTableComponent: TaonSpreadsheetTableComponent,
11440
12734
  TaonTaonCmsWebsiteFeatureConfig: TaonTaonCmsWebsiteFeatureConfig,
11441
12735
  contentError: contentError,
11442
12736
  default: index,
@@ -11444,13 +12738,20 @@ var index$1 = /*#__PURE__*/Object.freeze({
11444
12738
  defaultThumbnailUrl: defaultThumbnailUrl,
11445
12739
  extractDocumentHtml: extractDocumentHtml,
11446
12740
  generateVideoThumbnail: generateVideoThumbnail,
12741
+ importSpreadsheetJson: importSpreadsheetJson,
12742
+ inferSpreadsheetData: inferSpreadsheetData,
12743
+ normalizeSpreadsheetKey: normalizeSpreadsheetKey,
12744
+ parseSpreadsheetCsv: parseSpreadsheetCsv,
12745
+ parseSpreadsheetDocument: parseSpreadsheetDocument,
11447
12746
  requireContentId: requireContentId,
11448
12747
  requireContentVersion: requireContentVersion,
11449
12748
  resolveTemplateVariables: resolveTemplateVariables,
12749
+ serializeSpreadsheetCsv: serializeSpreadsheetCsv,
11450
12750
  startCli: startCli,
11451
12751
  trackCmsUpload: trackCmsUpload,
11452
12752
  validateCategoryHierarchy: validateCategoryHierarchy,
11453
12753
  validateContentInput: validateContentInput,
12754
+ validateSpreadsheetData: validateSpreadsheetData,
11454
12755
  validateTaxonomy: validateTaxonomy,
11455
12756
  visiblePixelRatio: visiblePixelRatio
11456
12757
  });
@@ -11459,5 +12760,5 @@ var index$1 = /*#__PURE__*/Object.freeze({
11459
12760
  * Generated bundle index. Do not edit.
11460
12761
  */
11461
12762
 
11462
- 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__NS__isActive, TaonCmsCommentAbstractContext, TaonCmsCommentApiService, TaonCmsCommentBlockedPhraseEntity, TaonCmsCommentBlockedUserEntity, TaonCmsCommentController, TaonCmsCommentDefaultsValues, TaonCmsCommentEntity, TaonCmsCommentMiddleware, TaonCmsCommentProvider, TaonCmsCommentRepository, TaonCmsCommentSpamReportEntity, TaonCmsCommentState, TaonCmsCommentSubscriber, TaonCmsCommentUtils__NS__isActive, TaonCmsCommentVoteEntity, TaonCmsCommentsAllPreviewBackofficeComponent, TaonCmsCommentsAllPreviewBackofficeRoutes, TaonCmsCommentsBackofficeComponent, TaonCmsCommentsBackofficeRoutes, TaonCmsCommentsBlockedUsersRoutes, TaonCmsCommentsComponent, TaonCmsCommentsFiltersSettingsComponent, TaonCmsCommentsFiltersSettingsRoutes, TaonCmsContentAbstractContext, TaonCmsContentApiService, TaonCmsContentAssetAbstractContext, TaonCmsContentAssetApiService, TaonCmsContentAssetController, TaonCmsContentAssetDefaultsValues, TaonCmsContentAssetEntity, TaonCmsContentAssetMiddleware, TaonCmsContentAssetProvider, TaonCmsContentAssetRepository, TaonCmsContentAssetState, TaonCmsContentAssetSubscriber, TaonCmsContentAssetUtils__NS__isActive, TaonCmsContentConfigService, TaonCmsContentController, TaonCmsContentDefaultsValues, TaonCmsContentEntity, TaonCmsContentMiddleware, TaonCmsContentPermissionAbstractContext, TaonCmsContentPermissionApiService, TaonCmsContentPermissionController, TaonCmsContentPermissionDefaultsValues, TaonCmsContentPermissionEntity, TaonCmsContentPermissionMiddleware, TaonCmsContentPermissionProvider, TaonCmsContentPermissionRepository, TaonCmsContentPermissionState, TaonCmsContentPermissionSubscriber, TaonCmsContentPermissionUtils__NS__isActive, TaonCmsContentProvider, TaonCmsContentRepository, TaonCmsContentRevisionAbstractContext, TaonCmsContentRevisionApiService, TaonCmsContentRevisionController, TaonCmsContentRevisionDefaultsValues, TaonCmsContentRevisionEntity, TaonCmsContentRevisionMiddleware, TaonCmsContentRevisionProvider, TaonCmsContentRevisionRepository, TaonCmsContentRevisionState, TaonCmsContentRevisionSubscriber, TaonCmsContentRevisionUtils__NS__isActive, TaonCmsContentState, TaonCmsContentStorageController, TaonCmsContentSubscriber, TaonCmsContentTagAbstractContext, TaonCmsContentTagApiService, TaonCmsContentTagController, TaonCmsContentTagDefaultsValues, TaonCmsContentTagEntity, TaonCmsContentTagMiddleware, TaonCmsContentTagProvider, TaonCmsContentTagRepository, TaonCmsContentTagState, TaonCmsContentTagSubscriber, TaonCmsContentTagUtils__NS__isActive, TaonCmsContentType, TaonCmsContentUtils__NS__isActive, TaonCmsContentUtils__NS__orderRelatedPosts, 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__NS__isActive, TaonCmsTagsBackofficeRoutes, TaonCmsTaxonomyBackofficeComponent, TaonCmsTaxonomyQuickCreateDialogComponent, TaonCmsWebsiteFeatureAbstractContext, TaonCmsWebsiteFeatureApiService, TaonCmsWebsiteFeatureConfigService, TaonCmsWebsiteFeatureController, TaonCmsWebsiteFeatureDefaultsValues, TaonCmsWebsiteFeatureErrors, TaonCmsWebsiteFeatureKvRepository, TaonCmsWebsiteFeatureModels__NS__TaonCmsWebsiteFeatureState, TaonCmsWebsiteFeatureProvider, TaonCmsWebsiteFeatureTranslationErorsMap, TaonCmsWebsiteFeatureUtils__NS__isActive, TaonCmsWebsiteFeatureUtils__NS__keyFromName, TaonCmsWebsiteFeatureValidators__NS__passwordMatchValidator, TaonCmsWebsiteFeaturesComponent, TaonCmsWebsiteFeaturesRoutes, TaonRelatedPostChooserComponent, TaonTaonCmsWebsiteFeatureConfig, contentError, defaultThumbnailKey, defaultThumbnailUrl, extractDocumentHtml, generateVideoThumbnail, requireContentId, requireContentVersion, resolveTemplateVariables, startCli, trackCmsUpload, validateCategoryHierarchy, validateContentInput, validateTaxonomy, visiblePixelRatio };
12763
+ 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__NS__isActive, TaonCmsCommentAbstractContext, TaonCmsCommentApiService, TaonCmsCommentBlockedPhraseEntity, TaonCmsCommentBlockedUserEntity, TaonCmsCommentController, TaonCmsCommentDefaultsValues, TaonCmsCommentEntity, TaonCmsCommentMiddleware, TaonCmsCommentProvider, TaonCmsCommentRepository, TaonCmsCommentSpamReportEntity, TaonCmsCommentState, TaonCmsCommentSubscriber, TaonCmsCommentUtils__NS__isActive, TaonCmsCommentVoteEntity, TaonCmsCommentsAllPreviewBackofficeComponent, TaonCmsCommentsAllPreviewBackofficeRoutes, TaonCmsCommentsBackofficeComponent, TaonCmsCommentsBackofficeRoutes, TaonCmsCommentsBlockedUsersRoutes, TaonCmsCommentsComponent, TaonCmsCommentsFiltersSettingsComponent, TaonCmsCommentsFiltersSettingsRoutes, TaonCmsContentAbstractContext, TaonCmsContentApiService, TaonCmsContentAssetAbstractContext, TaonCmsContentAssetApiService, TaonCmsContentAssetController, TaonCmsContentAssetDefaultsValues, TaonCmsContentAssetEntity, TaonCmsContentAssetMiddleware, TaonCmsContentAssetProvider, TaonCmsContentAssetRepository, TaonCmsContentAssetState, TaonCmsContentAssetSubscriber, TaonCmsContentAssetUtils__NS__isActive, TaonCmsContentConfigService, TaonCmsContentController, TaonCmsContentDefaultsValues, TaonCmsContentEntity, TaonCmsContentMiddleware, TaonCmsContentPermissionAbstractContext, TaonCmsContentPermissionApiService, TaonCmsContentPermissionController, TaonCmsContentPermissionDefaultsValues, TaonCmsContentPermissionEntity, TaonCmsContentPermissionMiddleware, TaonCmsContentPermissionProvider, TaonCmsContentPermissionRepository, TaonCmsContentPermissionState, TaonCmsContentPermissionSubscriber, TaonCmsContentPermissionUtils__NS__isActive, TaonCmsContentProvider, TaonCmsContentRepository, TaonCmsContentRevisionAbstractContext, TaonCmsContentRevisionApiService, TaonCmsContentRevisionController, TaonCmsContentRevisionDefaultsValues, TaonCmsContentRevisionEntity, TaonCmsContentRevisionMiddleware, TaonCmsContentRevisionProvider, TaonCmsContentRevisionRepository, TaonCmsContentRevisionState, TaonCmsContentRevisionSubscriber, TaonCmsContentRevisionUtils__NS__isActive, TaonCmsContentState, TaonCmsContentStorageController, TaonCmsContentSubscriber, TaonCmsContentTagAbstractContext, TaonCmsContentTagApiService, TaonCmsContentTagController, TaonCmsContentTagDefaultsValues, TaonCmsContentTagEntity, TaonCmsContentTagMiddleware, TaonCmsContentTagProvider, TaonCmsContentTagRepository, TaonCmsContentTagState, TaonCmsContentTagSubscriber, TaonCmsContentTagUtils__NS__isActive, TaonCmsContentType, TaonCmsContentUtils__NS__isActive, TaonCmsContentUtils__NS__orderRelatedPosts, 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__NS__isActive, TaonCmsTagsBackofficeRoutes, TaonCmsTaxonomyBackofficeComponent, TaonCmsTaxonomyQuickCreateDialogComponent, TaonCmsWebsiteFeatureAbstractContext, TaonCmsWebsiteFeatureApiService, TaonCmsWebsiteFeatureConfigService, TaonCmsWebsiteFeatureController, TaonCmsWebsiteFeatureDefaultsValues, TaonCmsWebsiteFeatureErrors, TaonCmsWebsiteFeatureKvRepository, TaonCmsWebsiteFeatureModels__NS__TaonCmsWebsiteFeatureState, TaonCmsWebsiteFeatureProvider, TaonCmsWebsiteFeatureTranslationErorsMap, TaonCmsWebsiteFeatureUtils__NS__isActive, TaonCmsWebsiteFeatureUtils__NS__keyFromName, TaonCmsWebsiteFeatureValidators__NS__passwordMatchValidator, 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 };
11463
12764
  //# sourceMappingURL=cms-websql-prod.mjs.map