@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';
6
6
  import { TaonSessionRepository, TaonAuthContextRepository, TaonSessionAbstractContext, TaonSessionProvider, TaonSessionApiService, TaonPermissionApiService } from '@taon-dev/session/browser';
7
7
  import { Translation } from '@taon-dev/i18n/browser';
8
8
  import * as i0 from '@angular/core';
9
- import { Injectable, inject, DestroyRef, signal, Input, ChangeDetectionStrategy, Component, input, effect, Directive, output, ViewChild, ElementRef, EventEmitter, SecurityContext, HostListener, Output, computed, model, viewChild, untracked } from '@angular/core';
9
+ import { Injectable, inject, DestroyRef, signal, Input, ChangeDetectionStrategy, Component, input, effect, model, computed, Directive, output, ViewChild, ElementRef, EventEmitter, SecurityContext, HostListener, Output, viewChild, untracked } from '@angular/core';
10
10
  import * as i1 from '@angular/material/button';
11
11
  import { MatButtonModule } from '@angular/material/button';
12
- import { Observable, of, map as map$1, finalize, take, forkJoin, switchMap, from, firstValueFrom, combineLatest } from 'rxjs';
12
+ import { of, map as map$1, finalize, Observable, take, forkJoin, switchMap, from, firstValueFrom, combineLatest } from 'rxjs';
13
13
  import { map, catchError } from 'rxjs/operators';
14
- import { Resource } from 'ng2-rest/browser';
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';
17
- import { AsyncPipe, DatePipe, NgTemplateOutlet } from '@angular/common';
16
+ import { AsyncPipe, DatePipe, NgTemplateOutlet, JsonPipe } from '@angular/common';
18
17
  import { RouterOutlet, ActivatedRoute, Router, RouterLink } from '@angular/router';
18
+ import { Resource } from 'ng2-rest/browser';
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.methodsInterceptors.has(key)) {
5266
- Resource.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
  /**
@@ -6204,6 +6670,13 @@ class TaonCmsContentConfigService extends TaonBaseAngularService {
6204
6670
  get siteName() {
6205
6671
  return this.taonCmsContentProvider.siteName;
6206
6672
  }
6673
+ get requiredSlugs() {
6674
+ return [
6675
+ ...new Set((this.taonCmsContentProvider.requiredSlugs ?? [])
6676
+ .map(slug => String(slug ?? '').trim())
6677
+ .filter(Boolean)),
6678
+ ];
6679
+ }
6207
6680
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
6208
6681
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsContentConfigService }); }
6209
6682
  }
@@ -6211,17 +6684,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
6211
6684
  type: Injectable
6212
6685
  }] });
6213
6686
 
6214
- const TaonCmsContentDefaultsValues = {
6215
- description: '',
6216
- version: 0,
6217
- id: void 0,
6218
- type: TaonCmsContentType.Normal,
6219
- videoKey: null,
6220
- audioKey: null,
6221
- attachmentKey: null,
6222
- thumbnailKey: null,
6223
- };
6224
-
6225
6687
  var TaonCmsContentUtils;
6226
6688
  (function (TaonCmsContentUtils) {
6227
6689
  function orderRelatedPosts(posts, ids) {
@@ -6264,6 +6726,81 @@ function resolveTemplateVariables(content, context) {
6264
6726
  });
6265
6727
  }
6266
6728
 
6729
+ const progressCallbacks = new WeakMap();
6730
+ // Keep Taon's middleware, retries and response decoding; replace only the
6731
+ // final Fetch transport for uploads that request browser upload events.
6732
+ function trackCmsUpload(url, body, onProgress) {
6733
+ const key = `cms-upload-progress-POST-${new URL(url).pathname}`;
6734
+ if (!Resource.methodsInterceptors.has(key)) {
6735
+ Resource.methodsInterceptors.set(key, {
6736
+ intercept: ({ req, next }) => {
6737
+ if (!(req.body instanceof FormData)) {
6738
+ return next.handle(req);
6739
+ }
6740
+ const uploadBody = req.body;
6741
+ const callback = progressCallbacks.get(uploadBody);
6742
+ if (!callback) {
6743
+ return next.handle(req);
6744
+ }
6745
+ return new Observable(subscriber => {
6746
+ const xhr = new XMLHttpRequest();
6747
+ xhr.open(req.method || 'POST', req.url);
6748
+ xhr.withCredentials = req.credentials === 'include';
6749
+ new Headers(req.headers).forEach((value, name) => {
6750
+ // The browser must supply the multipart boundary.
6751
+ if (name.toLowerCase() !== 'content-type') {
6752
+ xhr.setRequestHeader(name, value);
6753
+ }
6754
+ });
6755
+ xhr.upload.onprogress = event => {
6756
+ if (event.lengthComputable && event.total > 0) {
6757
+ callback(Math.min(100, Math.floor((event.loaded * 100) / event.total)));
6758
+ }
6759
+ };
6760
+ xhr.onload = () => {
6761
+ const headers = new Headers();
6762
+ for (const line of xhr
6763
+ .getAllResponseHeaders()
6764
+ .trim()
6765
+ .split(/[\r\n]+/)) {
6766
+ const separator = line.indexOf(':');
6767
+ if (separator > 0) {
6768
+ headers.append(line.slice(0, separator), line.slice(separator + 1).trim());
6769
+ }
6770
+ }
6771
+ subscriber.next(new Response([204, 205, 304].includes(xhr.status) ? null : xhr.responseText, {
6772
+ status: xhr.status,
6773
+ statusText: xhr.statusText,
6774
+ headers,
6775
+ }));
6776
+ subscriber.complete();
6777
+ };
6778
+ xhr.onerror = () => subscriber.error(new Error('Media upload network error.'));
6779
+ xhr.ontimeout = () => subscriber.error(new Error('Media upload timed out.'));
6780
+ xhr.onabort = () => subscriber.error(new Error('Media upload was cancelled.'));
6781
+ const abort = () => xhr.abort();
6782
+ req.signal?.addEventListener('abort', abort, { once: true });
6783
+ if (req.signal?.aborted) {
6784
+ subscriber.error(new Error('Media upload was cancelled.'));
6785
+ }
6786
+ else {
6787
+ callback(0);
6788
+ xhr.send(uploadBody);
6789
+ }
6790
+ return () => {
6791
+ req.signal?.removeEventListener('abort', abort);
6792
+ if (xhr.readyState !== XMLHttpRequest.DONE) {
6793
+ xhr.abort();
6794
+ }
6795
+ };
6796
+ });
6797
+ },
6798
+ });
6799
+ }
6800
+ progressCallbacks.set(body, onProgress);
6801
+ return () => progressCallbacks.delete(body);
6802
+ }
6803
+
6267
6804
  class TaonCmsContentAssetApiService extends TaonBaseAngularService {
6268
6805
  constructor() {
6269
6806
  super(...arguments);
@@ -6852,6 +7389,606 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
6852
7389
  }]
6853
7390
  }] });
6854
7391
 
7392
+ class TaonSpreadsheetTableComponent {
7393
+ constructor() {
7394
+ this.data = model({
7395
+ version: 1,
7396
+ columns: [],
7397
+ rows: [],
7398
+ }, ...(ngDevMode ? [{ debugName: "data" }] : []));
7399
+ this.readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
7400
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
7401
+ this.validationError = model('', ...(ngDevMode ? [{ debugName: "validationError" }] : []));
7402
+ this.view = signal('table', ...(ngDevMode ? [{ debugName: "view" }] : []));
7403
+ this.activeCell = signal(null, ...(ngDevMode ? [{ debugName: "activeCell" }] : []));
7404
+ this.pendingImport = signal(null, ...(ngDevMode ? [{ debugName: "pendingImport" }] : []));
7405
+ this.importFormat = signal('', ...(ngDevMode ? [{ debugName: "importFormat" }] : []));
7406
+ this.importMode = signal('replace', ...(ngDevMode ? [{ debugName: "importMode" }] : []));
7407
+ this.columnTypes = [
7408
+ 'string',
7409
+ 'number',
7410
+ 'boolean',
7411
+ 'date',
7412
+ 'json',
7413
+ ];
7414
+ this.jsonPreview = computed(() => JSON.stringify(this.data().rows, null, 2), ...(ngDevMode ? [{ debugName: "jsonPreview" }] : []));
7415
+ this.undoStack = [];
7416
+ this.redoStack = [];
7417
+ }
7418
+ addColumn() {
7419
+ if (!this.canEdit()) {
7420
+ return;
7421
+ }
7422
+ const data = this.data();
7423
+ let number = data.columns.length + 1;
7424
+ let key = `column${number}`;
7425
+ while (data.columns.some(column => column.key === key)) {
7426
+ number += 1;
7427
+ key = `column${number}`;
7428
+ }
7429
+ this.commit({
7430
+ ...data,
7431
+ columns: [
7432
+ ...data.columns,
7433
+ { key, label: `Column ${number}`, type: 'string' },
7434
+ ],
7435
+ rows: data.rows.map(row => ({ ...row, [key]: null })),
7436
+ });
7437
+ }
7438
+ addRow() {
7439
+ if (!this.canEdit()) {
7440
+ return;
7441
+ }
7442
+ const row = {};
7443
+ for (const column of this.data().columns) {
7444
+ row[column.key] = null;
7445
+ }
7446
+ this.commit({ ...this.data(), rows: [...this.data().rows, row] });
7447
+ }
7448
+ deleteRow(rowIndex) {
7449
+ if (!this.canEdit() ||
7450
+ !globalThis.confirm('Delete this row? This cannot be undone.')) {
7451
+ return;
7452
+ }
7453
+ this.commit({
7454
+ ...this.data(),
7455
+ rows: this.data().rows.filter((_, index) => index !== rowIndex),
7456
+ });
7457
+ }
7458
+ duplicateRow(rowIndex) {
7459
+ if (!this.canEdit()) {
7460
+ return;
7461
+ }
7462
+ const rows = [...this.data().rows];
7463
+ rows.splice(rowIndex + 1, 0, { ...rows[rowIndex] });
7464
+ this.commit({ ...this.data(), rows });
7465
+ }
7466
+ deleteColumn(columnIndex) {
7467
+ if (!this.canEdit() ||
7468
+ !globalThis.confirm('Delete this column and its cell values? This cannot be undone.')) {
7469
+ return;
7470
+ }
7471
+ const column = this.data().columns[columnIndex];
7472
+ const rows = this.data().rows.map(row => {
7473
+ const { [column.key]: _removed, ...remaining } = row;
7474
+ return remaining;
7475
+ });
7476
+ this.commit({
7477
+ ...this.data(),
7478
+ columns: this.data().columns.filter((_, index) => index !== columnIndex),
7479
+ rows,
7480
+ });
7481
+ }
7482
+ renameColumn(columnIndex, field, value) {
7483
+ if (!this.canEdit()) {
7484
+ return;
7485
+ }
7486
+ const columns = this.data().columns.map(column => ({ ...column }));
7487
+ const previousKey = columns[columnIndex].key;
7488
+ const nextValue = field === 'key' ? value.trim() : value;
7489
+ const nextKey = field === 'key' ? nextValue : previousKey;
7490
+ if (field === 'key' &&
7491
+ (!/^[a-z][A-Za-z0-9]*$/.test(nextKey) ||
7492
+ ['__proto__', 'prototype', 'constructor'].includes(nextKey) ||
7493
+ columns.some((column, index) => index !== columnIndex && column.key === nextKey))) {
7494
+ this.validationError.set('Column keys must be unique, safe camelCase identifiers.');
7495
+ return;
7496
+ }
7497
+ columns[columnIndex] = { ...columns[columnIndex], [field]: nextValue };
7498
+ const rows = field === 'key' && previousKey !== nextKey
7499
+ ? this.data().rows.map(row => {
7500
+ const { [previousKey]: value, ...remaining } = row;
7501
+ return { ...remaining, [nextKey]: value };
7502
+ })
7503
+ : this.data().rows;
7504
+ this.commit({ ...this.data(), columns, rows });
7505
+ }
7506
+ setColumnType(columnIndex, type) {
7507
+ if (!this.canEdit()) {
7508
+ return;
7509
+ }
7510
+ const columns = this.data().columns.map(column => ({ ...column }));
7511
+ columns[columnIndex] = { ...columns[columnIndex], type };
7512
+ this.commit({ ...this.data(), columns });
7513
+ }
7514
+ setCell(rowIndex, column, event) {
7515
+ if (!this.canEdit()) {
7516
+ return;
7517
+ }
7518
+ const target = event.target;
7519
+ let value = target.value;
7520
+ if (column.type === 'number') {
7521
+ value = target.value === '' ? null : Number(target.value);
7522
+ if (value !== null && !Number.isFinite(value)) {
7523
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter a valid number.`);
7524
+ return;
7525
+ }
7526
+ }
7527
+ else if (column.type === 'boolean') {
7528
+ value = target.checked;
7529
+ }
7530
+ else if (column.type === 'date') {
7531
+ value = target.value
7532
+ ? new Date(`${target.value}T00:00:00.000Z`).toISOString().slice(0, 10)
7533
+ : null;
7534
+ }
7535
+ else if (column.type === 'json') {
7536
+ if (!target.value.trim()) {
7537
+ value = null;
7538
+ }
7539
+ else {
7540
+ try {
7541
+ value = JSON.parse(target.value);
7542
+ }
7543
+ catch {
7544
+ this.validationError.set(`Row ${rowIndex + 1}, ${column.label || column.key}: enter valid JSON.`);
7545
+ return;
7546
+ }
7547
+ }
7548
+ }
7549
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: value } : row);
7550
+ this.commit({ ...this.data(), rows });
7551
+ }
7552
+ onCellKeydown(event, row, column) {
7553
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
7554
+ const target = event.target;
7555
+ if (target.tagName === 'TEXTAREA') {
7556
+ return;
7557
+ }
7558
+ if (target instanceof HTMLInputElement &&
7559
+ target.type !== 'checkbox' &&
7560
+ (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
7561
+ const atBoundary = event.key === 'ArrowLeft'
7562
+ ? target.selectionStart === 0
7563
+ : target.selectionStart === target.value.length;
7564
+ if (!atBoundary) {
7565
+ return;
7566
+ }
7567
+ }
7568
+ event.preventDefault();
7569
+ const next = {
7570
+ row: row +
7571
+ (event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0),
7572
+ column: column +
7573
+ (event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0),
7574
+ };
7575
+ this.focusCell(next.row, next.column);
7576
+ return;
7577
+ }
7578
+ if (event.target.tagName === 'TEXTAREA' &&
7579
+ event.key === 'Enter' &&
7580
+ !(event.ctrlKey || event.metaKey)) {
7581
+ return;
7582
+ }
7583
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'z') {
7584
+ event.preventDefault();
7585
+ event.shiftKey ? this.redo() : this.undo();
7586
+ return;
7587
+ }
7588
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'y') {
7589
+ event.preventDefault();
7590
+ this.redo();
7591
+ return;
7592
+ }
7593
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') {
7594
+ event.preventDefault();
7595
+ const value = this.cellValue(row, this.data().columns[column]);
7596
+ void (navigator.clipboard?.writeText(value) ??
7597
+ Promise.reject(new Error('Clipboard access is unavailable.'))).catch(() => {
7598
+ this.validationError.set('Could not access the clipboard.');
7599
+ });
7600
+ return;
7601
+ }
7602
+ if (event.key === 'Escape') {
7603
+ event.target.blur();
7604
+ return;
7605
+ }
7606
+ if (event.key === 'Tab' || event.key === 'Enter') {
7607
+ event.preventDefault();
7608
+ const direction = event.shiftKey ? -1 : 1;
7609
+ let next = { row, column };
7610
+ if (event.key === 'Tab') {
7611
+ let nextColumn = column + direction;
7612
+ let nextRow = row;
7613
+ if (nextColumn >= this.data().columns.length) {
7614
+ nextColumn = 0;
7615
+ nextRow += 1;
7616
+ }
7617
+ else if (nextColumn < 0) {
7618
+ nextColumn = this.data().columns.length - 1;
7619
+ nextRow -= 1;
7620
+ }
7621
+ next = { row: nextRow, column: nextColumn };
7622
+ }
7623
+ else {
7624
+ next = { row: row + direction, column };
7625
+ }
7626
+ if (event.key === 'Tab' &&
7627
+ !event.shiftKey &&
7628
+ next.row === this.data().rows.length) {
7629
+ this.addRow();
7630
+ }
7631
+ this.activeCell.set(next);
7632
+ this.focusCell(next.row, next.column);
7633
+ }
7634
+ else if (event.key === 'Backspace' || event.key === 'Delete') {
7635
+ if (!['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
7636
+ event.preventDefault();
7637
+ this.clearCell(row, column);
7638
+ }
7639
+ }
7640
+ }
7641
+ onCellPaste(event, rowIndex, columnIndex) {
7642
+ if (!this.canEdit()) {
7643
+ return;
7644
+ }
7645
+ const text = event.clipboardData?.getData('text/plain');
7646
+ if (!text || (!text.includes('\t') && !/[\r\n]/.test(text))) {
7647
+ return;
7648
+ }
7649
+ event.preventDefault();
7650
+ try {
7651
+ const matrix = parseClipboardMatrix(text);
7652
+ const columns = [...this.data().columns];
7653
+ const pastedWidth = Math.max(...matrix.map(row => row.length));
7654
+ while (columns.length < columnIndex + pastedWidth &&
7655
+ columns.length < 200) {
7656
+ let number = columns.length + 1;
7657
+ let key = `column${number}`;
7658
+ while (columns.some(column => column.key === key)) {
7659
+ key = `column${++number}`;
7660
+ }
7661
+ columns.push({ key, label: `Column ${number}`, type: 'string' });
7662
+ }
7663
+ if (columns.length < columnIndex + pastedWidth) {
7664
+ throw new Error('Pasted data exceeds the 200-column table limit.');
7665
+ }
7666
+ const rows = [...this.data().rows];
7667
+ while (rows.length < rowIndex + matrix.length && rows.length < 10_000) {
7668
+ const newRow = {};
7669
+ for (const column of columns) {
7670
+ newRow[column.key] = null;
7671
+ }
7672
+ rows.push(newRow);
7673
+ }
7674
+ if (rows.length < rowIndex + matrix.length) {
7675
+ throw new Error('Pasted data exceeds the 10,000-row table limit.');
7676
+ }
7677
+ matrix.forEach((values, pastedRowIndex) => {
7678
+ values.forEach((rawValue, pastedColumnIndex) => {
7679
+ const targetColumn = columnIndex + pastedColumnIndex;
7680
+ const column = columns[targetColumn];
7681
+ rows[rowIndex + pastedRowIndex] = {
7682
+ ...rows[rowIndex + pastedRowIndex],
7683
+ [column.key]: parsePastedCell(rawValue, column),
7684
+ };
7685
+ });
7686
+ });
7687
+ this.commit({ version: 1, columns, rows });
7688
+ }
7689
+ catch (error) {
7690
+ this.validationError.set(error instanceof Error ? error.message : 'The pasted data is invalid.');
7691
+ }
7692
+ }
7693
+ onRowDrop(event) {
7694
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7695
+ return;
7696
+ }
7697
+ const rows = [...this.data().rows];
7698
+ moveItemInArray(rows, event.previousIndex, event.currentIndex);
7699
+ this.commit({ ...this.data(), rows });
7700
+ }
7701
+ onColumnDrop(event) {
7702
+ if (!this.canEdit() || event.previousIndex === event.currentIndex) {
7703
+ return;
7704
+ }
7705
+ const columns = [...this.data().columns];
7706
+ moveItemInArray(columns, event.previousIndex, event.currentIndex);
7707
+ this.commit({ ...this.data(), columns });
7708
+ }
7709
+ undo() {
7710
+ if (!this.canEdit()) {
7711
+ return;
7712
+ }
7713
+ const previous = this.undoStack.pop();
7714
+ if (!previous) {
7715
+ return;
7716
+ }
7717
+ this.redoStack.push(this.data());
7718
+ this.data.set(previous);
7719
+ this.validationError.set('');
7720
+ }
7721
+ redo() {
7722
+ if (!this.canEdit()) {
7723
+ return;
7724
+ }
7725
+ const next = this.redoStack.pop();
7726
+ if (!next) {
7727
+ return;
7728
+ }
7729
+ this.undoStack.push(this.data());
7730
+ this.data.set(next);
7731
+ this.validationError.set('');
7732
+ }
7733
+ async importFile(event) {
7734
+ const inputElement = event.target;
7735
+ const file = inputElement.files?.[0];
7736
+ inputElement.value = '';
7737
+ if (!file || !this.canEdit()) {
7738
+ return;
7739
+ }
7740
+ try {
7741
+ if (file.size > TAON_SPREADSHEET_MAX_BYTES) {
7742
+ throw new Error('Import files must be 10 MB or smaller.');
7743
+ }
7744
+ const text = await file.text();
7745
+ const imported = /\.json$/i.test(file.name)
7746
+ ? importSpreadsheetJson(text)
7747
+ : /\.csv$/i.test(file.name)
7748
+ ? parseSpreadsheetCsv(text)
7749
+ : (() => {
7750
+ throw new Error('Only JSON and CSV files are supported.');
7751
+ })();
7752
+ this.importFormat.set(/\.json$/i.test(file.name) ? 'JSON' : 'CSV');
7753
+ this.importMode.set('replace');
7754
+ this.pendingImport.set(imported);
7755
+ }
7756
+ catch (error) {
7757
+ this.validationError.set(error instanceof Error
7758
+ ? error.message
7759
+ : 'The selected file could not be imported.');
7760
+ }
7761
+ }
7762
+ applyImport() {
7763
+ const imported = this.pendingImport();
7764
+ if (!imported || !this.canEdit()) {
7765
+ return;
7766
+ }
7767
+ if (this.importMode() === 'replace' &&
7768
+ (this.data().rows.length > 0 || this.data().columns.length > 0) &&
7769
+ !globalThis.confirm('Replace the current table with the imported data?')) {
7770
+ return;
7771
+ }
7772
+ if (this.importMode() === 'append') {
7773
+ const currentColumns = this.data().columns;
7774
+ const compatible = currentColumns.length === imported.columns.length &&
7775
+ currentColumns.every((column, index) => column.key === imported.columns[index].key &&
7776
+ column.type === imported.columns[index].type);
7777
+ if (!compatible) {
7778
+ this.validationError.set('Append requires imported columns with the same keys, types, and order as this table.');
7779
+ return;
7780
+ }
7781
+ this.commit({
7782
+ ...this.data(),
7783
+ rows: [...this.data().rows, ...imported.rows],
7784
+ });
7785
+ }
7786
+ else {
7787
+ this.commit(imported);
7788
+ }
7789
+ this.pendingImport.set(null);
7790
+ }
7791
+ cancelImport() {
7792
+ this.pendingImport.set(null);
7793
+ }
7794
+ previewCell(row, column) {
7795
+ const value = row[column.key];
7796
+ return value === undefined
7797
+ ? ''
7798
+ : typeof value === 'string'
7799
+ ? value
7800
+ : JSON.stringify(value);
7801
+ }
7802
+ downloadJson(fullTable = false) {
7803
+ const content = JSON.stringify(fullTable ? this.data() : this.data().rows, null, 2);
7804
+ this.download(content, fullTable ? 'json-table.json' : 'table-data.json', 'application/json');
7805
+ }
7806
+ downloadCsv() {
7807
+ this.download(serializeSpreadsheetCsv(this.data()), 'table-data.csv', 'text/csv');
7808
+ }
7809
+ async copyJson() {
7810
+ try {
7811
+ if (!navigator.clipboard) {
7812
+ throw new Error('Clipboard access is unavailable.');
7813
+ }
7814
+ await navigator.clipboard.writeText(this.jsonPreview());
7815
+ this.validationError.set('');
7816
+ }
7817
+ catch {
7818
+ this.validationError.set('Could not access the clipboard. Copy the preview manually.');
7819
+ }
7820
+ }
7821
+ keyFromLabel(label) {
7822
+ return normalizeSpreadsheetKey(label);
7823
+ }
7824
+ setColumnWidth(columnIndex, value) {
7825
+ if (!this.canEdit()) {
7826
+ return;
7827
+ }
7828
+ const width = Number(value);
7829
+ if (!Number.isFinite(width) || width < 40 || width > 1000) {
7830
+ this.validationError.set('Column width must be between 40 and 1000 pixels.');
7831
+ return;
7832
+ }
7833
+ const columns = this.data().columns.map(column => ({ ...column }));
7834
+ columns[columnIndex] = { ...columns[columnIndex], width };
7835
+ this.commit({ ...this.data(), columns });
7836
+ }
7837
+ cellValue(rowIndex, column) {
7838
+ const value = this.data().rows[rowIndex]?.[column.key];
7839
+ if (column.type === 'json' && value !== null && value !== undefined) {
7840
+ return JSON.stringify(value);
7841
+ }
7842
+ return value === null || value === undefined ? '' : String(value);
7843
+ }
7844
+ trackColumn(_index, column) {
7845
+ return column.key;
7846
+ }
7847
+ clearCell(rowIndex, columnIndex) {
7848
+ const column = this.data().columns[columnIndex];
7849
+ const rows = this.data().rows.map((row, index) => index === rowIndex ? { ...row, [column.key]: null } : row);
7850
+ this.commit({ ...this.data(), rows });
7851
+ }
7852
+ focusCell(row, column) {
7853
+ if (row < 0 ||
7854
+ row >= this.data().rows.length ||
7855
+ column < 0 ||
7856
+ column >= this.data().columns.length) {
7857
+ return;
7858
+ }
7859
+ this.activeCell.set({ row, column });
7860
+ setTimeout(() => {
7861
+ document
7862
+ .querySelector(`[data-cell="${row}:${column}"]`)
7863
+ ?.focus();
7864
+ });
7865
+ }
7866
+ commit(value) {
7867
+ try {
7868
+ const validated = validateSpreadsheetData(value);
7869
+ this.undoStack.push(this.data());
7870
+ if (this.undoStack.length > 100) {
7871
+ this.undoStack.shift();
7872
+ }
7873
+ this.redoStack.length = 0;
7874
+ this.data.set(validated);
7875
+ this.validationError.set('');
7876
+ }
7877
+ catch (error) {
7878
+ this.validationError.set(error instanceof Error ? error.message : 'Table data is invalid.');
7879
+ }
7880
+ }
7881
+ canEdit() {
7882
+ return !this.readOnly() && !this.disabled();
7883
+ }
7884
+ download(content, fileName, contentType) {
7885
+ const url = URL.createObjectURL(new Blob([content], { type: contentType }));
7886
+ const anchor = document.createElement('a');
7887
+ anchor.href = url;
7888
+ anchor.download = fileName;
7889
+ anchor.click();
7890
+ URL.revokeObjectURL(url);
7891
+ }
7892
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7893
+ 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 }); }
7894
+ }
7895
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonSpreadsheetTableComponent, decorators: [{
7896
+ type: Component,
7897
+ args: [{ selector: 'taon-spreadsheet-table', standalone: true, imports: [
7898
+ CdkDrag,
7899
+ CdkDragHandle,
7900
+ CdkDropList,
7901
+ MatButtonModule,
7902
+ MatIconModule,
7903
+ MatSelectModule,
7904
+ ], 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"] }]
7905
+ }], 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"] }] } });
7906
+ function parseClipboardMatrix(value) {
7907
+ const rows = [];
7908
+ let row = [];
7909
+ let cell = '';
7910
+ let quoted = false;
7911
+ for (let index = 0; index < value.length; index += 1) {
7912
+ const character = value[index];
7913
+ if (quoted) {
7914
+ if (character === '"' && value[index + 1] === '"') {
7915
+ cell += '"';
7916
+ index += 1;
7917
+ }
7918
+ else if (character === '"') {
7919
+ quoted = false;
7920
+ }
7921
+ else {
7922
+ cell += character;
7923
+ }
7924
+ }
7925
+ else if (character === '"' && !cell) {
7926
+ quoted = true;
7927
+ }
7928
+ else if (character === '\t') {
7929
+ row.push(cell);
7930
+ cell = '';
7931
+ }
7932
+ else if (character === '\r' || character === '\n') {
7933
+ row.push(cell);
7934
+ rows.push(row);
7935
+ row = [];
7936
+ cell = '';
7937
+ if (character === '\r' && value[index + 1] === '\n') {
7938
+ index += 1;
7939
+ }
7940
+ }
7941
+ else {
7942
+ cell += character;
7943
+ }
7944
+ }
7945
+ if (quoted) {
7946
+ throw new Error('Pasted data contains an unterminated quoted value.');
7947
+ }
7948
+ if (cell.length || row.length) {
7949
+ row.push(cell);
7950
+ rows.push(row);
7951
+ }
7952
+ return rows;
7953
+ }
7954
+ function parsePastedCell(value, column) {
7955
+ if (value === '') {
7956
+ return null;
7957
+ }
7958
+ if (column.type === 'number') {
7959
+ const parsed = Number(value);
7960
+ if (!Number.isFinite(parsed)) {
7961
+ throw new Error(`"${value}" is not a valid number for ${column.label || column.key}.`);
7962
+ }
7963
+ return parsed;
7964
+ }
7965
+ if (column.type === 'boolean') {
7966
+ if (/^(true|1|yes)$/i.test(value)) {
7967
+ return true;
7968
+ }
7969
+ if (/^(false|0|no)$/i.test(value)) {
7970
+ return false;
7971
+ }
7972
+ throw new Error(`"${value}" is not a valid boolean for ${column.label || column.key}.`);
7973
+ }
7974
+ if (column.type === 'date') {
7975
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
7976
+ Number.isNaN(Date.parse(`${value}T00:00:00.000Z`))) {
7977
+ throw new Error(`"${value}" is not a valid date for ${column.label || column.key}.`);
7978
+ }
7979
+ return value;
7980
+ }
7981
+ if (column.type === 'json') {
7982
+ try {
7983
+ return JSON.parse(value);
7984
+ }
7985
+ catch {
7986
+ throw new Error(`"${value}" is not valid JSON for ${column.label || column.key}.`);
7987
+ }
7988
+ }
7989
+ return value;
7990
+ }
7991
+
6855
7992
  //#region imports
6856
7993
  //#endregion
6857
7994
  class TaonCmsWebsiteFeatureApiService extends TaonBaseAngularService {
@@ -7032,7 +8169,7 @@ class TaonCmsWebsiteFeaturesComponent {
7032
8169
  }
7033
8170
  }
7034
8171
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7035
- 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 }); }
8172
+ 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 }); }
7036
8173
  }
7037
8174
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsWebsiteFeaturesComponent, decorators: [{
7038
8175
  type: Component,
@@ -7136,7 +8273,7 @@ const TaonCmsCommentsBackofficeRoutes = [
7136
8273
  */
7137
8274
  // export default TaonCmsCommentsBackofficeRoutes;
7138
8275
 
7139
- 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-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-b7N9eQx6.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-C60-CdWA.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-5Qiho28U.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ch5so5tI.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-yUfBicV4.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-p-8AkPxT.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BnQ5iyjF.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BK-39xw9.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-BsUvV7wV.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.html.en-US.i18n.data-CY1znnmK.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-BcbuzOtn.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.html.de-DE.i18n.data-Bv4175Nc.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.ts.en-US.i18n.data-MgewtvKr.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-B1ILfsGK.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DzuYOTSn.mjs')).default } });
8276
+ 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-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-b7N9eQx6.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-C60-CdWA.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-5Qiho28U.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ch5so5tI.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-yUfBicV4.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-p-8AkPxT.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BnQ5iyjF.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BK-39xw9.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-BsUvV7wV.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.html.en-US.i18n.data-Sti5WK0f.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-D4hUUPRh.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.html.de-DE.i18n.data-GLOdlJtK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.ts.en-US.i18n.data-C2yhA0CQ.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-BSX4auNB.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DFs0vM5m.mjs')).default } });
7140
8277
  class TaonCmsPostChoiceDialogComponent {
7141
8278
  constructor() {
7142
8279
  this.t = t$f.for(this);
@@ -7444,6 +8581,7 @@ function defaultThumbnailUrl(key) {
7444
8581
  video: '<rect x="248" y="132" width="144" height="100" rx="16"/><path d="m308 158 40 24-40 24z"/>',
7445
8582
  audio: '<path d="M280 164v40m20-60v80m20-98v116m20-98v80m20-60v40"/>',
7446
8583
  attachment: '<path d="M276 124h60l32 32v84h-92z M336 124v36h32m-70 30h48m-48 22h36"/>',
8584
+ 'json-table': '<path d="M252 130h136v100H252z M252 164h136m-92-34v100m46-100v100m-90-66h136m-136 33h136"/>',
7447
8585
  };
7448
8586
  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>`;
7449
8587
  return `data:image/svg+xml,${encodeURIComponent(svg)}`;
@@ -7676,7 +8814,7 @@ class TaonCmsCourseBackofficeComponent extends TaonCmsPostTableActions {
7676
8814
  });
7677
8815
  }
7678
8816
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
7679
- 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 }); }
8817
+ 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 }); }
7680
8818
  }
7681
8819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsCourseBackofficeComponent, decorators: [{
7682
8820
  type: Component,
@@ -8386,7 +9524,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8386
9524
  args: ['paste', ['$event']]
8387
9525
  }] } });
8388
9526
 
8389
- 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-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-b7N9eQx6.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-C60-CdWA.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-5Qiho28U.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ch5so5tI.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-yUfBicV4.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-p-8AkPxT.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BnQ5iyjF.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BK-39xw9.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-BsUvV7wV.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.html.en-US.i18n.data-CY1znnmK.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-BcbuzOtn.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.html.de-DE.i18n.data-Bv4175Nc.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.ts.en-US.i18n.data-MgewtvKr.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-B1ILfsGK.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DzuYOTSn.mjs')).default } });
9527
+ 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-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-b7N9eQx6.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-C60-CdWA.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-5Qiho28U.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ch5so5tI.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-yUfBicV4.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-p-8AkPxT.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BnQ5iyjF.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BK-39xw9.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-BsUvV7wV.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.html.en-US.i18n.data-Sti5WK0f.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-D4hUUPRh.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.html.de-DE.i18n.data-GLOdlJtK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.ts.en-US.i18n.data-C2yhA0CQ.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-BSX4auNB.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DFs0vM5m.mjs')).default } });
8390
9528
  class TaonCmsPostThumbnailComponent {
8391
9529
  constructor() {
8392
9530
  this.t = t$a.for(this);
@@ -8592,8 +9730,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
8592
9730
 
8593
9731
  //#region imports
8594
9732
  //#endregion
8595
- 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-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-b7N9eQx6.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-C60-CdWA.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-5Qiho28U.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ch5so5tI.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-yUfBicV4.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-p-8AkPxT.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BnQ5iyjF.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BK-39xw9.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-BsUvV7wV.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.html.en-US.i18n.data-CY1znnmK.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-BcbuzOtn.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.html.de-DE.i18n.data-Bv4175Nc.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.ts.en-US.i18n.data-MgewtvKr.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-B1ILfsGK.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DzuYOTSn.mjs')).default } });
9733
+ 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-taon-cms-post-choice-dialog.component.html.en-US.i18n.data-b7N9eQx6.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.pl-PL.i18n.data-C60-CdWA.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.html.de-DE.i18n.data-5Qiho28U.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-choice-dialog.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.en-US.i18n.data-Ch5so5tI.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.pl-PL.i18n.data-yUfBicV4.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-choice-dialog.component.ts.de-DE.i18n.data-p-8AkPxT.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post-thumbnail.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.en-US.i18n.data-BnQ5iyjF.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.pl-PL.i18n.data-BK-39xw9.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post-thumbnail.component.ts.de-DE.i18n.data-BsUvV7wV.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.html": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.html.en-US.i18n.data-Sti5WK0f.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.html.pl-PL.i18n.data-D4hUUPRh.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.html.de-DE.i18n.data-GLOdlJtK.mjs')).default }, "src/lib/taon-posts/taon-cms-post/taon-cms-post.component.ts": { "en-US": async () => (await import('./cms-browser-taon-cms-post.component.ts.en-US.i18n.data-C2yhA0CQ.mjs')).default, "pl-PL": async () => (await import('./cms-browser-taon-cms-post.component.ts.pl-PL.i18n.data-BSX4auNB.mjs')).default, "de-DE": async () => (await import('./cms-browser-taon-cms-post.component.ts.de-DE.i18n.data-DFs0vM5m.mjs')).default } });
8596
9734
  class TaonCmsPostComponent {
9735
+ get requiredSlugs() {
9736
+ return this.contentConfig.requiredSlugs;
9737
+ }
8597
9738
  insertTable(event) {
8598
9739
  event.preventDefault();
8599
9740
  const editor = this.contentEditor();
@@ -8614,6 +9755,7 @@ class TaonCmsPostComponent {
8614
9755
  constructor() {
8615
9756
  //#region fields and getters
8616
9757
  this.t = t$7.for(this);
9758
+ this.contentType = TaonCmsContentType;
8617
9759
  this.post = input(null, ...(ngDevMode ? [{ debugName: "post" }] : []));
8618
9760
  this.mode = model('view', ...(ngDevMode ? [{ debugName: "mode" }] : []));
8619
9761
  this.changed = output();
@@ -8640,6 +9782,12 @@ class TaonCmsPostComponent {
8640
9782
  categoryId: null,
8641
9783
  tagIds: [],
8642
9784
  }, ...(ngDevMode ? [{ debugName: "draft" }] : []));
9785
+ this.jsonTableData = signal({
9786
+ version: 1,
9787
+ columns: [],
9788
+ rows: [],
9789
+ }, ...(ngDevMode ? [{ debugName: "jsonTableData" }] : []));
9790
+ this.jsonTableValidationError = signal('', ...(ngDevMode ? [{ debugName: "jsonTableValidationError" }] : []));
8643
9791
  this.postTypes = [
8644
9792
  {
8645
9793
  value: TaonCmsContentType.Normal,
@@ -8651,6 +9799,10 @@ class TaonCmsPostComponent {
8651
9799
  value: TaonCmsContentType.Attachment,
8652
9800
  label: this.t.gettext('Attachment post'),
8653
9801
  },
9802
+ {
9803
+ value: TaonCmsContentType.JsonTable,
9804
+ label: this.t.gettext('JSON Table post'),
9805
+ },
8654
9806
  ];
8655
9807
  this.currentPostType = computed(() => this.postTypes.find(type => type.value === this.draft().type) ??
8656
9808
  this.postTypes[0], ...(ngDevMode ? [{ debugName: "currentPostType" }] : []));
@@ -8662,6 +9814,8 @@ class TaonCmsPostComponent {
8662
9814
  return 'audiotrack';
8663
9815
  case TaonCmsContentType.Attachment:
8664
9816
  return 'attach_file';
9817
+ case TaonCmsContentType.JsonTable:
9818
+ return 'table_chart';
8665
9819
  default:
8666
9820
  return 'article';
8667
9821
  }
@@ -8722,6 +9876,9 @@ class TaonCmsPostComponent {
8722
9876
  this.stagedThumbnail = signal(null, ...(ngDevMode ? [{ debugName: "stagedThumbnail" }] : []));
8723
9877
  this.uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : []));
8724
9878
  this.uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
9879
+ this.mediaUploadAbort = null;
9880
+ this.childUploadAbort = null;
9881
+ this.childUploadRunning = signal(false, ...(ngDevMode ? [{ debugName: "childUploadRunning" }] : []));
8725
9882
  this.mediaPreviewUrl = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewUrl" }] : []));
8726
9883
  this.mediaPreviewLoading = signal(false, ...(ngDevMode ? [{ debugName: "mediaPreviewLoading" }] : []));
8727
9884
  this.mediaPreviewError = signal('', ...(ngDevMode ? [{ debugName: "mediaPreviewError" }] : []));
@@ -8834,7 +9991,9 @@ class TaonCmsPostComponent {
8834
9991
  !this.permissionsLoading() &&
8835
9992
  !this.permissionsError() &&
8836
9993
  !this.taxonomyLoading() &&
8837
- !this.taxonomyError(), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
9994
+ !this.taxonomyError() &&
9995
+ (this.draft().type !== TaonCmsContentType.JsonTable ||
9996
+ !this.jsonTableValidationError()), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
8838
9997
  this.statusLabel = computed(() => {
8839
9998
  const status = this.mode() === 'add'
8840
9999
  ? this.draftStatus()
@@ -8856,6 +10015,10 @@ class TaonCmsPostComponent {
8856
10015
  (this.mode() === 'edit' && !this.canSave()), ...(ngDevMode ? [{ debugName: "statusControlDisabled" }] : []));
8857
10016
  this.api = inject(TaonCmsContentApiService);
8858
10017
  this.contentConfig = inject(TaonCmsContentConfigService);
10018
+ this.selectedRequiredSlug = computed(() => {
10019
+ const slug = this.draft().slug;
10020
+ return this.requiredSlugs.includes(slug) ? slug : null;
10021
+ }, ...(ngDevMode ? [{ debugName: "selectedRequiredSlug" }] : []));
8859
10022
  this.contentTab = signal(0, ...(ngDevMode ? [{ debugName: "contentTab" }] : []));
8860
10023
  this.contentEditor = signal(null, ...(ngDevMode ? [{ debugName: "contentEditor" }] : []));
8861
10024
  this.rawContent = viewChild('rawContent', ...(ngDevMode ? [{ debugName: "rawContent" }] : []));
@@ -9066,7 +10229,10 @@ class TaonCmsPostComponent {
9066
10229
  });
9067
10230
  }
9068
10231
  //#endregion
9069
- ngOnDestroy() { }
10232
+ ngOnDestroy() {
10233
+ this.mediaUploadAbort?.abort();
10234
+ this.childUploadAbort?.abort();
10235
+ }
9070
10236
  //#region methods / load related posts
9071
10237
  loadTaxonomy(preserveSelectedTags = false) {
9072
10238
  this.taxonomyLoad?.unsubscribe();
@@ -9186,6 +10352,10 @@ class TaonCmsPostComponent {
9186
10352
  this.error.set(this.t.gettext('Please select an image for the thumbnail.'));
9187
10353
  return;
9188
10354
  }
10355
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
10356
+ this.error.set(this.fileTooLargeMessage(file));
10357
+ return;
10358
+ }
9189
10359
  this.busy.set(true);
9190
10360
  this.error.set('');
9191
10361
  try {
@@ -9769,6 +10939,33 @@ class TaonCmsPostComponent {
9769
10939
  //#endregion
9770
10940
  //#region methods / update draft
9771
10941
  updateDraft(field, value) {
10942
+ if (field === 'type' &&
10943
+ value === TaonCmsContentType.JsonTable &&
10944
+ this.draft().type !== TaonCmsContentType.JsonTable) {
10945
+ let tableData = {
10946
+ version: 1,
10947
+ columns: [],
10948
+ rows: [],
10949
+ };
10950
+ if (this.draft().body.trim()) {
10951
+ try {
10952
+ tableData = parseSpreadsheetDocument(this.draft().body);
10953
+ }
10954
+ catch {
10955
+ if (!globalThis.confirm('Changing the post to a JSON Table will replace its current body. Continue?')) {
10956
+ return;
10957
+ }
10958
+ }
10959
+ }
10960
+ this.jsonTableData.set(tableData);
10961
+ this.jsonTableValidationError.set('');
10962
+ this.draft.update(draft => ({
10963
+ ...draft,
10964
+ type: value,
10965
+ body: JSON.stringify(tableData),
10966
+ }));
10967
+ return;
10968
+ }
9772
10969
  if (field === 'title' && this.mode() === 'add') {
9773
10970
  const title = String(value);
9774
10971
  this.draft.update(draft => ({
@@ -9802,10 +10999,13 @@ class TaonCmsPostComponent {
9802
10999
  if (!this.currentPost() || this.busy() || !files.length) {
9803
11000
  return;
9804
11001
  }
9805
- this.childBatchError.set('');
11002
+ const oversized = files.filter(file => file.size > TAON_CMS_MAX_UPLOAD_BYTES);
11003
+ this.childBatchError.set(oversized.map(file => this.fileTooLargeMessage(file)).join(' '));
9806
11004
  this.childFiles.update(selected => [
9807
11005
  ...selected,
9808
- ...files.map(file => ({
11006
+ ...files
11007
+ .filter(file => file.size <= TAON_CMS_MAX_UPLOAD_BYTES)
11008
+ .map(file => ({
9809
11009
  id: ++this.childFileSequence,
9810
11010
  file,
9811
11011
  status: 'pending',
@@ -9836,10 +11036,16 @@ class TaonCmsPostComponent {
9836
11036
  const pendingFiles = this.childFiles()
9837
11037
  .map((file, index) => ({ file, index }))
9838
11038
  .filter(({ file }) => file.status !== 'success');
11039
+ const abort = new AbortController();
11040
+ this.childUploadAbort = abort;
11041
+ this.childUploadRunning.set(true);
9839
11042
  this.busy.set(true);
9840
11043
  this.childBatchError.set('');
9841
11044
  try {
9842
11045
  for (const { file: selectedFile, index } of pendingFiles) {
11046
+ if (abort.signal.aborted) {
11047
+ break;
11048
+ }
9843
11049
  const expectedTitle = this.childTitlePreviews()[index];
9844
11050
  this.updateRelatedChildFile(selectedFile.id, {
9845
11051
  status: 'processing',
@@ -9855,22 +11061,24 @@ class TaonCmsPostComponent {
9855
11061
  }
9856
11062
  let uploads = this.childFiles().find(file => file.id === selectedFile.id)?.uploads;
9857
11063
  if (!uploads?.source) {
11064
+ abort.signal.throwIfAborted();
9858
11065
  const source = await this.api.uploadMedia(selectedFile.file, percentage => this.updateRelatedChildFile(selectedFile.id, {
9859
11066
  progress: type === TaonCmsContentType.Video
9860
11067
  ? Math.round(percentage * 0.65)
9861
11068
  : Math.round(percentage * 0.8),
9862
- }));
11069
+ }), abort.signal);
9863
11070
  uploads = { ...uploads, source };
9864
11071
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9865
11072
  }
9866
11073
  if (type === TaonCmsContentType.Video && !uploads.thumbnail) {
9867
11074
  const thumbnailBlob = await generateVideoThumbnail(selectedFile.file);
9868
11075
  const extension = thumbnailBlob.type === 'image/webp' ? 'webp' : 'png';
11076
+ abort.signal.throwIfAborted();
9869
11077
  const thumbnail = await this.api.uploadMedia(new File([thumbnailBlob], `thumbnail.${extension}`, {
9870
11078
  type: thumbnailBlob.type,
9871
11079
  }), percentage => this.updateRelatedChildFile(selectedFile.id, {
9872
11080
  progress: 65 + Math.round(percentage * 0.25),
9873
- }));
11081
+ }), abort.signal);
9874
11082
  uploads = { ...uploads, thumbnail };
9875
11083
  this.updateRelatedChildFile(selectedFile.id, { uploads });
9876
11084
  }
@@ -9887,6 +11095,8 @@ class TaonCmsPostComponent {
9887
11095
  if (!sourceUpload) {
9888
11096
  throw new Error('The uploaded source file is unavailable.');
9889
11097
  }
11098
+ // Cancellation is honoured only before the child post is created.
11099
+ abort.signal.throwIfAborted();
9890
11100
  const result = await this.api.createRelatedChild(parent.id, {
9891
11101
  titlePrefix,
9892
11102
  expectedTitle,
@@ -9940,7 +11150,10 @@ class TaonCmsPostComponent {
9940
11150
  ? await this.cleanupRelatedChildUploads(selectedFile.id, uploads)
9941
11151
  : []
9942
11152
  : [];
9943
- const reason = error instanceof Error ? error.message : String(error);
11153
+ const reason = abort.signal.aborted
11154
+ ? this.t.gettext('Upload cancelled. No child post was created.')
11155
+ : error?.body?.json?.message ||
11156
+ (error instanceof Error ? error.message : String(error));
9944
11157
  const verificationError = relatedPosts
9945
11158
  ? ''
9946
11159
  : `${this.t.gettext('The creation result could not be verified; retrying will not duplicate an existing child.')} `;
@@ -9954,6 +11167,10 @@ class TaonCmsPostComponent {
9954
11167
  }
9955
11168
  }
9956
11169
  finally {
11170
+ if (this.childUploadAbort === abort) {
11171
+ this.childUploadAbort = null;
11172
+ }
11173
+ this.childUploadRunning.set(false);
9957
11174
  this.busy.set(false);
9958
11175
  this.loadRelatedPosts();
9959
11176
  }
@@ -10091,7 +11308,14 @@ class TaonCmsPostComponent {
10091
11308
  input.value = '';
10092
11309
  return;
10093
11310
  }
11311
+ if (file.size > TAON_CMS_MAX_UPLOAD_BYTES) {
11312
+ input.value = '';
11313
+ this.error.set(this.fileTooLargeMessage(file));
11314
+ return;
11315
+ }
10094
11316
  const session = this.editorSession;
11317
+ const abort = new AbortController();
11318
+ this.mediaUploadAbort = abort;
10095
11319
  this.busy.set(true);
10096
11320
  this.uploading.set(true);
10097
11321
  this.uploadProgress.set(0);
@@ -10101,7 +11325,7 @@ class TaonCmsPostComponent {
10101
11325
  if (!this.destroyRef.destroyed) {
10102
11326
  this.uploadProgress.set(percentage);
10103
11327
  }
10104
- });
11328
+ }, abort.signal);
10105
11329
  if (!this.destroyRef.destroyed && this.editorSession === session) {
10106
11330
  const fileName = object.fileName || file.name;
10107
11331
  this.mediaFileNames.update(names => ({
@@ -10130,10 +11354,20 @@ class TaonCmsPostComponent {
10130
11354
  }
10131
11355
  }
10132
11356
  catch (error) {
10133
- console.error('[taon-cms-post] Unable to upload media', error);
10134
- this.error.set(this.t.gettext('The media could not be uploaded. Please try again.'));
11357
+ if (abort.signal.aborted) {
11358
+ if (!this.destroyRef.destroyed) {
11359
+ this.error.set(this.t.gettext('The media upload was cancelled.'));
11360
+ }
11361
+ }
11362
+ else {
11363
+ console.error('[taon-cms-post] Unable to upload media', error);
11364
+ this.error.set(`${this.t.gettext('The media could not be uploaded. Please try again.')}${this.uploadErrorDetails(error)}`);
11365
+ }
10135
11366
  }
10136
11367
  finally {
11368
+ if (this.mediaUploadAbort === abort) {
11369
+ this.mediaUploadAbort = null;
11370
+ }
10137
11371
  this.busy.set(false);
10138
11372
  this.uploading.set(false);
10139
11373
  this.uploadProgress.set(0);
@@ -10141,6 +11375,26 @@ class TaonCmsPostComponent {
10141
11375
  }
10142
11376
  }
10143
11377
  //#endregion
11378
+ //#region methods / cancel media upload
11379
+ cancelMediaUpload() {
11380
+ this.mediaUploadAbort?.abort();
11381
+ }
11382
+ //#endregion
11383
+ //#region methods / cancel related child uploads
11384
+ cancelRelatedChildUploads() {
11385
+ this.childUploadAbort?.abort();
11386
+ }
11387
+ //#endregion
11388
+ //#region methods / upload error helpers
11389
+ fileTooLargeMessage(file) {
11390
+ return this.t.gettext('The file "[[ fileName ]]" is too large. The maximum upload size is 1 GB.', { fileName: file.name });
11391
+ }
11392
+ uploadErrorDetails(error) {
11393
+ const message = error?.body?.json?.message ||
11394
+ (error instanceof Error ? error.message : '');
11395
+ return message ? ` (${message})` : '';
11396
+ }
11397
+ //#endregion
10144
11398
  //#region methods / remove media
10145
11399
  removeMedia() {
10146
11400
  const media = this.mediaAttachment();
@@ -10214,6 +11468,11 @@ class TaonCmsPostComponent {
10214
11468
  this.updateDraft('slug', event.target.value);
10215
11469
  }
10216
11470
  }
11471
+ selectRequiredSlug(slug) {
11472
+ if (slug && this.requiredSlugs.includes(slug)) {
11473
+ this.updateDraft('slug', slug);
11474
+ }
11475
+ }
10217
11476
  //#endregion
10218
11477
  //#region methods / edit
10219
11478
  edit() {
@@ -10604,6 +11863,13 @@ class TaonCmsPostComponent {
10604
11863
  categoryId: post?.categoryId ?? null,
10605
11864
  tagIds: [],
10606
11865
  });
11866
+ if ((post?.type ?? TaonCmsContentType.Normal) === TaonCmsContentType.JsonTable) {
11867
+ this.loadJsonTableData(post?.body ?? null);
11868
+ }
11869
+ else {
11870
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11871
+ this.jsonTableValidationError.set('');
11872
+ }
10607
11873
  if (post &&
10608
11874
  post.relatedPosts === undefined &&
10609
11875
  this.mode() !== 'view-clean') {
@@ -10617,6 +11883,34 @@ class TaonCmsPostComponent {
10617
11883
  }
10618
11884
  }
10619
11885
  //#endregion
11886
+ setJsonTableData(value) {
11887
+ try {
11888
+ const data = validateSpreadsheetData(value);
11889
+ this.jsonTableData.set(data);
11890
+ this.draft.update(draft => ({
11891
+ ...draft,
11892
+ body: JSON.stringify(data),
11893
+ }));
11894
+ }
11895
+ catch (error) {
11896
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11897
+ }
11898
+ }
11899
+ loadJsonTableData(body) {
11900
+ try {
11901
+ const data = parseSpreadsheetDocument(body);
11902
+ this.jsonTableData.set(data);
11903
+ this.draft.update(draft => ({
11904
+ ...draft,
11905
+ body: JSON.stringify(data),
11906
+ }));
11907
+ this.jsonTableValidationError.set('');
11908
+ }
11909
+ catch (error) {
11910
+ this.jsonTableData.set({ version: 1, columns: [], rows: [] });
11911
+ this.jsonTableValidationError.set(error instanceof Error ? error.message : 'JSON Table data is invalid.');
11912
+ }
11913
+ }
10620
11914
  //#region methods / set application hook selection
10621
11915
  setApplicationHookSelection(post, hooks) {
10622
11916
  const assignedHook = post?.applicationHook !== undefined
@@ -10664,7 +11958,7 @@ class TaonCmsPostComponent {
10664
11958
  TaonPermissionApiService,
10665
11959
  TaonCmsCategoryApiService,
10666
11960
  TaonCmsTagApiService,
10667
- ], 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 }); }
11961
+ ], 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 }); }
10668
11962
  }
10669
11963
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: TaonCmsPostComponent, decorators: [{
10670
11964
  type: Component,
@@ -10690,13 +11984,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
10690
11984
  MatDividerModule,
10691
11985
  DragDropModule,
10692
11986
  TaonCmsPostThumbnailComponent,
11987
+ TaonSpreadsheetTableComponent,
11988
+ JsonPipe,
10693
11989
  ], providers: [
10694
11990
  TaonCmsContentApiService,
10695
11991
  TaonCmsContentConfigService,
10696
11992
  TaonPermissionApiService,
10697
11993
  TaonCmsCategoryApiService,
10698
11994
  TaonCmsTagApiService,
10699
- ], 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"] }]
11995
+ ], 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"] }]
10700
11996
  }], 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: [{
10701
11997
  type: ViewChild,
10702
11998
  args: ['actionButtonsTemplate']
@@ -11343,6 +12639,8 @@ var index$1 = /*#__PURE__*/Object.freeze({
11343
12639
  get CmsContext_1793664000000_postVersionLinks () { return CmsContext_1793664000000_postVersionLinks; },
11344
12640
  MIGRATIONS_CLASSES_FOR_CmsContext: MIGRATIONS_CLASSES_FOR_CmsContext,
11345
12641
  PROJECT_NPM_NAME: PROJECT_NPM_NAME,
12642
+ TAON_CMS_MAX_UPLOAD_BYTES: TAON_CMS_MAX_UPLOAD_BYTES,
12643
+ TAON_SPREADSHEET_MAX_BYTES: TAON_SPREADSHEET_MAX_BYTES,
11346
12644
  TaonCmsAbstractContext: TaonCmsAbstractContext,
11347
12645
  TaonCmsApplicationHook: TaonCmsApplicationHook,
11348
12646
  get TaonCmsApplicationHookEntity () { return TaonCmsApplicationHookEntity; },
@@ -11488,6 +12786,7 @@ var index$1 = /*#__PURE__*/Object.freeze({
11488
12786
  TaonCmsWebsiteFeaturesComponent: TaonCmsWebsiteFeaturesComponent,
11489
12787
  TaonCmsWebsiteFeaturesRoutes: TaonCmsWebsiteFeaturesRoutes,
11490
12788
  TaonRelatedPostChooserComponent: TaonRelatedPostChooserComponent,
12789
+ TaonSpreadsheetTableComponent: TaonSpreadsheetTableComponent,
11491
12790
  TaonTaonCmsWebsiteFeatureConfig: TaonTaonCmsWebsiteFeatureConfig,
11492
12791
  contentError: contentError,
11493
12792
  default: index,
@@ -11495,13 +12794,20 @@ var index$1 = /*#__PURE__*/Object.freeze({
11495
12794
  defaultThumbnailUrl: defaultThumbnailUrl,
11496
12795
  extractDocumentHtml: extractDocumentHtml,
11497
12796
  generateVideoThumbnail: generateVideoThumbnail,
12797
+ importSpreadsheetJson: importSpreadsheetJson,
12798
+ inferSpreadsheetData: inferSpreadsheetData,
12799
+ normalizeSpreadsheetKey: normalizeSpreadsheetKey,
12800
+ parseSpreadsheetCsv: parseSpreadsheetCsv,
12801
+ parseSpreadsheetDocument: parseSpreadsheetDocument,
11498
12802
  requireContentId: requireContentId,
11499
12803
  requireContentVersion: requireContentVersion,
11500
12804
  resolveTemplateVariables: resolveTemplateVariables,
12805
+ serializeSpreadsheetCsv: serializeSpreadsheetCsv,
11501
12806
  startCli: startCli,
11502
12807
  trackCmsUpload: trackCmsUpload,
11503
12808
  validateCategoryHierarchy: validateCategoryHierarchy,
11504
12809
  validateContentInput: validateContentInput,
12810
+ validateSpreadsheetData: validateSpreadsheetData,
11505
12811
  validateTaxonomy: validateTaxonomy,
11506
12812
  visiblePixelRatio: visiblePixelRatio
11507
12813
  });
@@ -11510,5 +12816,5 @@ var index$1 = /*#__PURE__*/Object.freeze({
11510
12816
  * Generated bundle index. Do not edit.
11511
12817
  */
11512
12818
 
11513
- export { APP_ID, BUILD_BASE_HREF, BUILD_FRAMEWORK_CLI_NAME, CURRENT_PACKAGE_TAON_VERSION, CURRENT_PACKAGE_VERSION, CmsContext_1792506757808_applicaitonHooks, CmsContext_1793664000000_postVersionLinks, MIGRATIONS_CLASSES_FOR_CmsContext, PROJECT_NPM_NAME, TaonCmsAbstractContext, TaonCmsApplicationHook, TaonCmsApplicationHookEntity, TaonCmsAssetPreviewComponent, TaonCmsAssetsBackofficeComponent, TaonCmsAssetsBackofficeRoutes, TaonCmsBackofficeComponent, TaonCmsBackofficeRoutes, TaonCmsBlockedUsersBackofficeComponent, TaonCmsCategoriesBackofficeRoutes, TaonCmsCategoryAbstractContext, TaonCmsCategoryApiService, TaonCmsCategoryController, TaonCmsCategoryDefaultsValues, TaonCmsCategoryEntity, TaonCmsCategoryMiddleware, TaonCmsCategoryProvider, TaonCmsCategoryRepository, TaonCmsCategoryState, TaonCmsCategorySubscriber, TaonCmsCategoryUtils, TaonCmsCommentAbstractContext, TaonCmsCommentApiService, TaonCmsCommentBlockedPhraseEntity, TaonCmsCommentBlockedUserEntity, TaonCmsCommentController, TaonCmsCommentDefaultsValues, TaonCmsCommentEntity, TaonCmsCommentMiddleware, TaonCmsCommentProvider, TaonCmsCommentRepository, TaonCmsCommentSpamReportEntity, TaonCmsCommentState, TaonCmsCommentSubscriber, TaonCmsCommentUtils, TaonCmsCommentVoteEntity, TaonCmsCommentsAllPreviewBackofficeComponent, TaonCmsCommentsAllPreviewBackofficeRoutes, TaonCmsCommentsBackofficeComponent, TaonCmsCommentsBackofficeRoutes, TaonCmsCommentsBlockedUsersRoutes, TaonCmsCommentsComponent, TaonCmsCommentsFiltersSettingsComponent, TaonCmsCommentsFiltersSettingsRoutes, TaonCmsContentAbstractContext, TaonCmsContentApiService, TaonCmsContentAssetAbstractContext, TaonCmsContentAssetApiService, TaonCmsContentAssetController, TaonCmsContentAssetDefaultsValues, TaonCmsContentAssetEntity, TaonCmsContentAssetMiddleware, TaonCmsContentAssetProvider, TaonCmsContentAssetRepository, TaonCmsContentAssetState, TaonCmsContentAssetSubscriber, TaonCmsContentAssetUtils, TaonCmsContentConfigService, TaonCmsContentController, TaonCmsContentDefaultsValues, TaonCmsContentEntity, TaonCmsContentMiddleware, TaonCmsContentPermissionAbstractContext, TaonCmsContentPermissionApiService, TaonCmsContentPermissionController, TaonCmsContentPermissionDefaultsValues, TaonCmsContentPermissionEntity, TaonCmsContentPermissionMiddleware, TaonCmsContentPermissionProvider, TaonCmsContentPermissionRepository, TaonCmsContentPermissionState, TaonCmsContentPermissionSubscriber, TaonCmsContentPermissionUtils, TaonCmsContentProvider, TaonCmsContentRepository, TaonCmsContentRevisionAbstractContext, TaonCmsContentRevisionApiService, TaonCmsContentRevisionController, TaonCmsContentRevisionDefaultsValues, TaonCmsContentRevisionEntity, TaonCmsContentRevisionMiddleware, TaonCmsContentRevisionProvider, TaonCmsContentRevisionRepository, TaonCmsContentRevisionState, TaonCmsContentRevisionSubscriber, TaonCmsContentRevisionUtils, TaonCmsContentState, TaonCmsContentStorageController, TaonCmsContentSubscriber, TaonCmsContentTagAbstractContext, TaonCmsContentTagApiService, TaonCmsContentTagController, TaonCmsContentTagDefaultsValues, TaonCmsContentTagEntity, TaonCmsContentTagMiddleware, TaonCmsContentTagProvider, TaonCmsContentTagRepository, TaonCmsContentTagState, TaonCmsContentTagSubscriber, TaonCmsContentTagUtils, TaonCmsContentType, TaonCmsContentUtils, TaonCmsCourseBackofficeComponent, TaonCmsCourseBackofficeRoutes, TaonCmsCourseContentsComponent, TaonCmsCourseCoverComponent, TaonCmsEditableDirective, TaonCmsPostChoiceDialogComponent, TaonCmsPostComponent, TaonCmsPostDetailsPageComponent, TaonCmsPostRoutes, TaonCmsPostTableActions, TaonCmsPostThumbnailComponent, TaonCmsPostsBackofficeComponent, TaonCmsPostsBackofficeRoutes, TaonCmsRevisionChooserComponent, TaonCmsTagAbstractContext, TaonCmsTagApiService, TaonCmsTagController, TaonCmsTagDefaultsValues, TaonCmsTagEntity, TaonCmsTagMiddleware, TaonCmsTagProvider, TaonCmsTagRepository, TaonCmsTagState, TaonCmsTagSubscriber, TaonCmsTagUtils, TaonCmsTagsBackofficeRoutes, TaonCmsTaxonomyBackofficeComponent, TaonCmsTaxonomyQuickCreateDialogComponent, TaonCmsWebsiteFeatureAbstractContext, TaonCmsWebsiteFeatureApiService, TaonCmsWebsiteFeatureConfigService, TaonCmsWebsiteFeatureController, TaonCmsWebsiteFeatureDefaultsValues, TaonCmsWebsiteFeatureErrors, TaonCmsWebsiteFeatureKvRepository, TaonCmsWebsiteFeatureModels, TaonCmsWebsiteFeatureProvider, TaonCmsWebsiteFeatureTranslationErorsMap, TaonCmsWebsiteFeatureUtils, TaonCmsWebsiteFeatureValidators, TaonCmsWebsiteFeaturesComponent, TaonCmsWebsiteFeaturesRoutes, TaonRelatedPostChooserComponent, TaonTaonCmsWebsiteFeatureConfig, contentError, defaultThumbnailKey, defaultThumbnailUrl, extractDocumentHtml, generateVideoThumbnail, requireContentId, requireContentVersion, resolveTemplateVariables, startCli, trackCmsUpload, validateCategoryHierarchy, validateContentInput, validateTaxonomy, visiblePixelRatio };
12819
+ export { APP_ID, BUILD_BASE_HREF, BUILD_FRAMEWORK_CLI_NAME, CURRENT_PACKAGE_TAON_VERSION, CURRENT_PACKAGE_VERSION, CmsContext_1792506757808_applicaitonHooks, CmsContext_1793664000000_postVersionLinks, MIGRATIONS_CLASSES_FOR_CmsContext, PROJECT_NPM_NAME, TAON_CMS_MAX_UPLOAD_BYTES, TAON_SPREADSHEET_MAX_BYTES, TaonCmsAbstractContext, TaonCmsApplicationHook, TaonCmsApplicationHookEntity, TaonCmsAssetPreviewComponent, TaonCmsAssetsBackofficeComponent, TaonCmsAssetsBackofficeRoutes, TaonCmsBackofficeComponent, TaonCmsBackofficeRoutes, TaonCmsBlockedUsersBackofficeComponent, TaonCmsCategoriesBackofficeRoutes, TaonCmsCategoryAbstractContext, TaonCmsCategoryApiService, TaonCmsCategoryController, TaonCmsCategoryDefaultsValues, TaonCmsCategoryEntity, TaonCmsCategoryMiddleware, TaonCmsCategoryProvider, TaonCmsCategoryRepository, TaonCmsCategoryState, TaonCmsCategorySubscriber, TaonCmsCategoryUtils, TaonCmsCommentAbstractContext, TaonCmsCommentApiService, TaonCmsCommentBlockedPhraseEntity, TaonCmsCommentBlockedUserEntity, TaonCmsCommentController, TaonCmsCommentDefaultsValues, TaonCmsCommentEntity, TaonCmsCommentMiddleware, TaonCmsCommentProvider, TaonCmsCommentRepository, TaonCmsCommentSpamReportEntity, TaonCmsCommentState, TaonCmsCommentSubscriber, TaonCmsCommentUtils, TaonCmsCommentVoteEntity, TaonCmsCommentsAllPreviewBackofficeComponent, TaonCmsCommentsAllPreviewBackofficeRoutes, TaonCmsCommentsBackofficeComponent, TaonCmsCommentsBackofficeRoutes, TaonCmsCommentsBlockedUsersRoutes, TaonCmsCommentsComponent, TaonCmsCommentsFiltersSettingsComponent, TaonCmsCommentsFiltersSettingsRoutes, TaonCmsContentAbstractContext, TaonCmsContentApiService, TaonCmsContentAssetAbstractContext, TaonCmsContentAssetApiService, TaonCmsContentAssetController, TaonCmsContentAssetDefaultsValues, TaonCmsContentAssetEntity, TaonCmsContentAssetMiddleware, TaonCmsContentAssetProvider, TaonCmsContentAssetRepository, TaonCmsContentAssetState, TaonCmsContentAssetSubscriber, TaonCmsContentAssetUtils, TaonCmsContentConfigService, TaonCmsContentController, TaonCmsContentDefaultsValues, TaonCmsContentEntity, TaonCmsContentMiddleware, TaonCmsContentPermissionAbstractContext, TaonCmsContentPermissionApiService, TaonCmsContentPermissionController, TaonCmsContentPermissionDefaultsValues, TaonCmsContentPermissionEntity, TaonCmsContentPermissionMiddleware, TaonCmsContentPermissionProvider, TaonCmsContentPermissionRepository, TaonCmsContentPermissionState, TaonCmsContentPermissionSubscriber, TaonCmsContentPermissionUtils, TaonCmsContentProvider, TaonCmsContentRepository, TaonCmsContentRevisionAbstractContext, TaonCmsContentRevisionApiService, TaonCmsContentRevisionController, TaonCmsContentRevisionDefaultsValues, TaonCmsContentRevisionEntity, TaonCmsContentRevisionMiddleware, TaonCmsContentRevisionProvider, TaonCmsContentRevisionRepository, TaonCmsContentRevisionState, TaonCmsContentRevisionSubscriber, TaonCmsContentRevisionUtils, TaonCmsContentState, TaonCmsContentStorageController, TaonCmsContentSubscriber, TaonCmsContentTagAbstractContext, TaonCmsContentTagApiService, TaonCmsContentTagController, TaonCmsContentTagDefaultsValues, TaonCmsContentTagEntity, TaonCmsContentTagMiddleware, TaonCmsContentTagProvider, TaonCmsContentTagRepository, TaonCmsContentTagState, TaonCmsContentTagSubscriber, TaonCmsContentTagUtils, TaonCmsContentType, TaonCmsContentUtils, TaonCmsCourseBackofficeComponent, TaonCmsCourseBackofficeRoutes, TaonCmsCourseContentsComponent, TaonCmsCourseCoverComponent, TaonCmsEditableDirective, TaonCmsPostChoiceDialogComponent, TaonCmsPostComponent, TaonCmsPostDetailsPageComponent, TaonCmsPostRoutes, TaonCmsPostTableActions, TaonCmsPostThumbnailComponent, TaonCmsPostsBackofficeComponent, TaonCmsPostsBackofficeRoutes, TaonCmsRevisionChooserComponent, TaonCmsTagAbstractContext, TaonCmsTagApiService, TaonCmsTagController, TaonCmsTagDefaultsValues, TaonCmsTagEntity, TaonCmsTagMiddleware, TaonCmsTagProvider, TaonCmsTagRepository, TaonCmsTagState, TaonCmsTagSubscriber, TaonCmsTagUtils, TaonCmsTagsBackofficeRoutes, TaonCmsTaxonomyBackofficeComponent, TaonCmsTaxonomyQuickCreateDialogComponent, TaonCmsWebsiteFeatureAbstractContext, TaonCmsWebsiteFeatureApiService, TaonCmsWebsiteFeatureConfigService, TaonCmsWebsiteFeatureController, TaonCmsWebsiteFeatureDefaultsValues, TaonCmsWebsiteFeatureErrors, TaonCmsWebsiteFeatureKvRepository, TaonCmsWebsiteFeatureModels, TaonCmsWebsiteFeatureProvider, TaonCmsWebsiteFeatureTranslationErorsMap, TaonCmsWebsiteFeatureUtils, TaonCmsWebsiteFeatureValidators, TaonCmsWebsiteFeaturesComponent, TaonCmsWebsiteFeaturesRoutes, TaonRelatedPostChooserComponent, TaonSpreadsheetTableComponent, TaonTaonCmsWebsiteFeatureConfig, contentError, defaultThumbnailKey, defaultThumbnailUrl, extractDocumentHtml, generateVideoThumbnail, importSpreadsheetJson, inferSpreadsheetData, normalizeSpreadsheetKey, parseSpreadsheetCsv, parseSpreadsheetDocument, requireContentId, requireContentVersion, resolveTemplateVariables, serializeSpreadsheetCsv, startCli, trackCmsUpload, validateCategoryHierarchy, validateContentInput, validateSpreadsheetData, validateTaxonomy, visiblePixelRatio };
11514
12820
  //# sourceMappingURL=cms-browser.mjs.map