diff --git a/console/package.json b/console/package.json index 8edeb8920..ad1b9172b 100644 --- a/console/package.json +++ b/console/package.json @@ -44,6 +44,8 @@ "@tailwindcss/vite": "^4.1.18", "@tanstack/react-hotkeys": "^0.4.1", "@tanstack/react-table": "^8.21.3", + "@templatical/editor": "0.19.0", + "@templatical/types": "0.19.0", "@textea/json-viewer": "^4.0.1", "@tiptap/core": "3.20.1", "@tiptap/extension-blockquote": "3.20.1", @@ -83,6 +85,7 @@ "html-react-parser": "^5.2.16", "i18next": "^23.16.8", "i18next-http-backend": "^2.7.3", + "liquidjs": "^10.27.2", "lucide-react": "^0.563.0", "maplibre-gl": "^5.19.0", "md5": "^2.3.0", diff --git a/console/pnpm-lock.yaml b/console/pnpm-lock.yaml index f0e901a3c..23804b3cd 100644 --- a/console/pnpm-lock.yaml +++ b/console/pnpm-lock.yaml @@ -128,6 +128,12 @@ importers: '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@templatical/editor': + specifier: 0.19.0 + version: 0.19.0 + '@templatical/types': + specifier: 0.19.0 + version: 0.19.0 '@textea/json-viewer': specifier: ^4.0.1 version: 4.0.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -245,6 +251,9 @@ importers: i18next-http-backend: specifier: ^2.7.3 version: 2.7.3 + liquidjs: + specifier: ^10.27.2 + version: 10.27.2 lucide-react: specifier: ^0.563.0 version: 0.563.0(react@18.3.1) @@ -335,7 +344,7 @@ importers: version: 9.39.2 '@react-router/dev': specifier: ^7.13.0 - version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3))(@types/node@22.19.9)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)) + version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3))(@types/node@22.19.9)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(supports-color@10.2.2)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)) '@testing-library/jest-dom': specifier: ^5.17.0 version: 5.17.0 @@ -365,10 +374,10 @@ importers: version: 10.0.0 '@typescript-eslint/eslint-plugin': specifier: ^8.46.2 - version: 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.46.2 - version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^5.1.3 version: 5.1.3(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)) @@ -377,25 +386,25 @@ importers: version: 2.4.1 eslint: specifier: ^9.39.2 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.2(jiti@2.6.1)) + version: 10.1.8(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1) + version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)))(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.8.1) eslint-plugin-react-hooks: specifier: ^7.0.1 - version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) + version: 7.0.1(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)) eslint-plugin-react-refresh: specifier: ^0.5.0 - version: 0.5.0(eslint@9.39.2(jiti@2.6.1)) + version: 0.5.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)) globals: specifier: ^17.3.0 version: 17.3.0 jsdom: specifier: ^28.0.0 - version: 28.0.0 + version: 28.0.0(supports-color@10.2.2) openapi-fetch: specifier: ^0.15.0 version: 0.15.0 @@ -416,13 +425,13 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.54.0 - version: 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(typescript@5.9.3) vite: specifier: ^7.3.1 version: 7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0) vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@22.19.9)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@28.0.0)(lightningcss@1.30.2)(terser@5.46.0) + version: 4.0.18(@types/node@22.19.9)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@28.0.0(supports-color@10.2.2))(lightningcss@1.30.2)(terser@5.46.0) packages: @@ -1722,30 +1731,35 @@ packages: '@react-email/body@0.2.1': resolution: {integrity: sha512-ljDiQiJDu/Fq//vSIIP0z5Nuvt4+DX1RqGasstChDGJB/14ogd4VdNS9aacoede/ZjGy3o3Qb+cxyS+XgM6SwQ==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/button@0.2.1': resolution: {integrity: sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-block@0.2.1': resolution: {integrity: sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/code-inline@0.0.6': resolution: {integrity: sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/column@0.0.14': resolution: {integrity: sha512-f+W+Bk2AjNO77zynE33rHuQhyqVICx4RYtGX9NKsGUg0wWjdGP0qAuIkhx9Rnmk4/hFMo1fUrtYNqca9fwJdHg==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -1759,60 +1773,70 @@ packages: '@react-email/container@0.0.16': resolution: {integrity: sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/font@0.0.10': resolution: {integrity: sha512-0urVSgCmQIfx5r7Xc586miBnQUVnGp3OTYUm8m5pwtQRdTRO5XrTtEfNJ3JhYhSOruV0nD8fd+dXtKXobum6tA==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/head@0.0.13': resolution: {integrity: sha512-AJg6le/08Gz4tm+6MtKXqtNNyKHzmooOCdmtqmWxD7FxoAdU1eVcizhtQ0gcnVaY6ethEyE/hnEzQxt1zu5Kog==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/heading@0.0.16': resolution: {integrity: sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/hr@0.0.12': resolution: {integrity: sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/html@0.0.12': resolution: {integrity: sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/img@0.0.12': resolution: {integrity: sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/link@0.0.13': resolution: {integrity: sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/markdown@0.0.18': resolution: {integrity: sha512-gSuYK5fsMbGk87jDebqQ6fa2fKcWlkf2Dkva8kMONqLgGCq8/0d+ZQYMEJsdidIeBo3kmsnHZPrwdFB4HgjUXg==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/preview@0.0.14': resolution: {integrity: sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -1826,12 +1850,14 @@ packages: '@react-email/row@0.0.13': resolution: {integrity: sha512-bYnOac40vIKCId7IkwuLAAsa3fKfSfqCvv6epJKmPE0JBuu5qI4FHFCl9o9dVpIIS08s/ub+Y/txoMt0dYziGw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc '@react-email/section@0.0.17': resolution: {integrity: sha512-qNl65ye3W0Rd5udhdORzTV9ezjb+GFqQQSae03NDzXtmJq6sqVXNWNiVolAjvJNypim+zGXmv6J9TcV5aNtE/w==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -1877,6 +1903,7 @@ packages: '@react-email/text@0.1.6': resolution: {integrity: sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==} engines: {node: '>=20.0.0'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: react: ^18.0 || ^19.0 || ^19.0.0-rc @@ -2004,66 +2031,79 @@ packages: resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} @@ -2145,24 +2185,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -2237,6 +2281,23 @@ packages: '@tanstack/virtual-core@3.13.18': resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==} + '@templatical/editor@0.19.0': + resolution: {integrity: sha512-QNsjU6O/82ReD/Rd7Cswrrnegc8yCL37mEij5NiNUeHKWfR9Psh5YMDb3iVhG1rwNdT5ZztPBOoJ2az99mm62g==} + peerDependencies: + '@templatical/media-library': 0.19.0 + '@templatical/quality': 0.19.0 + '@templatical/renderer': 0.19.0 + peerDependenciesMeta: + '@templatical/media-library': + optional: true + '@templatical/quality': + optional: true + '@templatical/renderer': + optional: true + + '@templatical/types@0.19.0': + resolution: {integrity: sha512-xZFXNCacBejKhZvWYqejf7KRONoar1dBtEzOB+0DTTWZ0Arm9n8bKhEmQiCrT+cLJmcQBGQGJzzxcKTYBGGBoQ==} + '@testing-library/dom@8.20.1': resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==} engines: {node: '>=12'} @@ -2925,6 +2986,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -3958,24 +4023,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -4002,6 +4071,11 @@ packages: linkifyjs@4.3.2: resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + liquidjs@10.27.2: + resolution: {integrity: sha512-kvknfAEtOHjHkAAv7GxLEJh8ghpMQm3Fc4uWVyF7hERSTsSRsdC7saWs0p5aDG7GDcWsu5o+T4232O+8KZO55w==} + engines: {node: '>=16'} + hasBin: true + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -6039,14 +6113,14 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))': dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.1': + '@eslint/config-array@0.21.1(supports-color@10.2.2)': dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3(supports-color@10.2.2) @@ -7010,7 +7084,7 @@ snapshots: dependencies: react: 18.3.1 - '@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3))(@types/node@22.19.9)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0))': + '@react-router/dev@7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3))(@types/node@22.19.9)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(supports-color@10.2.2)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0))': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.0 @@ -7041,7 +7115,7 @@ snapshots: tinyglobby: 0.2.15 valibot: 1.2.0(typescript@5.9.3) vite: 7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0) - vite-node: 3.2.4(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0) + vite-node: 3.2.4(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@10.2.2)(terser@5.46.0) optionalDependencies: '@react-router/serve': 7.13.0(react-router@7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.9.3) typescript: 5.9.3 @@ -7328,6 +7402,10 @@ snapshots: '@tanstack/virtual-core@3.13.18': {} + '@templatical/editor@0.19.0': {} + + '@templatical/types@0.19.0': {} + '@testing-library/dom@8.20.1': dependencies: '@babel/code-frame': 7.29.0 @@ -7678,15 +7756,15 @@ snapshots: dependencies: '@types/node': 22.19.18 - '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.54.0 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -7694,19 +7772,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.54.0 '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(supports-color@10.2.2)(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.54.0 debug: 4.4.3(supports-color@10.2.2) - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.54.0(supports-color@10.2.2)(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) '@typescript-eslint/types': 8.54.0 @@ -7724,13 +7802,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(typescript@5.9.3) debug: 4.4.3(supports-color@10.2.2) - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -7738,9 +7816,9 @@ snapshots: '@typescript-eslint/types@8.54.0': {} - '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.54.0(supports-color@10.2.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.54.0(supports-color@10.2.2)(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) '@typescript-eslint/types': 8.54.0 '@typescript-eslint/visitor-keys': 8.54.0 @@ -7753,13 +7831,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)) '@typescript-eslint/scope-manager': 8.54.0 '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/typescript-estree': 8.54.0(supports-color@10.2.2)(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8108,6 +8186,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} + commander@2.20.3: optional: true @@ -8365,7 +8445,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 csstype: 3.2.3 dom-serializer@2.0.0: @@ -8493,33 +8573,33 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1): + eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)))(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(prettier@3.8.1): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) prettier: 3.8.1 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1)) + eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)) - eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.0 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) hermes-parser: 0.25.1 zod: 4.3.6 zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.5.0(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-react-refresh@0.5.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) eslint-scope@8.4.0: dependencies: @@ -8530,11 +8610,11 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.39.2(jiti@2.6.1): + eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 + '@eslint/config-array': 0.21.1(supports-color@10.2.2) '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 '@eslint/eslintrc': 3.3.3 @@ -8734,7 +8814,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 get-nonce@1.0.1: {} @@ -8919,7 +8999,7 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@7.0.2: + http-proxy-agent@7.0.2(supports-color@10.2.2): dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@10.2.2) @@ -9130,7 +9210,7 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@28.0.0: + jsdom@28.0.0(supports-color@10.2.2): dependencies: '@acemir/cssom': 0.9.31 '@asamuzakjp/dom-selector': 6.7.8 @@ -9139,7 +9219,7 @@ snapshots: data-urls: 7.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 6.0.0 - http-proxy-agent: 7.0.2 + http-proxy-agent: 7.0.2(supports-color@10.2.2) https-proxy-agent: 7.0.6(supports-color@10.2.2) is-potential-custom-element-name: 1.0.1 parse5: 8.0.0 @@ -9244,6 +9324,10 @@ snapshots: linkifyjs@4.3.2: {} + liquidjs@10.27.2: + dependencies: + commander: 10.0.1 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -10215,7 +10299,7 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -10718,13 +10802,13 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1)(supports-color@10.2.2))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1)(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -10845,7 +10929,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-node@3.2.4(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0): + vite-node@3.2.4(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@10.2.2)(terser@5.46.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@10.2.2) @@ -10881,7 +10965,7 @@ snapshots: lightningcss: 1.30.2 terser: 5.46.0 - vitest@4.0.18(@types/node@22.19.9)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@28.0.0)(lightningcss@1.30.2)(terser@5.46.0): + vitest@4.0.18(@types/node@22.19.9)(happy-dom@20.5.0)(jiti@2.6.1)(jsdom@28.0.0(supports-color@10.2.2))(lightningcss@1.30.2)(terser@5.46.0): dependencies: '@vitest/expect': 4.0.18 '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)) @@ -10906,7 +10990,7 @@ snapshots: optionalDependencies: '@types/node': 22.19.9 happy-dom: 20.5.0 - jsdom: 28.0.0 + jsdom: 28.0.0(supports-color@10.2.2) transitivePeerDependencies: - jiti - less diff --git a/console/pnpm-workspace.yaml b/console/pnpm-workspace.yaml index 25c0d18a5..71c50193b 100644 --- a/console/pnpm-workspace.yaml +++ b/console/pnpm-workspace.yaml @@ -1,3 +1,9 @@ +allowBuilds: + '@clerk/shared': true + esbuild: true +minimumReleaseAgeExclude: + - '@templatical/editor@0.19.0' + - '@templatical/types@0.19.0' onlyBuiltDependencies: - '@clerk/shared' - esbuild diff --git a/console/src/components/iframe.tsx b/console/src/components/iframe.tsx index add3ada4c..f561b4136 100644 --- a/console/src/components/iframe.tsx +++ b/console/src/components/iframe.tsx @@ -24,7 +24,21 @@ export default function Iframe({ frame.contentDocument.documentElement.style.overflow = allowScroll ? "" : "hidden" } if (fullHeight) { - frame.style.minHeight = `${frame.contentWindow?.document.documentElement.scrollHeight}px` + // Collapse before measuring. A document can never report a + // scrollHeight smaller than the frame rendering it, so measuring + // in place only ever grows the frame — a short document inherits + // the height of whatever was shown before it, and the first + // measurement is pinned to the 300x150 default an iframe gets + // when no size is set. + const root = frame.contentDocument?.documentElement + if (root) { + const previous = frame.style.height + frame.style.height = "0px" + const contentHeight = root.scrollHeight + // Keep the previous height rather than collapsing to nothing + // if the document is not measurable yet. + frame.style.height = contentHeight > 0 ? `${contentHeight}px` : previous + } } } }, [allowScroll, content, fullHeight]) diff --git a/console/src/components/preview.tsx b/console/src/components/preview.tsx index 316e3ad0f..9db0f90f1 100644 --- a/console/src/components/preview.tsx +++ b/console/src/components/preview.tsx @@ -15,6 +15,7 @@ import { useTranslation } from "react-i18next" import { ProjectContext } from "../contexts" import clsx from "clsx" import { compileEmail } from "@/views/campaign/template/mail/editor/codeEditor/compileEmail" +import { templaticalPreviewHtml } from "@/lib/templatical-preview" import { getSystemPreviewProps } from "@/views/campaign/template/mail/editor/variableScope" import { EmailFrame } from "@/components/preview/EmailFrame" import { PhoneFrame } from "@/components/preview/PhoneFrame" @@ -52,6 +53,15 @@ function EmailPreviewContent({ const abortRef = useRef(null) useEffect(() => { + // A visually authored template is rendered by the backend at save time, + // and the resulting HTML is what the bundle holds. Compiling code.source + // here instead would show the JSX the template carried before it was + // switched, which is kept only so the switch stays reversible. + if (data?.type === "templatical") { + setCompiledHtml(templaticalPreviewHtml(data?.code?.bundle)) + return + } + const source = data?.code?.source if (!source) { setCompiledHtml("") @@ -80,7 +90,7 @@ function EmailPreviewContent({ return () => { abortController.abort() } - }, [data?.code?.source]) + }, [data?.type, data?.code?.bundle, data?.code?.source]) // In the small thumbnail (e.g. the Journey Send node) the Gmail-style // header chrome doesn't fit and just looks broken — show the rendered diff --git a/console/src/lib/path-suggestions.ts b/console/src/lib/path-suggestions.ts index f1c15eafc..abd5ea164 100644 --- a/console/src/lib/path-suggestions.ts +++ b/console/src/lib/path-suggestions.ts @@ -2,82 +2,92 @@ import { oapiClient } from "@/oapi/client" import type { UUID } from "@/types/common" import type { VariableSuggestions } from "@/types" +/** + * Swallow a failure from an endpoint that not every deployment serves, so one + * missing schema source cannot take the whole aggregate down. + */ +function optional(request: Promise, what: string): Promise { + return request.catch((error) => { + console.debug(`Failed to fetch ${what}:`, error) + return undefined + }) +} + /** * Fetches variable path suggestions for a project by aggregating the various * subject schema endpoints. Organization-related schemas are optional and fall * back to empty arrays when the endpoints are unavailable. * + * The six endpoints are independent, so they go out together: requested one + * after another this cost six round trips, which editors that must wait for a + * complete variable list before mounting pay in full. + * * Replaces the legacy `api.projects.pathSuggestions` aggregator; uses the typed * OpenAPI client throughout. */ export async function fetchPathSuggestions(projectId: UUID): Promise { const path = { projectID: projectId } - const { data: userEvents } = await oapiClient.GET( - "/api/admin/projects/{projectID}/subjects/user/events/schema", - { params: { path } }, - ) - const eventPaths = (userEvents?.results ?? []).map((event) => ({ + const [userEvents, users, scheduled, organizationEvents, organizationUsers, organizations] = + await Promise.all([ + oapiClient.GET("/api/admin/projects/{projectID}/subjects/user/events/schema", { + params: { path }, + }), + oapiClient.GET("/api/admin/projects/{projectID}/subjects/users/schema", { + params: { path }, + }), + optional( + oapiClient.GET("/api/admin/projects/{projectID}/subjects/user/scheduled/schema", { + params: { path }, + }), + "scheduled schemas", + ), + optional( + oapiClient.GET( + "/api/admin/projects/{projectID}/subjects/organization/events/schema", + { + params: { path }, + }, + ), + "organization event schemas", + ), + optional( + oapiClient.GET( + "/api/admin/projects/{projectID}/subjects/organizations/users/schema", + { params: { path } }, + ), + "organization user schemas", + ), + optional( + oapiClient.GET("/api/admin/projects/{projectID}/subjects/organizations/schema", { + params: { path }, + }), + "organization schemas", + ), + ]) + + const eventPaths = (userEvents.data?.results ?? []).map((event) => ({ ...event, schema: event.schema ?? [], })) as VariableSuggestions["eventPaths"] - const { data: users } = await oapiClient.GET( - "/api/admin/projects/{projectID}/subjects/users/schema", - { params: { path } }, - ) - const userPaths = (users?.results ?? []) as VariableSuggestions["userPaths"] + const userPaths = (users.data?.results ?? []) as VariableSuggestions["userPaths"] - let scheduledPaths: VariableSuggestions["scheduledPaths"] = [] - try { - const { data } = await oapiClient.GET( - "/api/admin/projects/{projectID}/subjects/user/scheduled/schema", - { params: { path } }, - ) - scheduledPaths = (data?.results ?? []).map((s) => ({ - ...s, - schema: s.schema ?? [], - })) as VariableSuggestions["scheduledPaths"] - } catch (error) { - console.debug("Failed to fetch scheduled schemas:", error) - } + const scheduledPaths = (scheduled?.data?.results ?? []).map((s) => ({ + ...s, + schema: s.schema ?? [], + })) as VariableSuggestions["scheduledPaths"] - let organizationEventPaths: VariableSuggestions["organizationEventPaths"] = [] - try { - const { data } = await oapiClient.GET( - "/api/admin/projects/{projectID}/subjects/organization/events/schema", - { params: { path } }, - ) - organizationEventPaths = (data?.results ?? []).map((event) => ({ - ...event, - schema: event.schema ?? [], - })) as VariableSuggestions["organizationEventPaths"] - } catch (error) { - console.debug("Failed to fetch organization event schemas:", error) - } + const organizationEventPaths = (organizationEvents?.data?.results ?? []).map((event) => ({ + ...event, + schema: event.schema ?? [], + })) as VariableSuggestions["organizationEventPaths"] - let organizationUserPaths: VariableSuggestions["organizationUserPaths"] = [] - try { - const { data } = await oapiClient.GET( - "/api/admin/projects/{projectID}/subjects/organizations/users/schema", - { params: { path } }, - ) - organizationUserPaths = (data?.results ?? - []) as VariableSuggestions["organizationUserPaths"] - } catch (error) { - console.debug("Failed to fetch organization user schemas:", error) - } + const organizationUserPaths = (organizationUsers?.data?.results ?? + []) as VariableSuggestions["organizationUserPaths"] - let organizationPaths: VariableSuggestions["organizationPaths"] = [] - try { - const { data } = await oapiClient.GET( - "/api/admin/projects/{projectID}/subjects/organizations/schema", - { params: { path } }, - ) - organizationPaths = (data?.results ?? []) as VariableSuggestions["organizationPaths"] - } catch (error) { - console.debug("Failed to fetch organization schemas:", error) - } + const organizationPaths = (organizations?.data?.results ?? + []) as VariableSuggestions["organizationPaths"] return { eventPaths, diff --git a/console/src/lib/templatical-preview.test.ts b/console/src/lib/templatical-preview.test.ts new file mode 100644 index 000000000..b211aec47 --- /dev/null +++ b/console/src/lib/templatical-preview.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from "vitest" +import { + resolveMergeTags, + templaticalPlainText, + templaticalPreviewHtml, +} from "./templatical-preview" + +const context = { + user: { email: "admin@localhost", data: { admin: true } }, + unsubscribe_url: "https://lunogram.com/unsubscribe", + now: "2026-07-26T00:00:00.000Z", +} + +describe("resolveMergeTags", () => { + it("resolves a nested user path", () => { + expect(resolveMergeTags("Hello {{ user.email }}", context)).toBe("Hello admin@localhost") + expect(resolveMergeTags("{{ user.data.admin }}", context)).toBe("true") + }) + + it("resolves tags outside the user scope", () => { + // These are offered by the merge-tag picker but were previously + // unresolvable, because only `user` was in the preview context. + expect(resolveMergeTags("{{ unsubscribe_url }}", context)).toBe( + "https://lunogram.com/unsubscribe", + ) + }) + + it("resolves filtered tags", () => { + // The picker's "Current Year". Handlebars could not parse the `|`, and + // because the pass covers the whole document its parse error took every + // other tag down with it. + expect(resolveMergeTags("{{ now | date: '%Y' }}", context)).toBe("2026") + }) + + it("renders an unknown path empty without affecting its neighbours", () => { + expect( + resolveMergeTags("a={{ user.email }} b={{ nope }} c={{ now | date: '%Y' }}", context), + ).toBe("a=admin@localhost b= c=2026") + }) + + it("leaves rendered email scaffolding intact", () => { + const html = [ + "", + "", + "

Hi {{ user.email }}

", + ].join("\n") + + const out = resolveMergeTags(html, context) + + expect(out).toContain("@media only screen and (min-width:480px)") + expect(out).toContain("