@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 { BaseCommandLineFeature } from 'tnp-helpers/browser-prod';
6
6
  import { TaonSessionRepository, TaonAuthContextRepository, TaonSessionAbstractContext, TaonSessionProvider, TaonSessionApiService, TaonPermissionApiService } from '@taon-dev/session/browser-prod';
7
7
  import { Translation } from '@taon-dev/i18n/browser-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/browser-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/browser-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/browser-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,19 +26,21 @@ 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 { DomSanitizer } from '@angular/platform-browser';
36
- import * as i17 from '@angular/cdk/drag-drop';
37
- import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
38
38
  import * as i3$3 from '@angular/material/card';
39
39
  import { MatCardModule } from '@angular/material/card';
40
40
  import * as i11 from '@angular/material/chips';
41
41
  import { MatChipsModule } from '@angular/material/chips';
42
+ import * as i16 from '@angular/material/divider';
43
+ import { MatDividerModule } from '@angular/material/divider';
42
44
  import * as i4$1 from '@angular/material/expansion';
43
45
  import { MatExpansionModule } from '@angular/material/expansion';
44
46
  import * as i8 from '@angular/material/menu';
@@ -52,8 +54,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
52
54
  import * as i10 from 'ngx-editor';
53
55
  import { toHTML, NgxEditorModule } from 'ngx-editor';
54
56
  import { addRowBefore, addRowAfter, deleteRow, addColumnBefore, addColumnAfter, deleteColumn, deleteTable } from 'prosemirror-tables';
55
- import * as i16 from '@angular/material/divider';
56
- import { MatDividerModule } from '@angular/material/divider';
57
57
 
58
58
  var TaonCmsContentType;
59
59
  (function (TaonCmsContentType) {
@@ -61,6 +61,7 @@ var TaonCmsContentType;
61
61
  TaonCmsContentType["Video"] = "video";
62
62
  TaonCmsContentType["Audio"] = "audio";
63
63
  TaonCmsContentType["Attachment"] = "attachment";
64
+ TaonCmsContentType["JsonTable"] = "json-table";
64
65
  })(TaonCmsContentType || (TaonCmsContentType = {}));
65
66
  var TaonCmsContentState;
66
67
  (function (TaonCmsContentState) {
@@ -282,7 +283,7 @@ const CURRENT_PACKAGE_TAON_VERSION = 'v21';
282
283
  /**
283
284
  * Autogenerated by current cli tool. Use *tnp release* to bump version.
284
285
  */
285
- const CURRENT_PACKAGE_VERSION = '21.0.25';
286
+ const CURRENT_PACKAGE_VERSION = '21.0.27';
286
287
  // THIS FILE IS GENERATED - DO NOT MODIFY
287
288
 
288
289
  //#region tnp-helpers cli template
@@ -1055,7 +1056,7 @@ function validateContentInput(input, creating = false) {
1055
1056
  }
1056
1057
  if (input.type !== undefined &&
1057
1058
  !Object.values(TaonCmsContentType).includes(input.type)) {
1058
- contentError('type must be normal, video, audio, or attachment.');
1059
+ contentError('type must be normal, video, audio, attachment, or json-table.');
1059
1060
  }
1060
1061
  for (const field of ['title', 'slug']) {
1061
1062
  if (creating || input[field] !== undefined) {
@@ -1119,6 +1120,389 @@ function validateContentInput(input, creating = false) {
1119
1120
  }
1120
1121
  }
1121
1122
 
1123
+ const dangerousKeys = new Set(['__proto__', 'prototype', 'constructor']);
1124
+ const supportedTypes = new Set([
1125
+ 'string',
1126
+ 'number',
1127
+ 'boolean',
1128
+ 'date',
1129
+ 'json',
1130
+ ]);
1131
+ const maximumColumns = 200;
1132
+ const maximumRows = 10_000;
1133
+ const TAON_SPREADSHEET_MAX_BYTES = 10_000_000;
1134
+ function normalizeSpreadsheetKey(header) {
1135
+ const words = header
1136
+ .normalize('NFKD')
1137
+ .replace(/[\u0300-\u036f]/g, '')
1138
+ .match(/[A-Za-z0-9]+/g);
1139
+ if (!words?.length) {
1140
+ return '';
1141
+ }
1142
+ return words
1143
+ .map((word, index) => index === 0
1144
+ ? word.charAt(0).toLowerCase() + word.slice(1)
1145
+ : word.charAt(0).toUpperCase() + word.slice(1))
1146
+ .join('');
1147
+ }
1148
+ function validateSpreadsheetData(value) {
1149
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1150
+ throw new Error('Table data must be an object.');
1151
+ }
1152
+ const source = value;
1153
+ if (source.version !== 1 ||
1154
+ !Array.isArray(source.columns) ||
1155
+ !Array.isArray(source.rows)) {
1156
+ throw new Error('Table data must use the supported version 1 format.');
1157
+ }
1158
+ if (source.columns.length > maximumColumns) {
1159
+ throw new Error(`A table cannot have more than ${maximumColumns} columns.`);
1160
+ }
1161
+ if (source.rows.length > maximumRows) {
1162
+ throw new Error(`A table cannot have more than ${maximumRows} rows.`);
1163
+ }
1164
+ const keys = new Set();
1165
+ const columns = source.columns.map((column, index) => {
1166
+ if (!column || typeof column !== 'object') {
1167
+ throw new Error(`Column ${index + 1} is invalid.`);
1168
+ }
1169
+ const candidate = column;
1170
+ if (typeof candidate.key !== 'string' ||
1171
+ !/^[a-z][A-Za-z0-9]*$/.test(candidate.key) ||
1172
+ dangerousKeys.has(candidate.key)) {
1173
+ throw new Error(`Column ${index + 1} has an invalid key.`);
1174
+ }
1175
+ if (keys.has(candidate.key)) {
1176
+ throw new Error(`The column key "${candidate.key}" is duplicated.`);
1177
+ }
1178
+ if (!supportedTypes.has(candidate.type)) {
1179
+ throw new Error(`Column "${candidate.key}" has an unsupported type.`);
1180
+ }
1181
+ if (candidate.label !== undefined && typeof candidate.label !== 'string') {
1182
+ throw new Error(`Column "${candidate.key}" has an invalid label.`);
1183
+ }
1184
+ if (candidate.width !== undefined &&
1185
+ (!Number.isFinite(candidate.width) ||
1186
+ candidate.width < 40 ||
1187
+ candidate.width > 1000)) {
1188
+ throw new Error(`Column "${candidate.key}" has an invalid width.`);
1189
+ }
1190
+ keys.add(candidate.key);
1191
+ return {
1192
+ key: candidate.key,
1193
+ ...(candidate.label !== undefined ? { label: candidate.label } : {}),
1194
+ type: candidate.type,
1195
+ ...(candidate.width !== undefined ? { width: candidate.width } : {}),
1196
+ };
1197
+ });
1198
+ const rows = source.rows.map((row, rowIndex) => {
1199
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
1200
+ throw new Error(`Row ${rowIndex + 1} must be an object.`);
1201
+ }
1202
+ const result = {};
1203
+ for (const key of Object.keys(row)) {
1204
+ if (dangerousKeys.has(key)) {
1205
+ throw new Error(`Row ${rowIndex + 1} contains a dangerous key.`);
1206
+ }
1207
+ if (!keys.has(key)) {
1208
+ throw new Error(`Row ${rowIndex + 1} contains unknown column "${key}".`);
1209
+ }
1210
+ const cell = row[key];
1211
+ const column = columns.find(candidate => candidate.key === key);
1212
+ if (cell !== null && cell !== undefined) {
1213
+ const validType = column.type === 'string'
1214
+ ? typeof cell === 'string'
1215
+ : column.type === 'number'
1216
+ ? typeof cell === 'number' && Number.isFinite(cell)
1217
+ : column.type === 'boolean'
1218
+ ? typeof cell === 'boolean'
1219
+ : column.type === 'date'
1220
+ ? typeof cell === 'string' && isValidDateOnly(cell)
1221
+ : true;
1222
+ if (!validType) {
1223
+ throw new Error(`Row ${rowIndex + 1}, column "${key}" does not match type "${column.type}".`);
1224
+ }
1225
+ }
1226
+ if (column.type === 'json' && cell !== null && cell !== undefined) {
1227
+ assertSafeJson(cell, `Row ${rowIndex + 1}, column "${key}"`);
1228
+ }
1229
+ result[key] = cell;
1230
+ }
1231
+ return result;
1232
+ });
1233
+ return { version: 1, columns, rows };
1234
+ }
1235
+ function parseSpreadsheetDocument(body) {
1236
+ if (!body?.trim()) {
1237
+ return { version: 1, columns: [], rows: [] };
1238
+ }
1239
+ let parsed;
1240
+ try {
1241
+ parsed = JSON.parse(body);
1242
+ }
1243
+ catch {
1244
+ throw new Error('The saved JSON Table content is not valid JSON.');
1245
+ }
1246
+ if (parsed &&
1247
+ typeof parsed === 'object' &&
1248
+ !Array.isArray(parsed) &&
1249
+ parsed.version === 1) {
1250
+ return validateSpreadsheetData(parsed);
1251
+ }
1252
+ if (!Array.isArray(parsed)) {
1253
+ throw new Error('JSON Table content must be a versioned table or an array.');
1254
+ }
1255
+ return inferSpreadsheetData(parsed);
1256
+ }
1257
+ function importSpreadsheetJson(text) {
1258
+ let parsed;
1259
+ try {
1260
+ parsed = JSON.parse(text.replace(/^\uFEFF/, ''));
1261
+ }
1262
+ catch {
1263
+ throw new Error('The selected JSON file is not valid JSON.');
1264
+ }
1265
+ if (parsed &&
1266
+ typeof parsed === 'object' &&
1267
+ !Array.isArray(parsed) &&
1268
+ parsed.version === 1) {
1269
+ return validateSpreadsheetData(parsed);
1270
+ }
1271
+ if (!Array.isArray(parsed)) {
1272
+ throw new Error('JSON import must contain an array of row objects.');
1273
+ }
1274
+ return inferSpreadsheetData(parsed);
1275
+ }
1276
+ function inferSpreadsheetData(rows) {
1277
+ if (rows.length > maximumRows) {
1278
+ throw new Error(`A table cannot have more than ${maximumRows} rows.`);
1279
+ }
1280
+ if (rows.some(row => !row || typeof row !== 'object' || Array.isArray(row))) {
1281
+ throw new Error('Every imported row must be an object.');
1282
+ }
1283
+ const headers = [...new Set(rows.flatMap(row => Object.keys(row)))];
1284
+ if (headers.length > maximumColumns) {
1285
+ throw new Error(`A table cannot have more than ${maximumColumns} columns.`);
1286
+ }
1287
+ const usedKeys = new Set();
1288
+ const columns = headers.map(header => {
1289
+ let key = normalizeSpreadsheetKey(header);
1290
+ if (!key || !/^[A-Za-z]/.test(key)) {
1291
+ key = `column${key ? key.charAt(0).toUpperCase() + key.slice(1) : ''}`;
1292
+ }
1293
+ const base = key;
1294
+ let suffix = 2;
1295
+ while (usedKeys.has(key) || dangerousKeys.has(key)) {
1296
+ key = `${base}${suffix++}`;
1297
+ }
1298
+ usedKeys.add(key);
1299
+ const values = rows.map(row => row[header]);
1300
+ return {
1301
+ key,
1302
+ ...(header !== key ? { label: header } : {}),
1303
+ type: inferType(values),
1304
+ };
1305
+ });
1306
+ const keyByHeader = new Map(headers.map((header, index) => [header, columns[index].key]));
1307
+ const normalizedRows = rows.map(row => {
1308
+ const result = {};
1309
+ for (const header of headers) {
1310
+ const key = keyByHeader.get(header);
1311
+ result[key] = row[header] ?? null;
1312
+ }
1313
+ return result;
1314
+ });
1315
+ return validateSpreadsheetData({
1316
+ version: 1,
1317
+ columns,
1318
+ rows: normalizedRows,
1319
+ });
1320
+ }
1321
+ function parseSpreadsheetCsv(text, delimiter) {
1322
+ const source = text.replace(/^\uFEFF/, '');
1323
+ const separator = delimiter ?? detectDelimiter(source);
1324
+ const records = [];
1325
+ let record = [];
1326
+ let field = '';
1327
+ let quoted = false;
1328
+ for (let index = 0; index < source.length; index += 1) {
1329
+ const char = source[index];
1330
+ if (quoted) {
1331
+ if (char === '"' && source[index + 1] === '"') {
1332
+ field += '"';
1333
+ index += 1;
1334
+ }
1335
+ else if (char === '"') {
1336
+ quoted = false;
1337
+ }
1338
+ else {
1339
+ field += char;
1340
+ }
1341
+ }
1342
+ else if (char === '"' && field.length === 0) {
1343
+ quoted = true;
1344
+ }
1345
+ else if (char === separator) {
1346
+ record.push(field);
1347
+ field = '';
1348
+ }
1349
+ else if (char === '\r' || char === '\n') {
1350
+ record.push(field);
1351
+ records.push(record);
1352
+ record = [];
1353
+ field = '';
1354
+ if (char === '\r' && source[index + 1] === '\n') {
1355
+ index += 1;
1356
+ }
1357
+ }
1358
+ else {
1359
+ field += char;
1360
+ }
1361
+ }
1362
+ if (quoted) {
1363
+ throw new Error('The CSV file contains an unterminated quoted value.');
1364
+ }
1365
+ if (field.length || record.length) {
1366
+ record.push(field);
1367
+ records.push(record);
1368
+ }
1369
+ const nonEmptyRecords = records.filter(row => row.some(cell => cell !== ''));
1370
+ if (!nonEmptyRecords.length) {
1371
+ return { version: 1, columns: [], rows: [] };
1372
+ }
1373
+ const headers = nonEmptyRecords[0].map(header => header.trim());
1374
+ if (!headers.length || headers.some(header => !header)) {
1375
+ throw new Error('CSV header cells must not be empty.');
1376
+ }
1377
+ if (new Set(headers).size !== headers.length) {
1378
+ throw new Error('CSV headers must be unique before import.');
1379
+ }
1380
+ const rows = nonEmptyRecords.slice(1).map((cells, rowIndex) => {
1381
+ if (cells.length > headers.length) {
1382
+ throw new Error(`CSV row ${rowIndex + 2} has more cells than the header.`);
1383
+ }
1384
+ const result = {};
1385
+ headers.forEach((header, index) => {
1386
+ result[header] = parseCsvValue(cells[index] ?? '');
1387
+ });
1388
+ return result;
1389
+ });
1390
+ const inferred = inferSpreadsheetData(rows.length
1391
+ ? rows
1392
+ : [Object.fromEntries(headers.map(header => [header, '']))]);
1393
+ return {
1394
+ version: 1,
1395
+ columns: inferred.columns,
1396
+ rows: rows.length ? inferred.rows : [],
1397
+ };
1398
+ }
1399
+ function serializeSpreadsheetCsv(data) {
1400
+ const escape = (value) => {
1401
+ let text = value === null || value === undefined
1402
+ ? ''
1403
+ : typeof value === 'string'
1404
+ ? value
1405
+ : JSON.stringify(value);
1406
+ if (/^[\t\r\n ]*[=+\-@]/.test(text)) {
1407
+ text = `'${text}`;
1408
+ }
1409
+ return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
1410
+ };
1411
+ const header = data.columns.map(column => escape(column.label || column.key));
1412
+ const rows = data.rows.map(row => data.columns.map(column => escape(row[column.key] ?? '')).join(','));
1413
+ return [header.join(','), ...rows].join('\r\n');
1414
+ }
1415
+ function inferType(values) {
1416
+ const present = values.filter(value => value !== null && value !== undefined && value !== '');
1417
+ if (present.length &&
1418
+ present.every(value => typeof value === 'string' && isValidDateOnly(value))) {
1419
+ return 'date';
1420
+ }
1421
+ if (present.length && present.every(value => typeof value === 'boolean')) {
1422
+ return 'boolean';
1423
+ }
1424
+ if (present.length &&
1425
+ present.every(value => typeof value === 'number' && Number.isFinite(value))) {
1426
+ return 'number';
1427
+ }
1428
+ if (present.length && present.every(value => typeof value === 'object')) {
1429
+ return 'json';
1430
+ }
1431
+ return 'string';
1432
+ }
1433
+ function detectDelimiter(text) {
1434
+ const firstLine = text.split(/\r\n|\n|\r/, 1)[0] ?? '';
1435
+ const candidates = [',', ';', '\t'];
1436
+ return candidates.reduce((selected, candidate) => countUnquoted(firstLine, candidate) > countUnquoted(firstLine, selected)
1437
+ ? candidate
1438
+ : selected);
1439
+ }
1440
+ function countUnquoted(line, delimiter) {
1441
+ let quoted = false;
1442
+ let count = 0;
1443
+ for (let index = 0; index < line.length; index += 1) {
1444
+ if (line[index] === '"' && line[index + 1] === '"' && quoted) {
1445
+ index += 1;
1446
+ }
1447
+ else if (line[index] === '"') {
1448
+ quoted = !quoted;
1449
+ }
1450
+ else if (line[index] === delimiter && !quoted) {
1451
+ count += 1;
1452
+ }
1453
+ }
1454
+ return count;
1455
+ }
1456
+ function parseCsvValue(value) {
1457
+ if (!value) {
1458
+ return null;
1459
+ }
1460
+ if (/^(true|false)$/i.test(value)) {
1461
+ return value.toLowerCase() === 'true';
1462
+ }
1463
+ if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) {
1464
+ const number = Number(value);
1465
+ if (Number.isFinite(number)) {
1466
+ return number;
1467
+ }
1468
+ }
1469
+ return value;
1470
+ }
1471
+ function isValidDateOnly(value) {
1472
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
1473
+ return false;
1474
+ }
1475
+ const date = new Date(`${value}T00:00:00.000Z`);
1476
+ return (!Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value);
1477
+ }
1478
+ function assertSafeJson(value, path) {
1479
+ if (value === null ||
1480
+ typeof value === 'string' ||
1481
+ typeof value === 'boolean') {
1482
+ return;
1483
+ }
1484
+ if (typeof value === 'number') {
1485
+ if (!Number.isFinite(value)) {
1486
+ throw new Error(`${path} contains a non-finite number.`);
1487
+ }
1488
+ return;
1489
+ }
1490
+ if (Array.isArray(value)) {
1491
+ value.forEach((item, index) => assertSafeJson(item, `${path}[${index}]`));
1492
+ return;
1493
+ }
1494
+ if (typeof value === 'object') {
1495
+ for (const [key, nestedValue] of Object.entries(value)) {
1496
+ if (dangerousKeys.has(key)) {
1497
+ throw new Error(`${path} contains a dangerous JSON key.`);
1498
+ }
1499
+ assertSafeJson(nestedValue, `${path}.${key}`);
1500
+ }
1501
+ return;
1502
+ }
1503
+ throw new Error(`${path} contains a value that cannot be serialized as JSON.`);
1504
+ }
1505
+
1122
1506
  //#endregion
1123
1507
  let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRepository {
1124
1508
  constructor() {
@@ -2460,6 +2844,21 @@ let TaonCmsContentRepository = class TaonCmsContentRepository extends TaonBaseRe
2460
2844
  if (title === undefined || slug === undefined) {
2461
2845
  contentError('title and slug are required.');
2462
2846
  }
2847
+ if (type === TaonCmsContentType.JsonTable) {
2848
+ try {
2849
+ const body = input.body === undefined ? (current?.body ?? null) : input.body;
2850
+ if (typeof body === 'string' && body.length > TAON_SPREADSHEET_MAX_BYTES) {
2851
+ contentError('JSON Table content exceeds the 10 MB limit.');
2852
+ }
2853
+ parseSpreadsheetDocument(body);
2854
+ }
2855
+ catch (error) {
2856
+ if (error instanceof Error) {
2857
+ contentError(`JSON Table content is invalid: ${error.message}`);
2858
+ }
2859
+ throw error;
2860
+ }
2861
+ }
2463
2862
  const publishedAt = input.publishedAt === undefined
2464
2863
  ? (current?.publishedAt ?? null)
2465
2864
  : input.publishedAt;
@@ -3825,6 +4224,10 @@ let TaonCmsContentStorageController = class TaonCmsContentStorageController exte
3825
4224
  /* */
3826
4225
  /* */
3827
4226
  /* */
4227
+ /* */
4228
+ /* */
4229
+ /* */
4230
+ /* */
3828
4231
  return (void 0);
3829
4232
  }
3830
4233
  async handleUploadFiles(files, queryParams, req, res) {
@@ -3865,6 +4268,50 @@ let TaonCmsContentStorageController = class TaonCmsContentStorageController exte
3865
4268
  /* */
3866
4269
  return (void 0);
3867
4270
  }
4271
+ /**
4272
+ * Access is checked in beforeEachRequest (CMS managers only).
4273
+ * The declared size is fixed at start and enforced for every part.
4274
+ */
4275
+ async authorizeMultipartUpload(options) {
4276
+ /* */
4277
+ /* */
4278
+ /* */
4279
+ /* */
4280
+ /* */
4281
+ /* */
4282
+ /* */
4283
+ /* */
4284
+ /* */
4285
+ /* */
4286
+ /* */
4287
+ return (void 0);
4288
+ }
4289
+ async handleCompletedMultipartUpload(object, queryParams, req) {
4290
+ /* */
4291
+ /* */
4292
+ /* */
4293
+ /* */
4294
+ /* */
4295
+ return (void 0);
4296
+ }
4297
+ async withDownloadUrl(object, req) {
4298
+ /* */
4299
+ /* */
4300
+ /* */
4301
+ /* */
4302
+ /* */
4303
+ /* */
4304
+ /* */
4305
+ /* */
4306
+ /* */
4307
+ /* */
4308
+ /* */
4309
+ /* */
4310
+ /* */
4311
+ /* */
4312
+ /* */
4313
+ return (void 0);
4314
+ }
3868
4315
  afterFileUploadHook() { }
3869
4316
  async isMediaObjectAvailable(key) {
3870
4317
  /* */
@@ -4071,6 +4518,8 @@ let TaonCmsContentController = class TaonCmsContentController extends TaonBaseCr
4071
4518
  /* */
4072
4519
  /* */
4073
4520
  /* */
4521
+ /* */
4522
+ /* */
4074
4523
  return (void 0);
4075
4524
  }
4076
4525
  //#endregion
@@ -4251,6 +4700,62 @@ let TaonCmsContentController = class TaonCmsContentController extends TaonBaseCr
4251
4700
  return (void 0);
4252
4701
  }
4253
4702
  //#endregion
4703
+ getJsonTableBySlug(slug) {
4704
+ /* */
4705
+ /* */
4706
+ /* */
4707
+ /* */
4708
+ /* */
4709
+ return (void 0);
4710
+ }
4711
+ getPublicJsonTableBySlug(slug) {
4712
+ /* */
4713
+ /* */
4714
+ /* */
4715
+ /* */
4716
+ /* */
4717
+ return (void 0);
4718
+ }
4719
+ async requireJsonTablePost(slug, req, t, publicOnly) {
4720
+ /* */
4721
+ /* */
4722
+ /* */
4723
+ /* */
4724
+ /* */
4725
+ /* */
4726
+ /* */
4727
+ /* */
4728
+ /* */
4729
+ /* */
4730
+ /* */
4731
+ /* */
4732
+ /* */
4733
+ /* */
4734
+ /* */
4735
+ /* */
4736
+ /* */
4737
+ return (void 0);
4738
+ }
4739
+ jsonTableRows(body, t) {
4740
+ /* */
4741
+ /* */
4742
+ /* */
4743
+ /* */
4744
+ /* */
4745
+ /* */
4746
+ /* */
4747
+ /* */
4748
+ /* */
4749
+ /* */
4750
+ /* */
4751
+ /* */
4752
+ /* */
4753
+ /* */
4754
+ /* */
4755
+ /* */
4756
+ /* */
4757
+ return (void 0);
4758
+ }
4254
4759
  getPublicBySlug(slug) {
4255
4760
  /* */
4256
4761
  /* */
@@ -4655,6 +5160,20 @@ __decorate([
4655
5160
  __metadata("design:paramtypes", [String]),
4656
5161
  __metadata("design:returntype", Object)
4657
5162
  ], TaonCmsContentController.prototype, "getBySlug", null);
5163
+ __decorate([
5164
+ GET(),
5165
+ __param(0, Query('slug')),
5166
+ __metadata("design:type", Function),
5167
+ __metadata("design:paramtypes", [String]),
5168
+ __metadata("design:returntype", Object)
5169
+ ], TaonCmsContentController.prototype, "getJsonTableBySlug", null);
5170
+ __decorate([
5171
+ GET(),
5172
+ __param(0, Query('slug')),
5173
+ __metadata("design:type", Function),
5174
+ __metadata("design:paramtypes", [String]),
5175
+ __metadata("design:returntype", Object)
5176
+ ], TaonCmsContentController.prototype, "getPublicJsonTableBySlug", null);
4658
5177
  __decorate([
4659
5178
  GET(),
4660
5179
  __param(0, Query('slug')),
@@ -4837,6 +5356,8 @@ TaonCmsContentController = __decorate([
4837
5356
  allowedMethods: [
4838
5357
  'getAll',
4839
5358
  'getBy',
5359
+ 'getJsonTableBySlug',
5360
+ 'getPublicJsonTableBySlug',
4840
5361
  'getPublicBySlug',
4841
5362
  'getPublicVideoChildrenBySlug',
4842
5363
  'createContent',
@@ -4887,6 +5408,13 @@ TaonCmsContentMiddleware = __decorate([
4887
5408
 
4888
5409
  //#endregion
4889
5410
  let TaonCmsContentProvider = class TaonCmsContentProvider extends TaonBaseProvider {
5411
+ constructor() {
5412
+ super(...arguments);
5413
+ /**
5414
+ * Slugs offered in the post editor "Required Slugs" selector.
5415
+ */
5416
+ this.requiredSlugs = [];
5417
+ }
4890
5418
  async _() {
4891
5419
  if (!this.siteName) {
4892
5420
  throw new Error(`Please provide siteName in TaonCmsContentProvider`);
@@ -5257,80 +5785,20 @@ const TaonCmsAbstractContext = createContext(() => ({
5257
5785
  }));
5258
5786
  // THIS FILE IS GENERATED - DO NOT MODIFY
5259
5787
 
5260
- const progressCallbacks = new WeakMap();
5261
- // Keep Taon's middleware, retries and response decoding; replace only the
5262
- // final Fetch transport for uploads that request browser upload events.
5263
- function trackCmsUpload(url, body, onProgress) {
5264
- const key = `cms-upload-progress-POST-${new URL(url).pathname}`;
5265
- if (!Resource__NS__methodsInterceptors.has(key)) {
5266
- Resource__NS__methodsInterceptors.set(key, {
5267
- intercept: ({ req, next }) => {
5268
- if (!(req.body instanceof FormData)) {
5269
- return next.handle(req);
5270
- }
5271
- const uploadBody = req.body;
5272
- const callback = progressCallbacks.get(uploadBody);
5273
- if (!callback) {
5274
- return next.handle(req);
5275
- }
5276
- return new Observable(subscriber => {
5277
- const xhr = new XMLHttpRequest();
5278
- xhr.open(req.method || 'POST', req.url);
5279
- xhr.withCredentials = req.credentials === 'include';
5280
- new Headers(req.headers).forEach((value, name) => {
5281
- // The browser must supply the multipart boundary.
5282
- if (name.toLowerCase() !== 'content-type') {
5283
- xhr.setRequestHeader(name, value);
5284
- }
5285
- });
5286
- xhr.upload.onprogress = event => {
5287
- if (event.lengthComputable && event.total > 0) {
5288
- callback(Math.min(100, Math.floor((event.loaded * 100) / event.total)));
5289
- }
5290
- };
5291
- xhr.onload = () => {
5292
- const headers = new Headers();
5293
- for (const line of xhr
5294
- .getAllResponseHeaders()
5295
- .trim()
5296
- .split(/[\r\n]+/)) {
5297
- const separator = line.indexOf(':');
5298
- if (separator > 0) {
5299
- headers.append(line.slice(0, separator), line.slice(separator + 1).trim());
5300
- }
5301
- }
5302
- subscriber.next(new Response([204, 205, 304].includes(xhr.status) ? null : xhr.responseText, {
5303
- status: xhr.status,
5304
- statusText: xhr.statusText,
5305
- headers,
5306
- }));
5307
- subscriber.complete();
5308
- };
5309
- xhr.onerror = () => subscriber.error(new Error('Media upload network error.'));
5310
- xhr.ontimeout = () => subscriber.error(new Error('Media upload timed out.'));
5311
- xhr.onabort = () => subscriber.error(new Error('Media upload was cancelled.'));
5312
- const abort = () => xhr.abort();
5313
- req.signal?.addEventListener('abort', abort, { once: true });
5314
- if (req.signal?.aborted) {
5315
- subscriber.error(new Error('Media upload was cancelled.'));
5316
- }
5317
- else {
5318
- callback(0);
5319
- xhr.send(uploadBody);
5320
- }
5321
- return () => {
5322
- req.signal?.removeEventListener('abort', abort);
5323
- if (xhr.readyState !== XMLHttpRequest.DONE) {
5324
- xhr.abort();
5325
- }
5326
- };
5327
- });
5328
- },
5329
- });
5330
- }
5331
- progressCallbacks.set(body, onProgress);
5332
- return () => progressCallbacks.delete(body);
5333
- }
5788
+ const TaonCmsContentDefaultsValues = {
5789
+ description: '',
5790
+ version: 0,
5791
+ id: void 0,
5792
+ type: TaonCmsContentType.Normal,
5793
+ videoKey: null,
5794
+ audioKey: null,
5795
+ attachmentKey: null,
5796
+ thumbnailKey: null,
5797
+ };
5798
+ /**
5799
+ * Strict maximum size of a single CMS media upload (1 GB).
5800
+ */
5801
+ const TAON_CMS_MAX_UPLOAD_BYTES = 1_000_000_000;
5334
5802
 
5335
5803
  //#region imports
5336
5804
  //#endregion
@@ -5363,23 +5831,33 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5363
5831
  }
5364
5832
  //#endregion
5365
5833
  async getPublicBySlug(slug) {
5366
- const response = await this.taonCmsContentController
5367
- .getPublicBySlug(slug)
5368
- .request();
5834
+ const response = await this.taonCmsContentController.getPublicBySlug(slug).request();
5369
5835
  if (response.statusCode >= 400 || !response.body?.json) {
5370
5836
  throw new Error(response.responseText || 'The post could not be loaded.');
5371
5837
  }
5372
5838
  return response.body.json;
5373
5839
  }
5374
- async getPublicVideoChildrenBySlug(slug) {
5375
- const response = await this.taonCmsContentController
5376
- .getPublicVideoChildrenBySlug(slug)
5377
- .request();
5840
+ async getJsonTableBySlug(slug) {
5841
+ const response = await this.taonCmsContentController.getJsonTableBySlug(slug).request();
5378
5842
  if (response.statusCode >= 400 || !Array.isArray(response.body?.json)) {
5379
- throw new Error(response.responseText || 'Public video children could not be loaded.');
5843
+ throw new Error(response.responseText || 'The JSON Table could not be loaded.');
5380
5844
  }
5381
5845
  return response.body.json;
5382
5846
  }
5847
+ getPublicJsonTableBySlug(slug) {
5848
+ return this.taonCmsContentController.getPublicJsonTableBySlug(slug)
5849
+ .request().observable.pipe(map(c => c.body.json), catchError(error => {
5850
+ console.error(`Failed to load JSON table "${slug}":`, error);
5851
+ return of([]);
5852
+ }));
5853
+ }
5854
+ getPublicVideoChildrenBySlug(slug) {
5855
+ return this.taonCmsContentController.getPublicVideoChildrenBySlug(slug)
5856
+ .request().observable.pipe(map(c => c.body.json), catchError(error => {
5857
+ console.error(`Failed to load public video children for "${slug}":`, error);
5858
+ return of([]);
5859
+ }));
5860
+ }
5383
5861
  //#region API / get video link by slug
5384
5862
  /**
5385
5863
  * Get link to private video
@@ -5442,7 +5920,7 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5442
5920
  //#region API / get public content by hook
5443
5921
  getPublicContentByHook(hook) {
5444
5922
  return this.taonCmsContentController.getPublicContentByHook(hook)
5445
- .request().observable.pipe(map(res => res.body.text), catchError(() => of('---')));
5923
+ .request().observable.pipe(map(res => (res.body.text || '')?.replace(/\\n/g, '\n')), catchError(() => of('---')));
5446
5924
  }
5447
5925
  //#endregion
5448
5926
  //#region API / list application hooks
@@ -5452,26 +5930,21 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5452
5930
  }
5453
5931
  //#endregion
5454
5932
  //#region API / upload media
5455
- async uploadMedia(file, onProgress) {
5456
- const formData = new FormData();
5457
- formData.append('file', file);
5458
- const stopTracking = onProgress
5459
- ? trackCmsUpload(this.taonCmsContentStorageController.getThisControllerMethodFullUrl('uploadFormDataToServer'), formData, onProgress)
5460
- : undefined;
5461
- try {
5462
- const response = await this.taonCmsContentStorageController
5463
- .uploadFormDataToServer(formData)
5464
- .request();
5465
- const objects = response.body.json;
5466
- if (!Array.isArray(objects) || objects.length !== 1 || !objects[0]?.key) {
5467
- throw new Error('The upload did not return a media key.');
5468
- }
5469
- onProgress?.(100);
5470
- return objects[0];
5471
- }
5472
- finally {
5473
- stopTracking?.();
5933
+ async uploadMedia(file, onProgress, signal) {
5934
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
5935
+ throw new Error(`The file "${file.name}" is too large. The maximum upload size is 1 GB.`);
5936
+ }
5937
+ const objects = await this.taonCmsContentStorageController.uploadFileInChunks(file, {
5938
+ fileName: file.name,
5939
+ contentType: file.type || 'application/octet-stream',
5940
+ signal,
5941
+ onProgress,
5942
+ });
5943
+ if (!Array.isArray(objects) || objects.length !== 1 || !objects[0]?.key) {
5944
+ throw new Error('The upload did not return a media key.');
5474
5945
  }
5946
+ onProgress?.(100);
5947
+ return objects[0];
5475
5948
  }
5476
5949
  //#endregion
5477
5950
  async deleteUnattachedMedia(key) {
@@ -5540,10 +6013,7 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5540
6013
  }
5541
6014
  //#endregion
5542
6015
  permanentlyDeleteContent(id, expectedVersion) {
5543
- return this.taonCmsContentController
5544
- .permanentlyDeleteContent(id, expectedVersion)
5545
- .request()
5546
- .observable.pipe(map(res => res.body.json));
6016
+ return this.taonCmsContentController.permanentlyDeleteContent(id, expectedVersion).request().observable.pipe(map(res => res.body.json));
5547
6017
  }
5548
6018
  //#region API / restore content
5549
6019
  restoreContent(id, input) {
@@ -5568,7 +6038,8 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5568
6038
  }
5569
6039
  //#endregion
5570
6040
  async getParentPost(id) {
5571
- return (await this.taonCmsContentController.getParentPost(id).request()).body.json;
6041
+ return (await this.taonCmsContentController.getParentPost(id).request())
6042
+ .body.json;
5572
6043
  }
5573
6044
  async getVisibilityConfiguration(id) {
5574
6045
  return (await this.taonCmsContentController
@@ -5576,7 +6047,8 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5576
6047
  .request()).body.json;
5577
6048
  }
5578
6049
  async getPostVersions(id) {
5579
- return (await this.taonCmsContentController.getPostVersions(id).request()).body.json;
6050
+ return (await this.taonCmsContentController.getPostVersions(id).request())
6051
+ .body.json;
5580
6052
  }
5581
6053
  //#region API / list permissions
5582
6054
  listPermissions(id) {
@@ -5601,16 +6073,10 @@ class TaonCmsContentApiService extends TaonBaseAngularService {
5601
6073
  }
5602
6074
  //#endregion
5603
6075
  linkVersionPost(id, versionPostId, kind, expectedVersion) {
5604
- return this.taonCmsContentController
5605
- .linkVersionPost(id, versionPostId, kind, expectedVersion)
5606
- .request()
5607
- .observable.pipe(map(res => res.body.json));
6076
+ return this.taonCmsContentController.linkVersionPost(id, versionPostId, kind, expectedVersion).request().observable.pipe(map(res => res.body.json));
5608
6077
  }
5609
6078
  removeVersionPost(id, kind, expectedVersion) {
5610
- return this.taonCmsContentController
5611
- .removeVersionPost(id, kind, expectedVersion)
5612
- .request()
5613
- .observable.pipe(map(res => res.body.json));
6079
+ return this.taonCmsContentController.removeVersionPost(id, kind, expectedVersion).request().observable.pipe(map(res => res.body.json));
5614
6080
  }
5615
6081
  //#region API / get all entites
5616
6082
  /**
@@ -6203,6 +6669,13 @@ class TaonCmsContentConfigService extends TaonBaseAngularService {
6203
6669
  get siteName() {
6204
6670
  return this.taonCmsContentProvider.siteName;
6205
6671
  }
6672
+ get requiredSlugs() {
6673
+ return [
6674
+ ...new Set((this.taonCmsContentProvider.requiredSlugs ?? [])
6675
+ .map(slug => String(slug ?? '').trim())
6676
+ .filter(Boolean)),
6677
+ ];
6678
+ }
6206
6679
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
6207
6680
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService }); }
6208
6681
  }
@@ -6210,17 +6683,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
6210
6683
  type: Injectable
6211
6684
  }] });
6212
6685
 
6213
- const TaonCmsContentDefaultsValues = {
6214
- description: '',
6215
- version: 0,
6216
- id: void 0,
6217
- type: TaonCmsContentType.Normal,
6218
- videoKey: null,
6219
- audioKey: null,
6220
- attachmentKey: null,
6221
- thumbnailKey: null,
6222
- };
6223
-
6224
6686
  //namespace TaonCmsContentUtils
6225
6687
  function TaonCmsContentUtils__NS__orderRelatedPosts(posts, ids) {
6226
6688
  const positions = new Map(ids.map((id, index) => [id, index]));
@@ -6260,6 +6722,81 @@ function resolveTemplateVariables(content, context) {
6260
6722
  });
6261
6723
  }
6262
6724
 
6725
+ const progressCallbacks = new WeakMap();
6726
+ // Keep Taon's middleware, retries and response decoding; replace only the
6727
+ // final Fetch transport for uploads that request browser upload events.
6728
+ function trackCmsUpload(url, body, onProgress) {
6729
+ const key = `cms-upload-progress-POST-${new URL(url).pathname}`;
6730
+ if (!Resource__NS__methodsInterceptors.has(key)) {
6731
+ Resource__NS__methodsInterceptors.set(key, {
6732
+ intercept: ({ req, next }) => {
6733
+ if (!(req.body instanceof FormData)) {
6734
+ return next.handle(req);
6735
+ }
6736
+ const uploadBody = req.body;
6737
+ const callback = progressCallbacks.get(uploadBody);
6738
+ if (!callback) {
6739
+ return next.handle(req);
6740
+ }
6741
+ return new Observable(subscriber => {
6742
+ const xhr = new XMLHttpRequest();
6743
+ xhr.open(req.method || 'POST', req.url);
6744
+ xhr.withCredentials = req.credentials === 'include';
6745
+ new Headers(req.headers).forEach((value, name) => {
6746
+ // The browser must supply the multipart boundary.
6747
+ if (name.toLowerCase() !== 'content-type') {
6748
+ xhr.setRequestHeader(name, value);
6749
+ }
6750
+ });
6751
+ xhr.upload.onprogress = event => {
6752
+ if (event.lengthComputable && event.total > 0) {
6753
+ callback(Math.min(100, Math.floor((event.loaded * 100) / event.total)));
6754
+ }
6755
+ };
6756
+ xhr.onload = () => {
6757
+ const headers = new Headers();
6758
+ for (const line of xhr
6759
+ .getAllResponseHeaders()
6760
+ .trim()
6761
+ .split(/[\r\n]+/)) {
6762
+ const separator = line.indexOf(':');
6763
+ if (separator > 0) {
6764
+ headers.append(line.slice(0, separator), line.slice(separator + 1).trim());
6765
+ }
6766
+ }
6767
+ subscriber.next(new Response([204, 205, 304].includes(xhr.status) ? null : xhr.responseText, {
6768
+ status: xhr.status,
6769
+ statusText: xhr.statusText,
6770
+ headers,
6771
+ }));
6772
+ subscriber.complete();
6773
+ };
6774
+ xhr.onerror = () => subscriber.error(new Error('Media upload network error.'));
6775
+ xhr.ontimeout = () => subscriber.error(new Error('Media upload timed out.'));
6776
+ xhr.onabort = () => subscriber.error(new Error('Media upload was cancelled.'));
6777
+ const abort = () => xhr.abort();
6778
+ req.signal?.addEventListener('abort', abort, { once: true });
6779
+ if (req.signal?.aborted) {
6780
+ subscriber.error(new Error('Media upload was cancelled.'));
6781
+ }
6782
+ else {
6783
+ callback(0);
6784
+ xhr.send(uploadBody);
6785
+ }
6786
+ return () => {
6787
+ req.signal?.removeEventListener('abort', abort);
6788
+ if (xhr.readyState !== XMLHttpRequest.DONE) {
6789
+ xhr.abort();
6790
+ }
6791
+ };
6792
+ });
6793
+ },
6794
+ });
6795
+ }
6796
+ progressCallbacks.set(body, onProgress);
6797
+ return () => progressCallbacks.delete(body);
6798
+ }
6799
+
6263
6800
  class TaonCmsContentAssetApiService extends TaonBaseAngularService {
6264
6801
  constructor() {
6265
6802
  super(...arguments);
@@ -6838,6 +7375,606 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
6838
7375
  }]
6839
7376
  }] });
6840
7377
 
7378
+ class TaonSpreadsheetTableComponent {
7379
+ constructor() {
7380
+ this.data = model({
7381
+ version: 1,
7382
+ columns: [],
7383
+ rows: [],
7384
+ }, ...(ngDevMode ? [{ debugName: "data" }] : []));
7385
+ this.readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
7386
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
7387
+ this.validationError = model('', ...(ngDevMode ? [{ debugName: "validationError" }] : []));
7388
+ this.view = signal('table', ...(ngDevMode ? [{ debugName: "view" }] : []));
7389
+ this.activeCell = signal(null, ...(ngDevMode ? [{ debugName: "activeCell" }] : []));
7390
+ this.pendingImport = signal(null, ...(ngDevMode ? [{ debugName: "pendingImport" }] : []));
7391
+ this.importFormat = signal('', ...(ngDevMode ? [{ debugName: "importFormat" }] : []));
7392
+ this.importMode = signal('replace', ...(ngDevMode ? [{ debugName: "importMode" }] : []));
7393
+ this.columnTypes = [
7394
+ 'string',
7395
+ 'number',
7396
+ 'boolean',
7397
+ 'date',
7398
+ 'json',
7399
+ ];
7400
+ this.jsonPreview = computed(() => JSON.stringify(this.data().rows, null, 2), ...(ngDevMode ? [{ debugName: "jsonPreview" }] : []));
7401
+ this.undoStack = [];
7402
+ this.redoStack = [];
7403
+ }
7404
+ addColumn() {
7405
+ if (!this.canEdit()) {
7406
+ return;
7407
+ }
7408
+ const data = this.data();
7409
+ let number = data.columns.length + 1;
7410
+ let key = `column${number}`;
7411
+ while (data.columns.some(column => column.key === key)) {
7412
+ number += 1;
7413
+ key = `column${number}`;
7414
+ }
7415
+ this.commit({
7416
+ ...data,
7417
+ columns: [
7418
+ ...data.columns,
7419
+ { key, label: `Column ${number}`, type: 'string' },
7420
+ ],
7421
+ rows: data.rows.map(row => ({ ...row, [key]: null })),
7422
+ });
7423
+ }
7424
+ addRow() {
7425
+ if (!this.canEdit()) {
7426
+ return;
7427
+ }
7428
+ const row = {};
7429
+ for (const column of this.data().columns) {
7430
+ row[column.key] = null;
7431
+ }
7432
+ this.commit({ ...this.data(), rows: [...this.data().rows, row] });
7433
+ }
7434
+ deleteRow(rowIndex) {
7435
+ if (!this.canEdit() ||
7436
+ !globalThis.confirm('Delete this row? This cannot be undone.')) {
7437
+ return;
7438
+ }
7439
+ this.commit({
7440
+ ...this.data(),
7441
+ rows: this.data().rows.filter((_, index) => index !== rowIndex),
7442
+ });
7443
+ }
7444
+ duplicateRow(rowIndex) {
7445
+ if (!this.canEdit()) {
7446
+ return;
7447
+ }
7448
+ const rows = [...this.data().rows];
7449
+ rows.splice(rowIndex + 1, 0, { ...rows[rowIndex] });
7450
+ this.commit({ ...this.data(), rows });
7451
+ }
7452
+ deleteColumn(columnIndex) {
7453
+ if (!this.canEdit() ||
7454
+ !globalThis.confirm('Delete this column and its cell values? This cannot be undone.')) {
7455
+ return;
7456
+ }
7457
+ const column = this.data().columns[columnIndex];
7458
+ const rows = this.data().rows.map(row => {
7459
+ const { [column.key]: _removed, ...remaining } = row;
7460
+ return remaining;
7461
+ });
7462
+ this.commit({
7463
+ ...this.data(),
7464
+ columns: this.data().columns.filter((_, index) => index !== columnIndex),
7465
+ rows,
7466
+ });
7467
+ }
7468
+ renameColumn(columnIndex, field, value) {
7469
+ if (!this.canEdit()) {
7470
+ return;
7471
+ }
7472
+ const columns = this.data().columns.map(column => ({ ...column }));
7473
+ const previousKey = columns[columnIndex].key;
7474
+ const nextValue = field === 'key' ? value.trim() : value;
7475
+ const nextKey = field === 'key' ? nextValue : previousKey;
7476
+ if (field === 'key' &&
7477
+ (!/^[a-z][A-Za-z0-9]*$/.test(nextKey) ||
7478
+ ['__proto__', 'prototype', 'constructor'].includes(nextKey) ||
7479
+ columns.some((column, index) => index !== columnIndex && column.key === nextKey))) {
7480
+ this.validationError.set('Column keys must be unique, safe camelCase identifiers.');
7481
+ return;
7482
+ }
7483
+ columns[columnIndex] = { ...columns[columnIndex], [field]: nextValue };
7484
+ const rows = field === 'key' && previousKey !== nextKey
7485
+ ? this.data().rows.map(row => {
7486
+ const { [previousKey]: value, ...remaining } = row;
7487
+ return { ...remaining, [nextKey]: value };
7488
+ })
7489
+ : this.data().rows;
7490
+ this.commit({ ...this.data(), columns, rows });
7491
+ }
7492
+ setColumnType(columnIndex, type) {
7493
+ if (!this.canEdit()) {
7494
+ return;
7495
+ }
7496
+ const columns = this.data().columns.map(column => ({ ...column }));
7497
+ columns[columnIndex] = { ...columns[columnIndex], type };
7498
+ this.commit({ ...this.data(), columns });
7499
+ }
7500
+ setCell(rowIndex, column, event) {
7501
+ if (!this.canEdit()) {
7502
+ return;
7503
+ }
7504
+ const target = event.target;
7505
+ let value = target.value;
7506
+ if (column.type === 'number') {
7507
+ value = target.value === '' ? null : Number(target.value);
7508
+ if (value !== null && !Number.isFinite(value)) {
7509
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter a valid number.`);
7510
+ return;
7511
+ }
7512
+ }
7513
+ else if (column.type === 'boolean') {
7514
+ value = target.checked;
7515
+ }
7516
+ else if (column.type === 'date') {
7517
+ value = target.value
7518
+ ? new Date(`${target.value}T00:00:00.000Z`).toISOString().slice(0, 10)
7519
+ : null;
7520
+ }
7521
+ else if (column.type === 'json') {
7522
+ if (!target.value.trim()) {
7523
+ value = null;
7524
+ }
7525
+ else {
7526
+ try {
7527
+ value = JSON.parse(target.value);
7528
+ }
7529
+ catch {
7530
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter valid JSON.`);
7531
+ return;
7532
+ }
7533
+ }
7534
+ }
7535
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: value } : row);
7536
+ this.commit({ ...this.data(), rows });
7537
+ }
7538
+ onCellKeydown(event, row, column) {
7539
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
7540
+ const target = event.target;
7541
+ if (target.tagName === 'TEXTAREA') {
7542
+ return;
7543
+ }
7544
+ if (target instanceof HTMLInputElement &&
7545
+ target.type !== 'checkbox' &&
7546
+ (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
7547
+ const atBoundary = event.key === 'ArrowLeft'
7548
+ ? target.selectionStart === 0
7549
+ : target.selectionStart === target.value.length;
7550
+ if (!atBoundary) {
7551
+ return;
7552
+ }
7553
+ }
7554
+ event.preventDefault();
7555
+ const next = {
7556
+ row: row +
7557
+ (event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0),
7558
+ column: column +
7559
+ (event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0),
7560
+ };
7561
+ this.focusCell(next.row, next.column);
7562
+ return;
7563
+ }
7564
+ if (event.target.tagName === 'TEXTAREA' &&
7565
+ event.key === 'Enter' &&
7566
+ !(event.ctrlKey || event.metaKey)) {
7567
+ return;
7568
+ }
7569
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'z') {
7570
+ event.preventDefault();
7571
+ event.shiftKey ? this.redo() : this.undo();
7572
+ return;
7573
+ }
7574
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'y') {
7575
+ event.preventDefault();
7576
+ this.redo();
7577
+ return;
7578
+ }
7579
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') {
7580
+ event.preventDefault();
7581
+ const value = this.cellValue(row, this.data().columns[column]);
7582
+ void (navigator.clipboard?.writeText(value) ??
7583
+ Promise.reject(new Error('Clipboard access is unavailable.'))).catch(() => {
7584
+ this.validationError.set('Could not access the clipboard.');
7585
+ });
7586
+ return;
7587
+ }
7588
+ if (event.key === 'Escape') {
7589
+ event.target.blur();
7590
+ return;
7591
+ }
7592
+ if (event.key === 'Tab' || event.key === 'Enter') {
7593
+ event.preventDefault();
7594
+ const direction = event.shiftKey ? -1 : 1;
7595
+ let next = { row, column };
7596
+ if (event.key === 'Tab') {
7597
+ let nextColumn = column + direction;
7598
+ let nextRow = row;
7599
+ if (nextColumn >= this.data().columns.length) {
7600
+ nextColumn = 0;
7601
+ nextRow += 1;
7602
+ }
7603
+ else if (nextColumn < 0) {
7604
+ nextColumn = this.data().columns.length - 1;
7605
+ nextRow -= 1;
7606
+ }
7607
+ next = { row: nextRow, column: nextColumn };
7608
+ }
7609
+ else {
7610
+ next = { row: row + direction, column };
7611
+ }
7612
+ if (event.key === 'Tab' &&
7613
+ !event.shiftKey &&
7614
+ next.row === this.data().rows.length) {
7615
+ this.addRow();
7616
+ }
7617
+ this.activeCell.set(next);
7618
+ this.focusCell(next.row, next.column);
7619
+ }
7620
+ else if (event.key === 'Backspace' || event.key === 'Delete') {
7621
+ if (!['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
7622
+ event.preventDefault();
7623
+ this.clearCell(row, column);
7624
+ }
7625
+ }
7626
+ }
7627
+ onCellPaste(event, rowIndex, columnIndex) {
7628
+ if (!this.canEdit()) {
7629
+ return;
7630
+ }
7631
+ const text = event.clipboardData?.getData('text/plain');
7632
+ if (!text || (!text.includes('\t') && !/[\r\n]/.test(text))) {
7633
+ return;
7634
+ }
7635
+ event.preventDefault();
7636
+ try {
7637
+ const matrix = parseClipboardMatrix(text);
7638
+ const columns = [...this.data().columns];
7639
+ const pastedWidth = Math.max(...matrix.map(row => row.length));
7640
+ while (columns.length < columnIndex + pastedWidth &&
7641
+ columns.length < 200) {
7642
+ let number = columns.length + 1;
7643
+ let key = `column${number}`;
7644
+ while (columns.some(column => column.key === key)) {
7645
+ key = `column${++number}`;
7646
+ }
7647
+ columns.push({ key, label: `Column ${number}`, type: 'string' });
7648
+ }
7649
+ if (columns.length < columnIndex + pastedWidth) {
7650
+ throw new Error('Pasted data exceeds the 200-column table limit.');
7651
+ }
7652
+ const rows = [...this.data().rows];
7653
+ while (rows.length < rowIndex + matrix.length && rows.length < 10_000) {
7654
+ const newRow = {};
7655
+ for (const column of columns) {
7656
+ newRow[column.key] = null;
7657
+ }
7658
+ rows.push(newRow);
7659
+ }
7660
+ if (rows.length < rowIndex + matrix.length) {
7661
+ throw new Error('Pasted data exceeds the 10,000-row table limit.');
7662
+ }
7663
+ matrix.forEach((values, pastedRowIndex) => {
7664
+ values.forEach((rawValue, pastedColumnIndex) => {
7665
+ const targetColumn = columnIndex + pastedColumnIndex;
7666
+ const column = columns[targetColumn];
7667
+ rows[rowIndex + pastedRowIndex] = {
7668
+ ...rows[rowIndex + pastedRowIndex],
7669
+ [column.key]: parsePastedCell(rawValue, column),
7670
+ };
7671
+ });
7672
+ });
7673
+ this.commit({ version: 1, columns, rows });
7674
+ }
7675
+ catch (error) {
7676
+ this.validationError.set(error instanceof Error ? error.message : 'The pasted data is invalid.');
7677
+ }
7678
+ }
7679
+ onRowDrop(event) {
7680
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7681
+ return;
7682
+ }
7683
+ const rows = [...this.data().rows];
7684
+ moveItemInArray(rows, event.previousIndex, event.currentIndex);
7685
+ this.commit({ ...this.data(), rows });
7686
+ }
7687
+ onColumnDrop(event) {
7688
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7689
+ return;
7690
+ }
7691
+ const columns = [...this.data().columns];
7692
+ moveItemInArray(columns, event.previousIndex, event.currentIndex);
7693
+ this.commit({ ...this.data(), columns });
7694
+ }
7695
+ undo() {
7696
+ if (!this.canEdit()) {
7697
+ return;
7698
+ }
7699
+ const previous = this.undoStack.pop();
7700
+ if (!previous) {
7701
+ return;
7702
+ }
7703
+ this.redoStack.push(this.data());
7704
+ this.data.set(previous);
7705
+ this.validationError.set('');
7706
+ }
7707
+ redo() {
7708
+ if (!this.canEdit()) {
7709
+ return;
7710
+ }
7711
+ const next = this.redoStack.pop();
7712
+ if (!next) {
7713
+ return;
7714
+ }
7715
+ this.undoStack.push(this.data());
7716
+ this.data.set(next);
7717
+ this.validationError.set('');
7718
+ }
7719
+ async importFile(event) {
7720
+ const inputElement = event.target;
7721
+ const file = inputElement.files?.[0];
7722
+ inputElement.value = '';
7723
+ if (!file || !this.canEdit()) {
7724
+ return;
7725
+ }
7726
+ try {
7727
+ if (file.size > TAON_SPREADSHEET_MAX_BYTES) {
7728
+ throw new Error('Import files must be 10 MB or smaller.');
7729
+ }
7730
+ const text = await file.text();
7731
+ const imported = /\.json$/i.test(file.name)
7732
+ ? importSpreadsheetJson(text)
7733
+ : /\.csv$/i.test(file.name)
7734
+ ? parseSpreadsheetCsv(text)
7735
+ : (() => {
7736
+ throw new Error('Only JSON and CSV files are supported.');
7737
+ })();
7738
+ this.importFormat.set(/\.json$/i.test(file.name) ? 'JSON' : 'CSV');
7739
+ this.importMode.set('replace');
7740
+ this.pendingImport.set(imported);
7741
+ }
7742
+ catch (error) {
7743
+ this.validationError.set(error instanceof Error
7744
+ ? error.message
7745
+ : 'The selected file could not be imported.');
7746
+ }
7747
+ }
7748
+ applyImport() {
7749
+ const imported = this.pendingImport();
7750
+ if (!imported || !this.canEdit()) {
7751
+ return;
7752
+ }
7753
+ if (this.importMode() === 'replace' &&
7754
+ (this.data().rows.length > 0 || this.data().columns.length > 0) &&
7755
+ !globalThis.confirm('Replace the current table with the imported data?')) {
7756
+ return;
7757
+ }
7758
+ if (this.importMode() === 'append') {
7759
+ const currentColumns = this.data().columns;
7760
+ const compatible = currentColumns.length === imported.columns.length &&
7761
+ currentColumns.every((column, index) => column.key === imported.columns[index].key &&
7762
+ column.type === imported.columns[index].type);
7763
+ if (!compatible) {
7764
+ this.validationError.set('Append requires imported columns with the same keys, types, and order as this table.');
7765
+ return;
7766
+ }
7767
+ this.commit({
7768
+ ...this.data(),
7769
+ rows: [...this.data().rows, ...imported.rows],
7770
+ });
7771
+ }
7772
+ else {
7773
+ this.commit(imported);
7774
+ }
7775
+ this.pendingImport.set(null);
7776
+ }
7777
+ cancelImport() {
7778
+ this.pendingImport.set(null);
7779
+ }
7780
+ previewCell(row, column) {
7781
+ const value = row[column.key];
7782
+ return value === undefined
7783
+ ? ''
7784
+ : typeof value === 'string'
7785
+ ? value
7786
+ : JSON.stringify(value);
7787
+ }
7788
+ downloadJson(fullTable = false) {
7789
+ const content = JSON.stringify(fullTable ? this.data() : this.data().rows, null, 2);
7790
+ this.download(content, fullTable ? 'json-table.json' : 'table-data.json', 'application/json');
7791
+ }
7792
+ downloadCsv() {
7793
+ this.download(serializeSpreadsheetCsv(this.data()), 'table-data.csv', 'text/csv');
7794
+ }
7795
+ async copyJson() {
7796
+ try {
7797
+ if (!navigator.clipboard) {
7798
+ throw new Error('Clipboard access is unavailable.');
7799
+ }
7800
+ await navigator.clipboard.writeText(this.jsonPreview());
7801
+ this.validationError.set('');
7802
+ }
7803
+ catch {
7804
+ this.validationError.set('Could not access the clipboard. Copy the preview manually.');
7805
+ }
7806
+ }
7807
+ keyFromLabel(label) {
7808
+ return normalizeSpreadsheetKey(label);
7809
+ }
7810
+ setColumnWidth(columnIndex, value) {
7811
+ if (!this.canEdit()) {
7812
+ return;
7813
+ }
7814
+ const width = Number(value);
7815
+ if (!Number.isFinite(width) || width < 40 || width > 1000) {
7816
+ this.validationError.set('Column width must be between 40 and 1000 pixels.');
7817
+ return;
7818
+ }
7819
+ const columns = this.data().columns.map(column => ({ ...column }));
7820
+ columns[columnIndex] = { ...columns[columnIndex], width };
7821
+ this.commit({ ...this.data(), columns });
7822
+ }
7823
+ cellValue(rowIndex, column) {
7824
+ const value = this.data().rows[rowIndex]?.[column.key];
7825
+ if (column.type === 'json' && value !== null && value !== undefined) {
7826
+ return JSON.stringify(value);
7827
+ }
7828
+ return value === null || value === undefined ? '' : String(value);
7829
+ }
7830
+ trackColumn(_index, column) {
7831
+ return column.key;
7832
+ }
7833
+ clearCell(rowIndex, columnIndex) {
7834
+ const column = this.data().columns[columnIndex];
7835
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: null } : row);
7836
+ this.commit({ ...this.data(), rows });
7837
+ }
7838
+ focusCell(row, column) {
7839
+ if (row < 0 ||
7840
+ row >= this.data().rows.length ||
7841
+ column < 0 ||
7842
+ column >= this.data().columns.length) {
7843
+ return;
7844
+ }
7845
+ this.activeCell.set({ row, column });
7846
+ setTimeout(() => {
7847
+ document
7848
+ .querySelector(`[data-cell="${row}:${column}"]`)
7849
+ ?.focus();
7850
+ });
7851
+ }
7852
+ commit(value) {
7853
+ try {
7854
+ const validated = validateSpreadsheetData(value);
7855
+ this.undoStack.push(this.data());
7856
+ if (this.undoStack.length > 100) {
7857
+ this.undoStack.shift();
7858
+ }
7859
+ this.redoStack.length = 0;
7860
+ this.data.set(validated);
7861
+ this.validationError.set('');
7862
+ }
7863
+ catch (error) {
7864
+ this.validationError.set(error instanceof Error ? error.message : 'Table data is invalid.');
7865
+ }
7866
+ }
7867
+ canEdit() {
7868
+ return !this.readOnly() && !this.disabled();
7869
+ }
7870
+ download(content, fileName, contentType) {
7871
+ const url = URL.createObjectURL(new Blob([content], { type: contentType }));
7872
+ const anchor = document.createElement('a');
7873
+ anchor.href = url;
7874
+ anchor.download = fileName;
7875
+ anchor.click();
7876
+ URL.revokeObjectURL(url);
7877
+ }
7878
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7879
+ 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 }); }
7880
+ }
7881
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, decorators: [{
7882
+ type: Component,
7883
+ args: [{ selector: 'taon-spreadsheet-table', standalone: true, imports: [
7884
+ CdkDrag,
7885
+ CdkDragHandle,
7886
+ CdkDropList,
7887
+ MatButtonModule,
7888
+ MatIconModule,
7889
+ MatSelectModule,
7890
+ ], 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"] }]
7891
+ }], 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"] }] } });
7892
+ function parseClipboardMatrix(value) {
7893
+ const rows = [];
7894
+ let row = [];
7895
+ let cell = '';
7896
+ let quoted = false;
7897
+ for (let index = 0; index < value.length; index += 1) {
7898
+ const character = value[index];
7899
+ if (quoted) {
7900
+ if (character === '"' && value[index + 1] === '"') {
7901
+ cell += '"';
7902
+ index += 1;
7903
+ }
7904
+ else if (character === '"') {
7905
+ quoted = false;
7906
+ }
7907
+ else {
7908
+ cell += character;
7909
+ }
7910
+ }
7911
+ else if (character === '"' && !cell) {
7912
+ quoted = true;
7913
+ }
7914
+ else if (character === '\t') {
7915
+ row.push(cell);
7916
+ cell = '';
7917
+ }
7918
+ else if (character === '\r' || character === '\n') {
7919
+ row.push(cell);
7920
+ rows.push(row);
7921
+ row = [];
7922
+ cell = '';
7923
+ if (character === '\r' && value[index + 1] === '\n') {
7924
+ index += 1;
7925
+ }
7926
+ }
7927
+ else {
7928
+ cell += character;
7929
+ }
7930
+ }
7931
+ if (quoted) {
7932
+ throw new Error('Pasted data contains an unterminated quoted value.');
7933
+ }
7934
+ if (cell.length || row.length) {
7935
+ row.push(cell);
7936
+ rows.push(row);
7937
+ }
7938
+ return rows;
7939
+ }
7940
+ function parsePastedCell(value, column) {
7941
+ if (value === '') {
7942
+ return null;
7943
+ }
7944
+ if (column.type === 'number') {
7945
+ const parsed = Number(value);
7946
+ if (!Number.isFinite(parsed)) {
7947
+ throw new Error(`"${value}" is not a valid number for ${column.label || column.key}.`);
7948
+ }
7949
+ return parsed;
7950
+ }
7951
+ if (column.type === 'boolean') {
7952
+ if (/^(true|1|yes)$/i.test(value)) {
7953
+ return true;
7954
+ }
7955
+ if (/^(false|0|no)$/i.test(value)) {
7956
+ return false;
7957
+ }
7958
+ throw new Error(`"${value}" is not a valid boolean for ${column.label || column.key}.`);
7959
+ }
7960
+ if (column.type === 'date') {
7961
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
7962
+ Number.isNaN(Date.parse(`${value}T00:00:00.000Z`))) {
7963
+ throw new Error(`"${value}" is not a valid date for ${column.label || column.key}.`);
7964
+ }
7965
+ return value;
7966
+ }
7967
+ if (column.type === 'json') {
7968
+ try {
7969
+ return JSON.parse(value);
7970
+ }
7971
+ catch {
7972
+ throw new Error(`"${value}" is not valid JSON for ${column.label || column.key}.`);
7973
+ }
7974
+ }
7975
+ return value;
7976
+ }
7977
+
6841
7978
  //#region imports
6842
7979
  //#endregion
6843
7980
  class TaonCmsWebsiteFeatureApiService extends TaonBaseAngularService {
@@ -7015,7 +8152,7 @@ class TaonCmsWebsiteFeaturesComponent {
7015
8152
  }
7016
8153
  }
7017
8154
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7018
- 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 }); }
8155
+ 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 }); }
7019
8156
  }
7020
8157
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, decorators: [{
7021
8158
  type: Component,
@@ -7125,7 +8262,7 @@ const TaonCmsCommentsBackofficeRoutes = [
7125
8262
  */
7126
8263
  // export default TaonCmsCommentsBackofficeRoutes;
7127
8264
 
7128
- 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-browser-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-D-5qFPjS.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-BvewjJas.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-D_pzY5c6.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DFXtq9fz.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-BEsM01k5.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DSCbLyki.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-DS_P-nD2.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-B4j_wPwr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-d1KbFK-8.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-DLaPQZZj.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-alQxEnOY.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DpFy5ZSO.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CyuTYn77.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-CgmdVwt_.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-4KAdwPda.mjs')).default } });
8265
+ 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-browser-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-D-5qFPjS.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-BvewjJas.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-D_pzY5c6.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DFXtq9fz.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-BEsM01k5.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DSCbLyki.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-DS_P-nD2.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-B4j_wPwr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-d1KbFK-8.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-CmuKeBoj.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-DlP8ezIr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DCh3tUVC.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CX4JzE60.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-C4xmbOD4.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-BnlwPUxT.mjs')).default } });
7129
8266
  class TaonCmsPostChoiceDialogComponent {
7130
8267
  constructor() {
7131
8268
  this.t = t$f.for(this);
@@ -7433,6 +8570,7 @@ function defaultThumbnailUrl(key) {
7433
8570
  video: '<rect x="248" y="132" width="144" height="100" rx="16"/><path d="m308 158 40 24-40 24z"/>',
7434
8571
  audio: '<path d="M280 164v40m20-60v80m20-98v116m20-98v80m20-60v40"/>',
7435
8572
  attachment: '<path d="M276 124h60l32 32v84h-92z M336 124v36h32m-70 30h48m-48 22h36"/>',
8573
+ 'json-table': '<path d="M252 130h136v100H252z M252 164h136m-92-34v100m46-100v100m-90-66h136m-136 33h136"/>',
7436
8574
  };
7437
8575
  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>`;
7438
8576
  return `data:image/svg+xml,${encodeURIComponent(svg)}`;
@@ -7665,7 +8803,7 @@ class TaonCmsCourseBackofficeComponent extends TaonCmsPostTableActions {
7665
8803
  });
7666
8804
  }
7667
8805
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
7668
- 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 }); }
8806
+ 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 }); }
7669
8807
  }
7670
8808
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, decorators: [{
7671
8809
  type: Component,
@@ -8378,7 +9516,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8378
9516
  args: ['paste', ['$event']]
8379
9517
  }] } });
8380
9518
 
8381
- 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-browser-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-D-5qFPjS.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-BvewjJas.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-D_pzY5c6.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DFXtq9fz.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-BEsM01k5.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DSCbLyki.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-DS_P-nD2.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-B4j_wPwr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-d1KbFK-8.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-DLaPQZZj.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-alQxEnOY.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DpFy5ZSO.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CyuTYn77.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-CgmdVwt_.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-4KAdwPda.mjs')).default } });
9519
+ 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-browser-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-D-5qFPjS.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-BvewjJas.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-D_pzY5c6.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DFXtq9fz.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-BEsM01k5.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DSCbLyki.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-DS_P-nD2.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-B4j_wPwr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-d1KbFK-8.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-CmuKeBoj.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-DlP8ezIr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DCh3tUVC.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CX4JzE60.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-C4xmbOD4.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-BnlwPUxT.mjs')).default } });
8382
9520
  class TaonCmsPostThumbnailComponent {
8383
9521
  constructor() {
8384
9522
  this.t = t$a.for(this);
@@ -8584,8 +9722,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8584
9722
 
8585
9723
  //#region imports
8586
9724
  //#endregion
8587
- 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-browser-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-D-5qFPjS.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-BvewjJas.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-D_pzY5c6.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DFXtq9fz.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-BEsM01k5.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DSCbLyki.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-DS_P-nD2.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-B4j_wPwr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-d1KbFK-8.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-DLaPQZZj.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-alQxEnOY.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DpFy5ZSO.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CyuTYn77.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-CgmdVwt_.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-4KAdwPda.mjs')).default } });
9725
+ 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-browser-prod-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-D-5qFPjS.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-BvewjJas.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-D_pzY5c6.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-DFXtq9fz.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-BEsM01k5.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-DSCbLyki.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-DS_P-nD2.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-B4j_wPwr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-d1KbFK-8.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.en-US.i18n.data-CmuKeBoj.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.pl-PL.i18n.data-DlP8ezIr.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.html.de-DE.i18n.data-DCh3tUVC.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.en-US.i18n.data-CX4JzE60.mjs')).default, "pl-PL": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.pl-PL.i18n.data-C4xmbOD4.mjs')).default, "de-DE": async () => (await import('./cms-browser-prod-taon-cms-post.component.ts.de-DE.i18n.data-BnlwPUxT.mjs')).default } });
8588
9726
  class TaonCmsPostComponent {
9727
+ get requiredSlugs() {
9728
+ return this.contentConfig.requiredSlugs;
9729
+ }
8589
9730
  insertTable(event) {
8590
9731
  event.preventDefault();
8591
9732
  const editor = this.contentEditor();
@@ -8606,6 +9747,7 @@ class TaonCmsPostComponent {
8606
9747
  constructor() {
8607
9748
  //#region fields and getters
8608
9749
  this.t = t$7.for(this);
9750
+ this.contentType = TaonCmsContentType;
8609
9751
  this.post = input(null, ...(ngDevMode ? [{ debugName: "post" }] : []));
8610
9752
  this.mode = model('view', ...(ngDevMode ? [{ debugName: "mode" }] : []));
8611
9753
  this.changed = output();
@@ -8632,6 +9774,12 @@ class TaonCmsPostComponent {
8632
9774
  categoryId: null,
8633
9775
  tagIds: [],
8634
9776
  }, ...(ngDevMode ? [{ debugName: "draft" }] : []));
9777
+ this.jsonTableData = signal({
9778
+ version: 1,
9779
+ columns: [],
9780
+ rows: [],
9781
+ }, ...(ngDevMode ? [{ debugName: "jsonTableData" }] : []));
9782
+ this.jsonTableValidationError = signal('', ...(ngDevMode ? [{ debugName: "jsonTableValidationError" }] : []));
8635
9783
  this.postTypes = [
8636
9784
  {
8637
9785
  value: TaonCmsContentType.Normal,
@@ -8643,6 +9791,10 @@ class TaonCmsPostComponent {
8643
9791
  value: TaonCmsContentType.Attachment,
8644
9792
  label: this.t.gettext('Attachment post'),
8645
9793
  },
9794
+ {
9795
+ value: TaonCmsContentType.JsonTable,
9796
+ label: this.t.gettext('JSON Table post'),
9797
+ },
8646
9798
  ];
8647
9799
  this.currentPostType = computed(() => this.postTypes.find(type => type.value === this.draft().type) ??
8648
9800
  this.postTypes[0], ...(ngDevMode ? [{ debugName: "currentPostType" }] : []));
@@ -8654,6 +9806,8 @@ class TaonCmsPostComponent {
8654
9806
  return 'audiotrack';
8655
9807
  case TaonCmsContentType.Attachment:
8656
9808
  return 'attach_file';
9809
+ case TaonCmsContentType.JsonTable:
9810
+ return 'table_chart';
8657
9811
  default:
8658
9812
  return 'article';
8659
9813
  }
@@ -8714,6 +9868,9 @@ class TaonCmsPostComponent {
8714
9868
  this.stagedThumbnail = signal(null, ...(ngDevMode ? [{ debugName: "stagedThumbnail" }] : []));
8715
9869
  this.uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : []));
8716
9870
  this.uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
9871
+ this.mediaUploadAbort = null;
9872
+ this.childUploadAbort = null;
9873
+ this.childUploadRunning = signal(false, ...(ngDevMode ? [{ debugName: "childUploadRunning" }] : []));
8717
9874
  this.mediaPreviewUrl = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewUrl" }] : []));
8718
9875
  this.mediaPreviewLoading = signal(false, ...(ngDevMode ? [{ debugName: "mediaPreviewLoading" }] : []));
8719
9876
  this.mediaPreviewError = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewError" }] : []));
@@ -8826,7 +9983,9 @@ class TaonCmsPostComponent {
8826
9983
  !this.permissionsLoading() &&
8827
9984
  !this.permissionsError() &&
8828
9985
  !this.taxonomyLoading() &&
8829
- !this.taxonomyError(), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
9986
+ !this.taxonomyError() &&
9987
+ (this.draft().type !== TaonCmsContentType.JsonTable ||
9988
+ !this.jsonTableValidationError()), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
8830
9989
  this.statusLabel = computed(() => {
8831
9990
  const status = this.mode() === 'add'
8832
9991
  ? this.draftStatus()
@@ -8848,6 +10007,10 @@ class TaonCmsPostComponent {
8848
10007
  (this.mode() === 'edit' && !this.canSave()), ...(ngDevMode ? [{ debugName: "statusControlDisabled" }] : []));
8849
10008
  this.api = inject(TaonCmsContentApiService);
8850
10009
  this.contentConfig = inject(TaonCmsContentConfigService);
10010
+ this.selectedRequiredSlug = computed(() => {
10011
+ const slug = this.draft().slug;
10012
+ return this.requiredSlugs.includes(slug) ? slug : null;
10013
+ }, ...(ngDevMode ? [{ debugName: "selectedRequiredSlug" }] : []));
8851
10014
  this.contentTab = signal(0, ...(ngDevMode ? [{ debugName: "contentTab" }] : []));
8852
10015
  this.contentEditor = signal(null, ...(ngDevMode ? [{ debugName: "contentEditor" }] : []));
8853
10016
  this.rawContent = viewChild('rawContent', ...(ngDevMode ? [{ debugName: "rawContent" }] : []));
@@ -9058,7 +10221,10 @@ class TaonCmsPostComponent {
9058
10221
  });
9059
10222
  }
9060
10223
  //#endregion
9061
- ngOnDestroy() { }
10224
+ ngOnDestroy() {
10225
+ this.mediaUploadAbort?.abort();
10226
+ this.childUploadAbort?.abort();
10227
+ }
9062
10228
  //#region methods / load related posts
9063
10229
  loadTaxonomy(preserveSelectedTags = false) {
9064
10230
  this.taxonomyLoad?.unsubscribe();
@@ -9178,6 +10344,10 @@ class TaonCmsPostComponent {
9178
10344
  this.error.set(this.t.gettext('Please select an image for the thumbnail.'));
9179
10345
  return;
9180
10346
  }
10347
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
10348
+ this.error.set(this.fileTooLargeMessage(file));
10349
+ return;
10350
+ }
9181
10351
  this.busy.set(true);
9182
10352
  this.error.set('');
9183
10353
  try {
@@ -9761,6 +10931,33 @@ class TaonCmsPostComponent {
9761
10931
  //#endregion
9762
10932
  //#region methods / update draft
9763
10933
  updateDraft(field, value) {
10934
+ if (field === 'type' &&
10935
+ value === TaonCmsContentType.JsonTable &&
10936
+ this.draft().type !== TaonCmsContentType.JsonTable) {
10937
+ let tableData = {
10938
+ version: 1,
10939
+ columns: [],
10940
+ rows: [],
10941
+ };
10942
+ if (this.draft().body.trim()) {
10943
+ try {
10944
+ tableData = parseSpreadsheetDocument(this.draft().body);
10945
+ }
10946
+ catch {
10947
+ if (!globalThis.confirm('Changing the post to a JSON Table will replace its current body. Continue?')) {
10948
+ return;
10949
+ }
10950
+ }
10951
+ }
10952
+ this.jsonTableData.set(tableData);
10953
+ this.jsonTableValidationError.set('');
10954
+ this.draft.update(draft => ({
10955
+ ...draft,
10956
+ type: value,
10957
+ body: JSON.stringify(tableData),
10958
+ }));
10959
+ return;
10960
+ }
9764
10961
  if (field === 'title' && this.mode() === 'add') {
9765
10962
  const title = String(value);
9766
10963
  this.draft.update(draft => ({
@@ -9794,10 +10991,13 @@ class TaonCmsPostComponent {
9794
10991
  if (!this.currentPost() || this.busy() || !files.length) {
9795
10992
  return;
9796
10993
  }
9797
- this.childBatchError.set('');
10994
+ const oversized = files.filter(file => file.size > TAON_CMS_MAX_UPLOAD_BYTES);
10995
+ this.childBatchError.set(oversized.map(file => this.fileTooLargeMessage(file)).join(' '));
9798
10996
  this.childFiles.update(selected => [
9799
10997
  ...selected,
9800
- ...files.map(file => ({
10998
+ ...files
10999
+ .filter(file => file.size <= TAON_CMS_MAX_UPLOAD_BYTES)
11000
+ .map(file => ({
9801
11001
  id: ++this.childFileSequence,
9802
11002
  file,
9803
11003
  status: 'pending',
@@ -9828,10 +11028,16 @@ class TaonCmsPostComponent {
9828
11028
  const pendingFiles = this.childFiles()
9829
11029
  .map((file, index) => ({ file, index }))
9830
11030
  .filter(({ file }) => file.status !== 'success');
11031
+ const abort = new AbortController();
11032
+ this.childUploadAbort = abort;
11033
+ this.childUploadRunning.set(true);
9831
11034
  this.busy.set(true);
9832
11035
  this.childBatchError.set('');
9833
11036
  try {
9834
11037
  for (const { file: selectedFile, index } of pendingFiles) {
11038
+ if (abort.signal.aborted) {
11039
+ break;
11040
+ }
9835
11041
  const expectedTitle = this.childTitlePreviews()[index];
9836
11042
  this.updateRelatedChildFile(selectedFile.id, {
9837
11043
  status: 'processing',
@@ -9847,22 +11053,24 @@ class TaonCmsPostComponent {
9847
11053
  }
9848
11054
  let uploads = this.childFiles().find(file => file.id === selectedFile.id)?.uploads;
9849
11055
  if (!uploads?.source) {
11056
+ abort.signal.throwIfAborted();
9850
11057
  const source = await this.api.uploadMedia(selectedFile.file, percentage => this.updateRelatedChildFile(selectedFile.id, {
9851
11058
  progress: type === TaonCmsContentType.Video
9852
11059
  ? Math.round(percentage * 0.65)
9853
11060
  : Math.round(percentage * 0.8),
9854
- }));
11061
+ }), abort.signal);
9855
11062
  uploads = { ...uploads, source };
9856
11063
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9857
11064
  }
9858
11065
  if (type === TaonCmsContentType.Video && !uploads.thumbnail) {
9859
11066
  const thumbnailBlob = await generateVideoThumbnail(selectedFile.file);
9860
11067
  const extension = thumbnailBlob.type === 'image/webp' ? 'webp' : 'png';
11068
+ abort.signal.throwIfAborted();
9861
11069
  const thumbnail = await this.api.uploadMedia(new File([thumbnailBlob], `thumbnail.${extension}`, {
9862
11070
  type: thumbnailBlob.type,
9863
11071
  }), percentage => this.updateRelatedChildFile(selectedFile.id, {
9864
11072
  progress: 65 + Math.round(percentage * 0.25),
9865
- }));
11073
+ }), abort.signal);
9866
11074
  uploads = { ...uploads, thumbnail };
9867
11075
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9868
11076
  }
@@ -9879,6 +11087,8 @@ class TaonCmsPostComponent {
9879
11087
  if (!sourceUpload) {
9880
11088
  throw new Error('The uploaded source file is unavailable.');
9881
11089
  }
11090
+ // Cancellation is honoured only before the child post is created.
11091
+ abort.signal.throwIfAborted();
9882
11092
  const result = await this.api.createRelatedChild(parent.id, {
9883
11093
  titlePrefix,
9884
11094
  expectedTitle,
@@ -9932,7 +11142,10 @@ class TaonCmsPostComponent {
9932
11142
  ? await this.cleanupRelatedChildUploads(selectedFile.id, uploads)
9933
11143
  : []
9934
11144
  : [];
9935
- const reason = error instanceof Error ? error.message : String(error);
11145
+ const reason = abort.signal.aborted
11146
+ ? this.t.gettext('Upload cancelled. No child post was created.')
11147
+ : error?.body?.json?.message ||
11148
+ (error instanceof Error ? error.message : String(error));
9936
11149
  const verificationError = relatedPosts
9937
11150
  ? ''
9938
11151
  : `${this.t.gettext('The creation result could not be verified; retrying will not duplicate an existing child.')} `;
@@ -9946,6 +11159,10 @@ class TaonCmsPostComponent {
9946
11159
  }
9947
11160
  }
9948
11161
  finally {
11162
+ if (this.childUploadAbort === abort) {
11163
+ this.childUploadAbort = null;
11164
+ }
11165
+ this.childUploadRunning.set(false);
9949
11166
  this.busy.set(false);
9950
11167
  this.loadRelatedPosts();
9951
11168
  }
@@ -10083,7 +11300,14 @@ class TaonCmsPostComponent {
10083
11300
  input.value = '';
10084
11301
  return;
10085
11302
  }
11303
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
11304
+ input.value = '';
11305
+ this.error.set(this.fileTooLargeMessage(file));
11306
+ return;
11307
+ }
10086
11308
  const session = this.editorSession;
11309
+ const abort = new AbortController();
11310
+ this.mediaUploadAbort = abort;
10087
11311
  this.busy.set(true);
10088
11312
  this.uploading.set(true);
10089
11313
  this.uploadProgress.set(0);
@@ -10093,7 +11317,7 @@ class TaonCmsPostComponent {
10093
11317
  if (!this.destroyRef.destroyed) {
10094
11318
  this.uploadProgress.set(percentage);
10095
11319
  }
10096
- });
11320
+ }, abort.signal);
10097
11321
  if (!this.destroyRef.destroyed && this.editorSession === session) {
10098
11322
  const fileName = object.fileName || file.name;
10099
11323
  this.mediaFileNames.update(names => ({
@@ -10122,10 +11346,20 @@ class TaonCmsPostComponent {
10122
11346
  }
10123
11347
  }
10124
11348
  catch (error) {
10125
- console.error('[taon-cms-post] Unable to upload media', error);
10126
- this.error.set(this.t.gettext('The media could not be uploaded. Please try again.'));
11349
+ if (abort.signal.aborted) {
11350
+ if (!this.destroyRef.destroyed) {
11351
+ this.error.set(this.t.gettext('The media upload was cancelled.'));
11352
+ }
11353
+ }
11354
+ else {
11355
+ console.error('[taon-cms-post] Unable to upload media', error);
11356
+ this.error.set(`${this.t.gettext('The media could not be uploaded. Please try again.')}${this.uploadErrorDetails(error)}`);
11357
+ }
10127
11358
  }
10128
11359
  finally {
11360
+ if (this.mediaUploadAbort === abort) {
11361
+ this.mediaUploadAbort = null;
11362
+ }
10129
11363
  this.busy.set(false);
10130
11364
  this.uploading.set(false);
10131
11365
  this.uploadProgress.set(0);
@@ -10133,6 +11367,26 @@ class TaonCmsPostComponent {
10133
11367
  }
10134
11368
  }
10135
11369
  //#endregion
11370
+ //#region methods / cancel media upload
11371
+ cancelMediaUpload() {
11372
+ this.mediaUploadAbort?.abort();
11373
+ }
11374
+ //#endregion
11375
+ //#region methods / cancel related child uploads
11376
+ cancelRelatedChildUploads() {
11377
+ this.childUploadAbort?.abort();
11378
+ }
11379
+ //#endregion
11380
+ //#region methods / upload error helpers
11381
+ fileTooLargeMessage(file) {
11382
+ return this.t.gettext('The file "[[ fileName ]]" is too large. The maximum upload size is 1 GB.', { fileName: file.name });
11383
+ }
11384
+ uploadErrorDetails(error) {
11385
+ const message = error?.body?.json?.message ||
11386
+ (error instanceof Error ? error.message : '');
11387
+ return message ? ` (${message})` : '';
11388
+ }
11389
+ //#endregion
10136
11390
  //#region methods / remove media
10137
11391
  removeMedia() {
10138
11392
  const media = this.mediaAttachment();
@@ -10206,6 +11460,11 @@ class TaonCmsPostComponent {
10206
11460
  this.updateDraft('slug', event.target.value);
10207
11461
  }
10208
11462
  }
11463
+ selectRequiredSlug(slug) {
11464
+ if (slug && this.requiredSlugs.includes(slug)) {
11465
+ this.updateDraft('slug', slug);
11466
+ }
11467
+ }
10209
11468
  //#endregion
10210
11469
  //#region methods / edit
10211
11470
  edit() {
@@ -10596,6 +11855,13 @@ class TaonCmsPostComponent {
10596
11855
  categoryId: post?.categoryId ?? null,
10597
11856
  tagIds: [],
10598
11857
  });
11858
+ if ((post?.type ?? TaonCmsContentType.Normal) === TaonCmsContentType.JsonTable) {
11859
+ this.loadJsonTableData(post?.body ?? null);
11860
+ }
11861
+ else {
11862
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11863
+ this.jsonTableValidationError.set('');
11864
+ }
10599
11865
  if (post &&
10600
11866
  post.relatedPosts === undefined &&
10601
11867
  this.mode() !== 'view-clean') {
@@ -10609,6 +11875,34 @@ class TaonCmsPostComponent {
10609
11875
  }
10610
11876
  }
10611
11877
  //#endregion
11878
+ setJsonTableData(value) {
11879
+ try {
11880
+ const data = validateSpreadsheetData(value);
11881
+ this.jsonTableData.set(data);
11882
+ this.draft.update(draft => ({
11883
+ ...draft,
11884
+ body: JSON.stringify(data),
11885
+ }));
11886
+ }
11887
+ catch (error) {
11888
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11889
+ }
11890
+ }
11891
+ loadJsonTableData(body) {
11892
+ try {
11893
+ const data = parseSpreadsheetDocument(body);
11894
+ this.jsonTableData.set(data);
11895
+ this.draft.update(draft => ({
11896
+ ...draft,
11897
+ body: JSON.stringify(data),
11898
+ }));
11899
+ this.jsonTableValidationError.set('');
11900
+ }
11901
+ catch (error) {
11902
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11903
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11904
+ }
11905
+ }
10612
11906
  //#region methods / set application hook selection
10613
11907
  setApplicationHookSelection(post, hooks) {
10614
11908
  const assignedHook = post?.applicationHook !== undefined
@@ -10656,7 +11950,7 @@ class TaonCmsPostComponent {
10656
11950
  TaonPermissionApiService,
10657
11951
  TaonCmsCategoryApiService,
10658
11952
  TaonCmsTagApiService,
10659
- ], 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 }); }
11953
+ ], 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 }); }
10660
11954
  }
10661
11955
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsPostComponent, decorators: [{
10662
11956
  type: Component,
@@ -10682,13 +11976,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
10682
11976
  MatDividerModule,
10683
11977
  DragDropModule,
10684
11978
  TaonCmsPostThumbnailComponent,
11979
+ TaonSpreadsheetTableComponent,
11980
+ JsonPipe,
10685
11981
  ], providers: [
10686
11982
  TaonCmsContentApiService,
10687
11983
  TaonCmsContentConfigService,
10688
11984
  TaonPermissionApiService,
10689
11985
  TaonCmsCategoryApiService,
10690
11986
  TaonCmsTagApiService,
10691
- ], 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"] }]
11987
+ ], 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"] }]
10692
11988
  }], 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: [{
10693
11989
  type: ViewChild,
10694
11990
  args: ['actionButtonsTemplate']
@@ -11345,6 +12641,8 @@ var index$1 = /*#__PURE__*/Object.freeze({
11345
12641
  get CmsContext_1793664000000_postVersionLinks () { return CmsContext_1793664000000_postVersionLinks; },
11346
12642
  MIGRATIONS_CLASSES_FOR_CmsContext: MIGRATIONS_CLASSES_FOR_CmsContext,
11347
12643
  PROJECT_NPM_NAME: PROJECT_NPM_NAME,
12644
+ TAON_CMS_MAX_UPLOAD_BYTES: TAON_CMS_MAX_UPLOAD_BYTES,
12645
+ TAON_SPREADSHEET_MAX_BYTES: TAON_SPREADSHEET_MAX_BYTES,
11348
12646
  TaonCmsAbstractContext: TaonCmsAbstractContext,
11349
12647
  TaonCmsApplicationHook: TaonCmsApplicationHook,
11350
12648
  get TaonCmsApplicationHookEntity () { return TaonCmsApplicationHookEntity; },
@@ -11492,6 +12790,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
11492
12790
  TaonCmsWebsiteFeaturesComponent: TaonCmsWebsiteFeaturesComponent,
11493
12791
  TaonCmsWebsiteFeaturesRoutes: TaonCmsWebsiteFeaturesRoutes,
11494
12792
  TaonRelatedPostChooserComponent: TaonRelatedPostChooserComponent,
12793
+ TaonSpreadsheetTableComponent: TaonSpreadsheetTableComponent,
11495
12794
  TaonTaonCmsWebsiteFeatureConfig: TaonTaonCmsWebsiteFeatureConfig,
11496
12795
  contentError: contentError,
11497
12796
  default: index,
@@ -11499,13 +12798,20 @@ var index$1 = /*#__PURE__*/Object.freeze({
11499
12798
  defaultThumbnailUrl: defaultThumbnailUrl,
11500
12799
  extractDocumentHtml: extractDocumentHtml,
11501
12800
  generateVideoThumbnail: generateVideoThumbnail,
12801
+ importSpreadsheetJson: importSpreadsheetJson,
12802
+ inferSpreadsheetData: inferSpreadsheetData,
12803
+ normalizeSpreadsheetKey: normalizeSpreadsheetKey,
12804
+ parseSpreadsheetCsv: parseSpreadsheetCsv,
12805
+ parseSpreadsheetDocument: parseSpreadsheetDocument,
11502
12806
  requireContentId: requireContentId,
11503
12807
  requireContentVersion: requireContentVersion,
11504
12808
  resolveTemplateVariables: resolveTemplateVariables,
12809
+ serializeSpreadsheetCsv: serializeSpreadsheetCsv,
11505
12810
  startCli: startCli,
11506
12811
  trackCmsUpload: trackCmsUpload,
11507
12812
  validateCategoryHierarchy: validateCategoryHierarchy,
11508
12813
  validateContentInput: validateContentInput,
12814
+ validateSpreadsheetData: validateSpreadsheetData,
11509
12815
  validateTaxonomy: validateTaxonomy,
11510
12816
  visiblePixelRatio: visiblePixelRatio
11511
12817
  });
@@ -11514,5 +12820,5 @@ var index$1 = /*#__PURE__*/Object.freeze({
11514
12820
  * Generated bundle index. Do not edit.
11515
12821
  */
11516
12822
 
11517
- 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 };
12823
+ 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 };
11518
12824
  //# sourceMappingURL=cms-browser-prod.mjs.map