diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6ad36935..5e50e3657 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,7 +48,7 @@ importers: version: 7.16.0(eslint@9.6.0)(typescript@5.5.3) '@vitejs/plugin-react-swc': specifier: ^3.5.0 - version: 3.7.0(vite@5.3.3(@types/node@20.14.10)) + version: 3.7.0(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3)) eslint: specifier: ^9.6.0 version: 9.6.0 @@ -90,13 +90,13 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10) + version: 5.3.3(@types/node@20.14.10)(terser@5.31.3) vite-plugin-static-copy: specifier: ^1.0.6 - version: 1.0.6(vite@5.3.3(@types/node@20.14.10)) + version: 1.0.6(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3)) vite-plugin-svgr: specifier: ^4.2.0 - version: 4.2.0(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)) + version: 4.2.0(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3)) zustand: specifier: ^4.5.4 version: 4.5.4(@types/react@18.3.3)(react@18.3.1) @@ -136,7 +136,7 @@ importers: devDependencies: vitepress: specifier: ^1.3.0 - version: 1.3.0(@algolia/client-search@4.23.3)(@types/node@20.14.10)(@types/react@18.3.3)(axios@1.7.2)(postcss@8.4.39)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) + version: 1.3.0(@algolia/client-search@4.23.3)(@types/node@20.14.10)(@types/react@18.3.3)(axios@1.7.2)(postcss@8.4.39)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.31.3)(typescript@5.5.3) src: dependencies: @@ -327,6 +327,9 @@ importers: '@types/underscore': specifier: ^1.11.15 version: 1.11.15 + chokidar: + specifier: ^3.6.0 + version: 3.6.0 eslint: specifier: ^9.6.0 version: 9.6.0 @@ -365,13 +368,23 @@ importers: version: 5.5.3 ui: + dependencies: + '@originjs/vite-plugin-commonjs': + specifier: ^1.0.3 + version: 1.0.3 devDependencies: + ep_etherpad-lite: + specifier: workspace:../src + version: link:../src typescript: specifier: ^5.5.3 version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10) + version: 5.3.3(@types/node@20.14.10)(terser@5.31.3) + vite-plugin-require: + specifier: ^1.2.14 + version: 1.2.14(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3)) packages: @@ -694,6 +707,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.14.54': + resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} @@ -894,6 +913,9 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} @@ -923,6 +945,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@originjs/vite-plugin-commonjs@1.0.3': + resolution: {integrity: sha512-KuEXeGPptM2lyxdIEJ4R11+5ztipHoE7hy8ClZt3PYaOVQ/pyngd2alaSrPnwyFeOW1UagRBaQ752aA1dTMdOQ==} + '@playwright/test@1.45.1': resolution: {integrity: sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==} engines: {node: '>=18'} @@ -1464,6 +1489,12 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@8.56.10': + resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1769,10 +1800,66 @@ packages: '@vueuse/shared@10.11.0': resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} + '@webassemblyjs/ast@1.12.1': + resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} + + '@webassemblyjs/floating-point-hex-parser@1.11.6': + resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} + + '@webassemblyjs/helper-api-error@1.11.6': + resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} + + '@webassemblyjs/helper-buffer@1.12.1': + resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} + + '@webassemblyjs/helper-numbers@1.11.6': + resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} + + '@webassemblyjs/helper-wasm-bytecode@1.11.6': + resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} + + '@webassemblyjs/helper-wasm-section@1.12.1': + resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} + + '@webassemblyjs/ieee754@1.11.6': + resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} + + '@webassemblyjs/leb128@1.11.6': + resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} + + '@webassemblyjs/utf8@1.11.6': + resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + + '@webassemblyjs/wasm-edit@1.12.1': + resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} + + '@webassemblyjs/wasm-gen@1.12.1': + resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} + + '@webassemblyjs/wasm-opt@1.12.1': + resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} + + '@webassemblyjs/wasm-parser@1.12.1': + resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} + + '@webassemblyjs/wast-printer@1.12.1': + resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1795,6 +1882,11 @@ packages: ajv: optional: true + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1934,6 +2026,9 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -1997,6 +2092,10 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -2024,6 +2123,9 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} @@ -2291,6 +2393,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -2306,6 +2411,131 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} + esbuild-android-64@0.14.54: + resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.14.54: + resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.14.54: + resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.14.54: + resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.14.54: + resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.14.54: + resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.14.54: + resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.14.54: + resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.14.54: + resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.14.54: + resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.14.54: + resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.14.54: + resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.14.54: + resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.14.54: + resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.14.54: + resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.14.54: + resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.14.54: + resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.14.54: + resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.14.54: + resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.14.54: + resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + esbuild@0.14.54: + resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -2442,6 +2672,10 @@ packages: resolution: {integrity: sha512-3zkkU/O1agczP7szJGHmisZJS/AknfVl6mb0Zqoc95dvFsdmfK+cbhrn+Ffy0UWB1pgDJwQr7kIO3rPstWs3Dw==} engines: {node: '>=4.0'} + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + eslint-scope@8.0.1: resolution: {integrity: sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2490,6 +2724,10 @@ packages: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -2522,6 +2760,10 @@ packages: resolution: {integrity: sha512-lVCqsZYpFsuIz417h+O83I7eadNXJ3MnQavriFa52/KTwj6xPAzEYr0PvH7KTxcqyAFtW7ItoTNVXe2h7zGxlw==} engines: {node: '>=12.13.0'} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + express-rate-limit@7.3.1: resolution: {integrity: sha512-BbaryvkY4wEgDqLgD18/NSy2lDO2jTuT9Y8c1Mpx0X63Yz0sYd5zN6KPe7UvpuSVvV33T6RaE1o1IVZQjHMYgw==} engines: {node: '>= 16'} @@ -2714,6 +2956,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} @@ -2778,6 +3023,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hash-sum@2.0.0: + resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -3037,6 +3285,10 @@ packages: engines: {node: '>=10'} hasBin: true + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + jose@5.6.3: resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==} @@ -3160,6 +3412,10 @@ packages: live-plugin-manager@1.0.0: resolution: {integrity: sha512-ZzSagtubz5lrlyRZzpJ4L3KCZwdPeYKQJqz3py7hO3R4BDbt6NH2Dn70rWoQTF6ichM8P91xji1cN6ytmZ2VjQ==} + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -3255,6 +3511,9 @@ packages: merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -3386,6 +3645,9 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + netmask@2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -3822,6 +4084,10 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + security@1.0.0: resolution: {integrity: sha512-5qfoAgfRWS1sUn+fUJtdbbqM1BD/LoQGa+smPTDjf9OqHyuJqi6ewtbYL0+V1S1RaU6OCOCMWGZocIfz2YK4uw==} @@ -3918,6 +4184,9 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -4030,6 +4299,27 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + terser-webpack-plugin@5.3.10: + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.31.3: + resolution: {integrity: sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==} + engines: {node: '>=10'} + hasBin: true + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -4238,6 +4528,12 @@ packages: vfile@6.0.1: resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + vite-plugin-require@1.2.14: + resolution: {integrity: sha512-i52DfITgYKtOZyh9kOjyy4ENTQBVHG0ozTKHQdFkGAHYqZwM3Dn2c5gsA5rR7IrHQ/PQET3SMz6HkNzZ2fXCyA==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + vite-plugin-static-copy@1.0.6: resolution: {integrity: sha512-3uSvsMwDVFZRitqoWHj0t4137Kz7UynnJeq1EZlRW7e25h2068fyIZX4ORCCOAkfp1FklGxJNVJBkBOD+PZIew==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4304,6 +4600,18 @@ packages: '@vue/composition-api': optional: true + vue-loader@17.4.2: + resolution: {integrity: sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==} + peerDependencies: + '@vue/compiler-sfc': '*' + vue: '*' + webpack: ^4.1.0 || ^5.0.0-0 + peerDependenciesMeta: + '@vue/compiler-sfc': + optional: true + vue: + optional: true + vue@3.4.31: resolution: {integrity: sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==} peerDependencies: @@ -4316,6 +4624,10 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + watchpack@2.4.1: + resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} + engines: {node: '>=10.13.0'} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} @@ -4327,6 +4639,20 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + + webpack@5.93.0: + resolution: {integrity: sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -4797,6 +5123,9 @@ snapshots: '@esbuild/linux-ia32@0.23.0': optional: true + '@esbuild/linux-loong64@0.14.54': + optional: true + '@esbuild/linux-loong64@0.21.5': optional: true @@ -4932,6 +5261,11 @@ snapshots: '@jridgewell/set-array@1.2.1': {} + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec@1.4.15': {} '@jridgewell/trace-mapping@0.3.25': @@ -4967,6 +5301,10 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@originjs/vite-plugin-commonjs@1.0.3': + dependencies: + esbuild: 0.14.54 + '@playwright/test@1.45.1': dependencies: playwright: 1.45.1 @@ -5431,6 +5769,16 @@ snapshots: dependencies: '@types/ms': 0.7.34 + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 8.56.10 + '@types/estree': 1.0.5 + + '@types/eslint@8.56.10': + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + '@types/estree@1.0.5': {} '@types/express-serve-static-core@4.19.3': @@ -5682,16 +6030,16 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react-swc@3.7.0(vite@5.3.3(@types/node@20.14.10))': + '@vitejs/plugin-react-swc@3.7.0(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3))': dependencies: '@swc/core': 1.5.28 - vite: 5.3.3(@types/node@20.14.10) + vite: 5.3.3(@types/node@20.14.10)(terser@5.31.3) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-vue@5.0.5(vite@5.3.3(@types/node@20.14.10))(vue@3.4.31(typescript@5.5.3))': + '@vitejs/plugin-vue@5.0.5(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3))(vue@3.4.31(typescript@5.5.3))': dependencies: - vite: 5.3.3(@types/node@20.14.10) + vite: 5.3.3(@types/node@20.14.10)(terser@5.31.3) vue: 3.4.31(typescript@5.5.3) '@vue/compiler-core@3.4.31': @@ -5797,11 +6145,95 @@ snapshots: - '@vue/composition-api' - vue + '@webassemblyjs/ast@1.12.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + + '@webassemblyjs/floating-point-hex-parser@1.11.6': {} + + '@webassemblyjs/helper-api-error@1.11.6': {} + + '@webassemblyjs/helper-buffer@1.12.1': {} + + '@webassemblyjs/helper-numbers@1.11.6': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.11.6 + '@webassemblyjs/helper-api-error': 1.11.6 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.11.6': {} + + '@webassemblyjs/helper-wasm-section@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/wasm-gen': 1.12.1 + + '@webassemblyjs/ieee754@1.11.6': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.11.6': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.11.6': {} + + '@webassemblyjs/wasm-edit@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-wasm-section': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-opt': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/wast-printer': 1.12.1 + + '@webassemblyjs/wasm-gen@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + + '@webassemblyjs/wasm-opt@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + + '@webassemblyjs/wasm-parser@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + + '@webassemblyjs/wast-printer@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-import-attributes@1.9.5(acorn@8.12.0): + dependencies: + acorn: 8.12.0 + acorn-jsx@5.3.2(acorn@8.12.0): dependencies: acorn: 8.12.0 @@ -5818,6 +6250,10 @@ snapshots: optionalDependencies: ajv: 8.16.0 + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -6004,6 +6440,8 @@ snapshots: buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} + builtin-modules@3.3.0: {} builtins@5.1.0: @@ -6074,6 +6512,8 @@ snapshots: chownr@2.0.0: {} + chrome-trace-event@1.0.4: {} + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -6100,6 +6540,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@2.20.3: {} + component-emitter@1.3.1: {} concat-map@0.0.1: {} @@ -6394,6 +6836,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.5.4: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -6414,6 +6858,90 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 + esbuild-android-64@0.14.54: + optional: true + + esbuild-android-arm64@0.14.54: + optional: true + + esbuild-darwin-64@0.14.54: + optional: true + + esbuild-darwin-arm64@0.14.54: + optional: true + + esbuild-freebsd-64@0.14.54: + optional: true + + esbuild-freebsd-arm64@0.14.54: + optional: true + + esbuild-linux-32@0.14.54: + optional: true + + esbuild-linux-64@0.14.54: + optional: true + + esbuild-linux-arm64@0.14.54: + optional: true + + esbuild-linux-arm@0.14.54: + optional: true + + esbuild-linux-mips64le@0.14.54: + optional: true + + esbuild-linux-ppc64le@0.14.54: + optional: true + + esbuild-linux-riscv64@0.14.54: + optional: true + + esbuild-linux-s390x@0.14.54: + optional: true + + esbuild-netbsd-64@0.14.54: + optional: true + + esbuild-openbsd-64@0.14.54: + optional: true + + esbuild-sunos-64@0.14.54: + optional: true + + esbuild-windows-32@0.14.54: + optional: true + + esbuild-windows-64@0.14.54: + optional: true + + esbuild-windows-arm64@0.14.54: + optional: true + + esbuild@0.14.54: + optionalDependencies: + '@esbuild/linux-loong64': 0.14.54 + esbuild-android-64: 0.14.54 + esbuild-android-arm64: 0.14.54 + esbuild-darwin-64: 0.14.54 + esbuild-darwin-arm64: 0.14.54 + esbuild-freebsd-64: 0.14.54 + esbuild-freebsd-arm64: 0.14.54 + esbuild-linux-32: 0.14.54 + esbuild-linux-64: 0.14.54 + esbuild-linux-arm: 0.14.54 + esbuild-linux-arm64: 0.14.54 + esbuild-linux-mips64le: 0.14.54 + esbuild-linux-ppc64le: 0.14.54 + esbuild-linux-riscv64: 0.14.54 + esbuild-linux-s390x: 0.14.54 + esbuild-netbsd-64: 0.14.54 + esbuild-openbsd-64: 0.14.54 + esbuild-sunos-64: 0.14.54 + esbuild-windows-32: 0.14.54 + esbuild-windows-64: 0.14.54 + esbuild-windows-arm64: 0.14.54 + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -6632,6 +7160,11 @@ snapshots: dependencies: kebab-case: 1.0.2 + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + eslint-scope@8.0.1: dependencies: esrecurse: 4.3.0 @@ -6706,6 +7239,8 @@ snapshots: dependencies: estraverse: 5.3.0 + estraverse@4.3.0: {} + estraverse@5.3.0: {} estree-walker@2.0.2: {} @@ -6732,6 +7267,8 @@ snapshots: optionalDependencies: mime: 1.6.0 + events@3.3.0: {} + express-rate-limit@7.3.1(express@4.19.2): dependencies: express: 4.19.2 @@ -6966,6 +7503,8 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-to-regexp@0.4.1: {} + glob@8.1.0: dependencies: fs.realpath: 1.0.0 @@ -7036,6 +7575,8 @@ snapshots: dependencies: has-symbols: 1.0.3 + hash-sum@2.0.0: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -7343,6 +7884,12 @@ snapshots: filelist: 1.0.4 minimatch: 3.1.2 + jest-worker@27.5.1: + dependencies: + '@types/node': 20.14.10 + merge-stream: 2.0.0 + supports-color: 8.1.1 + jose@5.6.3: {} js-cookie@3.0.5: {} @@ -7521,6 +8068,8 @@ snapshots: transitivePeerDependencies: - supports-color + loader-runner@4.3.0: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -7615,6 +8164,8 @@ snapshots: merge-descriptors@1.0.1: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} methods@1.1.2: {} @@ -7731,6 +8282,8 @@ snapshots: negotiator@0.6.3: {} + neo-async@2.6.2: {} + netmask@2.0.2: {} nise@6.0.0: @@ -8207,6 +8760,12 @@ snapshots: dependencies: loose-envify: 1.4.0 + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + security@1.0.0: {} semver@6.3.1: {} @@ -8358,8 +8917,12 @@ snapshots: source-map-js@1.2.0: {} - source-map@0.6.1: - optional: true + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} space-separated-tokens@2.0.2: {} @@ -8492,6 +9055,22 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + terser-webpack-plugin@5.3.10(webpack@5.93.0): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.31.3 + webpack: 5.93.0 + + terser@5.31.3: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.12.0 + commander: 2.20.3 + source-map-support: 0.5.21 + text-table@0.2.0: {} threads@1.7.0: @@ -8720,26 +9299,44 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-plugin-static-copy@1.0.6(vite@5.3.3(@types/node@20.14.10)): + vite-plugin-require@1.2.14(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3)): + dependencies: + '@babel/generator': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + '@vue/compiler-sfc': 3.4.31 + vite: 5.3.3(@types/node@20.14.10)(terser@5.31.3) + vue-loader: 17.4.2(@vue/compiler-sfc@3.4.31)(webpack@5.93.0) + webpack: 5.93.0 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - supports-color + - uglify-js + - vue + - webpack-cli + + vite-plugin-static-copy@1.0.6(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.2 fs-extra: 11.2.0 picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.10) + vite: 5.3.3(@types/node@20.14.10)(terser@5.31.3) - vite-plugin-svgr@4.2.0(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)): + vite-plugin-svgr@4.2.0(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3)): dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.18.0) '@svgr/core': 8.1.0(typescript@5.5.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.3)) - vite: 5.3.3(@types/node@20.14.10) + vite: 5.3.3(@types/node@20.14.10)(terser@5.31.3) transitivePeerDependencies: - rollup - supports-color - typescript - vite@5.3.3(@types/node@20.14.10): + vite@5.3.3(@types/node@20.14.10)(terser@5.31.3): dependencies: esbuild: 0.21.5 postcss: 8.4.39 @@ -8747,15 +9344,16 @@ snapshots: optionalDependencies: '@types/node': 20.14.10 fsevents: 2.3.3 + terser: 5.31.3 - vitepress@1.3.0(@algolia/client-search@4.23.3)(@types/node@20.14.10)(@types/react@18.3.3)(axios@1.7.2)(postcss@8.4.39)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3): + vitepress@1.3.0(@algolia/client-search@4.23.3)(@types/node@20.14.10)(@types/react@18.3.3)(axios@1.7.2)(postcss@8.4.39)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(terser@5.31.3)(typescript@5.5.3): dependencies: '@docsearch/css': 3.6.0 '@docsearch/js': 3.6.0(@algolia/client-search@4.23.3)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@shikijs/core': 1.10.3 '@shikijs/transformers': 1.10.3 '@types/markdown-it': 14.1.1 - '@vitejs/plugin-vue': 5.0.5(vite@5.3.3(@types/node@20.14.10))(vue@3.4.31(typescript@5.5.3)) + '@vitejs/plugin-vue': 5.0.5(vite@5.3.3(@types/node@20.14.10)(terser@5.31.3))(vue@3.4.31(typescript@5.5.3)) '@vue/devtools-api': 7.3.5 '@vue/shared': 3.4.31 '@vueuse/core': 10.11.0(vue@3.4.31(typescript@5.5.3)) @@ -8764,7 +9362,7 @@ snapshots: mark.js: 8.11.1 minisearch: 6.3.0 shiki: 1.10.3 - vite: 5.3.3(@types/node@20.14.10) + vite: 5.3.3(@types/node@20.14.10)(terser@5.31.3) vue: 3.4.31(typescript@5.5.3) optionalDependencies: postcss: 8.4.39 @@ -8801,6 +9399,15 @@ snapshots: dependencies: vue: 3.4.31(typescript@5.5.3) + vue-loader@17.4.2(@vue/compiler-sfc@3.4.31)(webpack@5.93.0): + dependencies: + chalk: 4.1.2 + hash-sum: 2.0.0 + watchpack: 2.4.1 + webpack: 5.93.0 + optionalDependencies: + '@vue/compiler-sfc': 3.4.31 + vue@3.4.31(typescript@5.5.3): dependencies: '@vue/compiler-dom': 3.4.31 @@ -8815,12 +9422,50 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + watchpack@2.4.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + web-namespaces@2.0.1: {} web-streams-polyfill@3.3.3: {} webidl-conversions@7.0.0: {} + webpack-sources@3.2.3: {} + + webpack@5.93.0: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.12.0 + acorn-import-attributes: 1.9.5(acorn@8.12.0) + browserslist: 4.23.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.17.0 + es-module-lexer: 1.5.4 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(webpack@5.93.0) + watchpack: 2.4.1 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 diff --git a/src/node/hooks/express/specialpages.ts b/src/node/hooks/express/specialpages.ts index 8ea64962a..6e2b03df9 100644 --- a/src/node/hooks/express/specialpages.ts +++ b/src/node/hooks/express/specialpages.ts @@ -10,10 +10,8 @@ const settings = require('../../utils/Settings'); import util from 'node:util'; const webaccess = require('./webaccess'); const plugins = require('../../../static/js/pluginfw/plugin_defs'); -import {hash, createHash} from 'node:crypto' - -import {buildSync} from 'esbuild' +import {build, buildSync} from 'esbuild' exports.expressPreSession = async (hookName:string, {app}:any) => { // This endpoint is intended to conform to: // https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html @@ -77,6 +75,54 @@ exports.expressPreSession = async (hookName:string, {app}:any) => { }); }; + + +const convertTypescript = (content: string) => { + const outputRaw = buildSync({ + stdin: { + contents: content, + resolveDir: path.join(settings.root, 'var','js'), + loader: 'js' + }, + bundle: true, // Bundle the files together + minify: process.env.NODE_ENV === "production", // Minify the output + sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps + sourceRoot: settings.root+"/src/static/js/", + target: ['es2020'], // Target ECMAScript version + metafile: true, + write: false, // Do not write to file system, + }) + const output = outputRaw.outputFiles[0].text + + return { + output, + hash: outputRaw.outputFiles[0].hash.replaceAll('/','2') + } +} + + +const convertTypescriptWatched = (content: string, cb: (output:string, hash: string)=>void) => { + build({ + stdin: { + contents: content, + resolveDir: path.join(settings.root, 'var','js'), + loader: 'js' + }, + bundle: true, // Bundle the files together + minify: process.env.NODE_ENV === "production", // Minify the output + sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps + sourceRoot: settings.root+"/src/static/js/", + target: ['es2020'], // Target ECMAScript version + metafile: true, + write: false, // Do not write to file system, + }).then((outputRaw) => { + cb( + outputRaw.outputFiles[0].text, + outputRaw.outputFiles[0].hash.replaceAll('/','2') + ) + }) +} + exports.expressCreateServer = async (hookName: string, args: any, cb: Function) => { // serve index.html under / args.app.get('/', (req: any, res: any) => { @@ -98,7 +144,6 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function) settings, }) - const timeSliderString = eejs.require('ep_etherpad-lite/templates/timeSliderBootstrap.js', { pluginModules: (() => { const pluginModules = new Set(); @@ -117,97 +162,128 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function) const outdir = path.join(settings.root, 'var','js') - const padWriteResult = buildSync({ - stdin: { - contents: padString, - resolveDir: path.join(settings.root, 'var','js'), - loader: 'js' - }, // Entry file(s) - bundle: true, // Bundle the files together - minify: process.env.NODE_ENV === "production", // Minify the output - sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps - sourceRoot: settings.root+"/src/static/js/", - target: ['es2020'], // Target ECMAScript version - metafile: true, - write: false, // Do not write to file system, - }) + let fileNamePad: string + let fileNameTimeSlider: string + if(process.env.NODE_ENV === "production"){ + const padSliderWrite = convertTypescript(padString) + const timeSliderWrite = convertTypescript(timeSliderString) - const outputPadJS = padWriteResult.outputFiles[0].text + fileNamePad = `padbootstrap-${padSliderWrite.hash}.min.js` + fileNameTimeSlider = `timeSliderBootstrap-${timeSliderWrite.hash}.min.js` + const pathNamePad = path.join(outdir, fileNamePad) + const pathNameTimeSlider = path.join(outdir, fileNameTimeSlider) - const timeSliderWrite = buildSync({ - stdin: { - contents: timeSliderString, - resolveDir: path.join(settings.root, 'var','js'), - loader: 'js' - }, - bundle: true, // Bundle the files together - minify: process.env.NODE_ENV === "production", // Minify the output - sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps - sourceRoot: settings.root+"/src/static/js/", - target: ['es2020'], // Target ECMAScript version - metafile: true, - write: false, // Do not write to file system, - }) + if (!fs.existsSync(pathNamePad)) { + fs.writeFileSync(pathNamePad, padSliderWrite.output); + } - const outputTimeslider = timeSliderWrite.outputFiles[0].text + if (!fs.existsSync(pathNameTimeSlider)) { + fs.writeFileSync(pathNameTimeSlider,timeSliderWrite.output) + } - const hash = padWriteResult.outputFiles[0].hash - const hashTimeSlider = timeSliderWrite.outputFiles[0].hash + args.app.get("/"+fileNamePad, (req: any, res: any) => { + res.sendFile(pathNamePad) + }) - const fileNamePad = `padbootstrap-${hash}.min.js` - const fileNameTimeSlider = `timeSliderBootstrap-${hashTimeSlider}.min.js` - const pathNamePad = path.join(outdir, fileNamePad) - const pathNameTimeSlider = path.join(outdir, fileNameTimeSlider) - - if (!fs.existsSync(pathNamePad)) { - fs.writeFileSync(pathNamePad, outputPadJS); - } - - if (!fs.existsSync(pathNameTimeSlider)) { - fs.writeFileSync(pathNameTimeSlider,outputTimeslider) - } - - args.app.get("/"+fileNamePad, (req: any, res: any) => { - res.sendFile(pathNamePad) - }) - - args.app.get("/"+fileNameTimeSlider, (req: any, res: any) => { - res.sendFile(pathNameTimeSlider) - }) + args.app.get("/"+fileNameTimeSlider, (req: any, res: any) => { + res.sendFile(pathNameTimeSlider) + }) - // serve pad.html under /p - args.app.get('/p/:pad', (req: any, res: any, next: Function) => { - // The below might break for pads being rewritten - const isReadOnly = !webaccess.userCanModify(req.params.pad, req); + // serve pad.html under /p + args.app.get('/p/:pad', (req: any, res: any, next: Function) => { + // The below might break for pads being rewritten + const isReadOnly = !webaccess.userCanModify(req.params.pad, req); - hooks.callAll('padInitToolbar', { - toolbar, - isReadOnly + hooks.callAll('padInitToolbar', { + toolbar, + isReadOnly + }); + + // can be removed when require-kernel is dropped + res.header('Feature-Policy', 'sync-xhr \'self\''); + const content = eejs.require('ep_etherpad-lite/templates/pad.html', { + req, + toolbar, + isReadOnly, + entrypoint: "/"+fileNamePad + }) + res.send(content); }); - // can be removed when require-kernel is dropped - res.header('Feature-Policy', 'sync-xhr \'self\''); - res.send(eejs.require('ep_etherpad-lite/templates/pad.html', { - req, - toolbar, - isReadOnly, - entrypoint: "/"+fileNamePad - })); - }); + // serve timeslider.html under /p/$padname/timeslider + args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => { + hooks.callAll('padInitToolbar', { + toolbar, + }); - // serve timeslider.html under /p/$padname/timeslider - args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => { - hooks.callAll('padInitToolbar', { - toolbar, + res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', { + req, + toolbar, + entrypoint: "/"+fileNameTimeSlider + })); + }); + } else { + const chokidar = await import('chokidar') + const map = new Map() + const watcher = chokidar.watch(path.join(settings.root, 'src','static','js')); + watcher.on('change', path => { + console.log(`File ${path} has been changed`); + convertTypescriptWatched(padString, (output, hash)=>{ + console.log("New hash is", hash) + map.set('output', output) + map.set('fileNamePad', `padbootstrap-${hash}.js`) + }); + convertTypescriptWatched(timeSliderString, (output, hash)=>{ + // serve timeslider.html under /p/$padname/timeslider + console.log("New hash is", hash) + + args.app.get('/watch/timeslider', (req: any, res: any) => { + res.header('Content-Type', 'application/javascript'); + res.send(output) + }) + }); }); - res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', { - req, - toolbar, - entrypoint: "/"+fileNameTimeSlider - })); - }); + args.app.get('/watch/pad', (req: any, res: any) => { + res.header('Content-Type', 'application/javascript'); + res.send(map.get('output')) + }) + // serve pad.html under /p + args.app.get('/p/:pad', (req: any, res: any, next: Function) => { + console.log("Reloading pad") + // The below might break for pads being rewritten + const isReadOnly = !webaccess.userCanModify(req.params.pad, req); + + hooks.callAll('padInitToolbar', { + toolbar, + isReadOnly + }); + + // can be removed when require-kernel is dropped + res.header('Feature-Policy', 'sync-xhr \'self\''); + const content = eejs.require('ep_etherpad-lite/templates/pad.html', { + req, + toolbar, + isReadOnly, + entrypoint: map.get('fileNamePad') + }) + res.send(content); + }); + args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => { + hooks.callAll('padInitToolbar', { + toolbar, + }); + let timeSliderFileName = "/watch/timeslider?hash="+map.get('fileNamePad') + + res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', { + req, + toolbar, + entrypoint: timeSliderFileName + })); + }); + } + // The client occasionally polls this endpoint to get an updated expiration for the express_sid // cookie. This handler must be installed after the express-session middleware. diff --git a/src/package.json b/src/package.json index ad5c95a69..61ce68e68 100644 --- a/src/package.json +++ b/src/package.json @@ -109,7 +109,8 @@ "sinon": "^18.0.0", "split-grid": "^1.0.11", "supertest": "^7.0.0", - "typescript": "^5.5.3" + "typescript": "^5.5.3", + "chokidar": "^3.6.0" }, "engines": { "node": ">=18.18.2", diff --git a/src/static/js/broadcast.js b/src/static/js/broadcast.js index cd2211ae1..2163fd78e 100644 --- a/src/static/js/broadcast.js +++ b/src/static/js/broadcast.js @@ -32,6 +32,9 @@ const colorutils = require('./colorutils').colorutils; const _ = require('./underscore'); const hooks = require('./pluginfw/hooks'); +import html10n from './vendors/html10n'; + + // These parameters were global, now they are injected. A reference to the // Timeslider controller would probably be more appropriate. const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) => { diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index 80afffe28..848ba06cf 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -26,6 +26,7 @@ const _ = require('./underscore'); const padmodals = require('./pad_modals').padmodals; const colorutils = require('./colorutils').colorutils; +import html10n from './vendors/html10n'; const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => { let BroadcastSlider; diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 9dee3be4f..d32a62c7a 100755 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -21,10 +21,12 @@ const padcookie = require('./pad_cookie').padcookie; const Tinycon = require('tinycon/tinycon'); const hooks = require('./pluginfw/hooks'); const padeditor = require('./pad_editor').padeditor; +import html10n from './vendors/html10n'; // Removes diacritics and lower-cases letters. https://stackoverflow.com/a/37511463 const normalize = (s) => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); + exports.chat = (() => { let isStuck = false; let userAndChat = false; diff --git a/src/static/js/l10n.js b/src/static/js/l10n.js deleted file mode 100644 index ab80ef3ca..000000000 --- a/src/static/js/l10n.js +++ /dev/null @@ -1,17 +0,0 @@ -import html10n from '../js/vendors/html10n'; - - -((document) => { - // Set language for l10n - let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/); - if (language) language = language[1]; - - html10n.bind('indexed', () => { - html10n.localize([language, navigator.language, navigator.userLanguage, 'en']); - }); - - html10n.bind('localized', () => { - document.documentElement.lang = html10n.getLanguage(); - document.documentElement.dir = html10n.getDirection(); - }); -})(document); diff --git a/src/static/js/l10n.ts b/src/static/js/l10n.ts new file mode 100644 index 000000000..b55c7f007 --- /dev/null +++ b/src/static/js/l10n.ts @@ -0,0 +1,18 @@ +import html10n from '../js/vendors/html10n'; + + +((document) => { + // Set language for l10n + let regexpLang: string | undefined; + let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/); + if (language) regexpLang = language[1]; + + html10n.mt.bind('indexed', () => { + html10n.localize([regexpLang, navigator.language, 'en']); + }); + + html10n.mt.bind('localized', () => { + document.documentElement.lang = html10n.getLanguage()!; + document.documentElement.dir = html10n.getDirection()!; + }); +})(document); diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 8740e2fb2..bcf5f5621 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -30,6 +30,8 @@ require('./vendors/jquery'); require('./vendors/farbtastic'); require('./vendors/gritter'); +import html10n from './vendors/html10n' + const Cookies = require('./pad_utils').Cookies; const chat = require('./chat').chat; const getCollabClient = require('./collab_client').getCollabClient; @@ -136,7 +138,7 @@ const getParameters = [ name: 'lang', checkVal: null, callback: (val) => { - window.html10n.localize([val, 'en']); + html10n.localize([val, 'en']); Cookies.set('language', val); }, }, diff --git a/src/static/js/pad_automatic_reconnect.js b/src/static/js/pad_automatic_reconnect.js index 576e0d350..03fc91432 100644 --- a/src/static/js/pad_automatic_reconnect.js +++ b/src/static/js/pad_automatic_reconnect.js @@ -1,4 +1,5 @@ 'use strict'; +import html10n from './vendors/html10n'; exports.showCountDownTimerToReconnectOnModal = ($modal, pad) => { if (clientVars.automaticReconnectionTimeout && $modal.is('.with_reconnect_timer')) { diff --git a/src/static/js/pad_editor.js b/src/static/js/pad_editor.js index c02832835..47a250734 100644 --- a/src/static/js/pad_editor.js +++ b/src/static/js/pad_editor.js @@ -25,6 +25,7 @@ const Cookies = require('./pad_utils').Cookies; const padcookie = require('./pad_cookie').padcookie; const padutils = require('./pad_utils').padutils; const Ace2Editor = require('./ace').Ace2Editor; +import html10n from '../js/vendors/html10n' const padeditor = (() => { let pad = undefined; @@ -98,7 +99,7 @@ const padeditor = (() => { $('#languagemenu').val(html10n.getLanguage()); $('#languagemenu').on('change', () => { Cookies.set('language', $('#languagemenu').val()); - window.html10n.localize([$('#languagemenu').val(), 'en']); + html10n.localize([$('#languagemenu').val(), 'en']); if ($('select').niceSelect) { $('select').niceSelect('update'); } diff --git a/src/static/js/pad_impexp.js b/src/static/js/pad_impexp.js index 4d607ff83..3aca9fb7c 100644 --- a/src/static/js/pad_impexp.js +++ b/src/static/js/pad_impexp.js @@ -22,6 +22,9 @@ * limitations under the License. */ +import html10n from './vendors/html10n'; + + const padimpexp = (() => { let pad; diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index b689ae1ad..a0cbd4b44 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -18,7 +18,7 @@ const padutils = require('./pad_utils').padutils; const hooks = require('./pluginfw/hooks'); - +import html10n from './vendors/html10n'; let myUserInfo = {}; let colorPickerOpen = false; diff --git a/src/static/js/vendors/farbtastic.js b/src/static/js/vendors/farbtastic.js index 5a187ea47..5d0c4718c 100644 --- a/src/static/js/vendors/farbtastic.js +++ b/src/static/js/vendors/farbtastic.js @@ -7,6 +7,7 @@ // Licensed under the terms of the GNU General Public License v2.0: // https://github.com/mattfarina/farbtastic/blob/71ca15f4a09c8e5a08a1b0d1cf37ef028adf22f0/LICENSE.txt // edited by Sebastian Castro on 2020-04-06 + (function ($) { var __debug = false; diff --git a/src/static/js/vendors/html10n.js b/src/static/js/vendors/html10n.js deleted file mode 100644 index 6fe5e307f..000000000 --- a/src/static/js/vendors/html10n.js +++ /dev/null @@ -1,1060 +0,0 @@ -// WARNING: This file has been modified from the Original - -/** - * Copyright (c) 2012 Marcel Klehr - * Copyright (c) 2011-2012 Fabien Cazenave, Mozilla - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -export let html10n = (function(window, document, undefined) { - - // fix console - (function() { - const noop = function () { - }; - const names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; - const console = (window.console = window.console || {}); - for (let i = 0; i < names.length; ++i) { - if (!console[names[i]]) { - console[names[i]] = noop; - } - } - }()); - - // fix Array#forEach in IE - // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach - if (!Array.prototype.forEach) { - Array.prototype.forEach = function(fn, scope) { - let i = 0, len = this.length; - for(; i < len; ++i) { - if (i in this) { - fn.call(scope, this[i], i, this); - } - } - }; - } - - // fix Array#indexOf in, guess what, IE! <3 - // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - "use strict"; - if (this == null) { - throw new TypeError(); - } - const t = Object(this); - const len = t.length >>> 0; - if (len === 0) { - return -1; - } - let n = 0; - if (arguments.length > 1) { - n = Number(arguments[1]); - if (n != n) { // shortcut for verifying if it's NaN - n = 0; - } else if (n != 0 && n != Infinity && n != -Infinity) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - if (n >= len) { - return -1; - } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - } - } - - /** - * MicroEvent - to make any js object an event emitter (server or browser) - */ - const MicroEvent = function () { - }; - MicroEvent.prototype = { - bind: function(event, fct){ - this._events = this._events || {}; - this._events[event] = this._events[event] || []; - this._events[event].push(fct); - }, - unbind: function(event, fct){ - this._events = this._events || {}; - if( event in this._events === false ) return; - this._events[event].splice(this._events[event].indexOf(fct), 1); - }, - trigger: function(event /* , args... */){ - this._events = this._events || {}; - if( event in this._events === false ) return; - for(var i = 0; i < this._events[event].length; i++){ - this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)) - } - } - }; - /** - * mixin will delegate all MicroEvent.js function in the destination object - * @param {Object} the object which will support MicroEvent - */ - MicroEvent.mixin = function(destObject){ - var props = ['bind', 'unbind', 'trigger']; - if(!destObject) return; - for(var i = 0; i < props.length; i ++){ - destObject[props[i]] = MicroEvent.prototype[props[i]]; - } - } - - /** - * Loader - * The loader is responsible for loading - * and caching all necessary resources - */ - function Loader(resources) { - this.resources = resources - this.cache = {} // file => contents - this.langs = {} // lang => strings - } - - Loader.prototype.load = function(lang, cb) { - if(this.langs[lang]) return cb() - - if (this.resources.length > 0) { - var reqs = 0; - for (var i=0, n=this.resources.length; i < n; i++) { - this.fetch(this.resources[i], lang, function(e) { - reqs++; - if(e) console.warn(e) - - if (reqs < n) return;// Call back once all reqs are completed - cb && cb() - }) - } - } - } - - Loader.prototype.fetch = function(href, lang, cb) { - var that = this - - if (this.cache[href]) { - this.parse(lang, href, this.cache[href], cb) - return; - } - - var xhr = new XMLHttpRequest() - xhr.open('GET', href, /*async: */true) - if (xhr.overrideMimeType) { - xhr.overrideMimeType('application/json; charset=utf-8'); - } - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - if (xhr.status == 200 || xhr.status === 0) { - var data = JSON.parse(xhr.responseText) - that.cache[href] = data - // Pass on the contents for parsing - that.parse(lang, href, data, cb) - } else { - cb(new Error('Failed to load '+href)) - } - } - }; - xhr.send(null); - } - - Loader.prototype.parse = function(lang, currHref, data, cb) { - if ('object' != typeof data) { - cb(new Error('A file couldn\'t be parsed as json.')) - return - } - - // Issue #6129: Fix exceptions caused by browsers - // Also for fallback, see BCP 47 RFC 4647 section 3.4 - // NOTE: this output the all lowercase form - function getBcp47LangCode(browserLang) { - const bcp47Lang = browserLang.toLowerCase(); - // Browser => BCP 47 - const langCodeMap = { - 'zh-cn': 'zh-hans-cn', - 'zh-hk': 'zh-hant-hk', - 'zh-mo': 'zh-hant-mo', - 'zh-my': 'zh-hans-my', - 'zh-sg': 'zh-hans-sg', - 'zh-tw': 'zh-hant-tw', - }; - - return langCodeMap[bcp47Lang] ?? bcp47Lang; - } - - // Issue #6129: Fix exceptions - // NOTE: translatewiki.net use all lowercase form by default ('en-gb' insted of 'en-GB') - function getJsonLangCode(bcp47Lang) { - const jsonLang = bcp47Lang.toLowerCase(); - // BCP 47 => JSON - const langCodeMap = { - 'sr-cyrl': 'sr-ec', - 'sr-latn': 'sr-el', - 'zh-hant-hk': 'zh-hk', - }; - - return langCodeMap[jsonLang] ?? jsonLang; - } - - let bcp47LangCode = getBcp47LangCode(lang); - let jsonLangCode = getJsonLangCode(bcp47LangCode); - - // Check if lang exists - if (!data[jsonLangCode]) { - // lang not found - // This may be due to formatting (expected 'ru' but browser sent 'ru-RU') - // Set err msg before mutating lang (we may need this later) - const msg = 'Couldn\'t find translations for ' + lang + - '(lowercase BCP 47 lang tag ' + bcp47LangCode + - ', JSON lang code ' + jsonLangCode + ')'; - - // Check for '-' (BCP 47 'ROOT-SCRIPT-REGION-VARIANT') and fallback until found data or ROOT - // - 'ROOT-SCRIPT-REGION': 'zh-Hans-CN' - // - 'ROOT-SCRIPT': 'zh-Hans' - // - 'ROOT-REGION': 'en-GB' - // - 'ROOT-VARIANT': 'be-tarask' - while (!data[jsonLangCode] && bcp47LangCode.lastIndexOf('-') > -1) { - // ROOT-SCRIPT-REGION-VARIANT formatting detected - bcp47LangCode = bcp47LangCode.substring(0, bcp47LangCode.lastIndexOf('-')); // set lang to ROOT lang - jsonLangCode = getJsonLangCode(bcp47LangCode); - } - - // Check if already found data or ROOT lang exists (e.g 'ru') - if (!data[jsonLangCode]) { - // ROOT lang not found. (e.g 'zh') - // Loop through langs data. Maybe we have a variant? e.g (zh-hans) - let l; // langs item. Declare outside of loop - - for (l in data) { - // Is not ROOT? - // And is variant of ROOT? - // (NOTE: index of ROOT equals 0 would cause unexpected ISO 639-1 vs. 639-3 issues, - // so append dash into query string) - // And is known lang? - if (bcp47LangCode != l && l.indexOf(lang + '-') === 0 && data[l]) { - bcp47LangCode = l; // set lang to ROOT-SCRIPT (e.g 'zh-hans') - jsonLangCode = getJsonLangCode(bcp47LangCode); - break; - } - } - - // Did we find a variant? If not, return err. - if (bcp47LangCode != l) { - return cb(new Error(msg)); - } - } - } - - lang = jsonLangCode; - - if ('string' == typeof data[lang]) { - // Import rule - - // absolute path - let importUrl = data[lang]; - - // relative path - if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) { - importUrl = currHref+"/../"+data[lang] - } - - this.fetch(importUrl, lang, cb) - return - } - - if ('object' != typeof data[lang]) { - cb(new Error('Translations should be specified as JSON objects!')) - return - } - - this.langs[lang] = data[lang] - // TODO: Also store accompanying langs - cb() - } - - - /** - * The html10n object - */ - const html10n = - { - language: null - }; - MicroEvent.mixin(html10n) - - html10n.macros = {} - - html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"] - - /** - * Get rules for plural forms (shared with JetPack), see: - * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p - * - * @param {string} lang - * locale (language) used. - * - * @return {Function} - * returns a function that gives the plural form name for a given integer: - * var fun = getPluralRules('en'); - * fun(1) -> 'one' - * fun(0) -> 'other' - * fun(1000) -> 'other'. - */ - function getPluralRules(lang) { - var locales2rules = { - 'af': 3, - 'ak': 4, - 'am': 4, - 'ar': 1, - 'asa': 3, - 'az': 0, - 'be': 11, - 'bem': 3, - 'bez': 3, - 'bg': 3, - 'bh': 4, - 'bm': 0, - 'bn': 3, - 'bo': 0, - 'br': 20, - 'brx': 3, - 'bs': 11, - 'ca': 3, - 'cgg': 3, - 'chr': 3, - 'cs': 12, - 'cy': 17, - 'da': 3, - 'de': 3, - 'dv': 3, - 'dz': 0, - 'ee': 3, - 'el': 3, - 'en': 3, - 'eo': 3, - 'es': 3, - 'et': 3, - 'eu': 3, - 'fa': 0, - 'ff': 5, - 'fi': 3, - 'fil': 4, - 'fo': 3, - 'fr': 5, - 'fur': 3, - 'fy': 3, - 'ga': 8, - 'gd': 24, - 'gl': 3, - 'gsw': 3, - 'gu': 3, - 'guw': 4, - 'gv': 23, - 'ha': 3, - 'haw': 3, - 'he': 2, - 'hi': 4, - 'hr': 11, - 'hu': 0, - 'id': 0, - 'ig': 0, - 'ii': 0, - 'is': 3, - 'it': 3, - 'iu': 7, - 'ja': 0, - 'jmc': 3, - 'jv': 0, - 'ka': 0, - 'kab': 5, - 'kaj': 3, - 'kcg': 3, - 'kde': 0, - 'kea': 0, - 'kk': 3, - 'kl': 3, - 'km': 0, - 'kn': 0, - 'ko': 0, - 'ksb': 3, - 'ksh': 21, - 'ku': 3, - 'kw': 7, - 'lag': 18, - 'lb': 3, - 'lg': 3, - 'ln': 4, - 'lo': 0, - 'lt': 10, - 'lv': 6, - 'mas': 3, - 'mg': 4, - 'mk': 16, - 'ml': 3, - 'mn': 3, - 'mo': 9, - 'mr': 3, - 'ms': 0, - 'mt': 15, - 'my': 0, - 'nah': 3, - 'naq': 7, - 'nb': 3, - 'nd': 3, - 'ne': 3, - 'nl': 3, - 'nn': 3, - 'no': 3, - 'nr': 3, - 'nso': 4, - 'ny': 3, - 'nyn': 3, - 'om': 3, - 'or': 3, - 'pa': 3, - 'pap': 3, - 'pl': 13, - 'ps': 3, - 'pt': 3, - 'rm': 3, - 'ro': 9, - 'rof': 3, - 'ru': 11, - 'rwk': 3, - 'sah': 0, - 'saq': 3, - 'se': 7, - 'seh': 3, - 'ses': 0, - 'sg': 0, - 'sh': 11, - 'shi': 19, - 'sk': 12, - 'sl': 14, - 'sma': 7, - 'smi': 7, - 'smj': 7, - 'smn': 7, - 'sms': 7, - 'sn': 3, - 'so': 3, - 'sq': 3, - 'sr': 11, - 'ss': 3, - 'ssy': 3, - 'st': 3, - 'sv': 3, - 'sw': 3, - 'syr': 3, - 'ta': 3, - 'te': 3, - 'teo': 3, - 'th': 0, - 'ti': 4, - 'tig': 3, - 'tk': 3, - 'tl': 4, - 'tn': 3, - 'to': 0, - 'tr': 0, - 'ts': 3, - 'tzm': 22, - 'uk': 11, - 'ur': 3, - 've': 3, - 'vi': 0, - 'vun': 3, - 'wa': 4, - 'wae': 3, - 'wo': 0, - 'xh': 3, - 'xog': 3, - 'yo': 0, - 'zh': 0, - 'zu': 3 - }; - - // utility functions for plural rules methods - function isIn(n, list) { - return list.indexOf(n) !== -1; - } - function isBetween(n, start, end) { - return start <= n && n <= end; - } - - // list of all plural rules methods: - // map an integer to the plural form name to use - var pluralRules = { - '0': function(n) { - return 'other'; - }, - '1': function(n) { - if ((isBetween((n % 100), 3, 10))) - return 'few'; - if (n === 0) - return 'zero'; - if ((isBetween((n % 100), 11, 99))) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '2': function(n) { - if (n !== 0 && (n % 10) === 0) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '3': function(n) { - if (n == 1) - return 'one'; - return 'other'; - }, - '4': function(n) { - if ((isBetween(n, 0, 1))) - return 'one'; - return 'other'; - }, - '5': function(n) { - if ((isBetween(n, 0, 2)) && n != 2) - return 'one'; - return 'other'; - }, - '6': function(n) { - if (n === 0) - return 'zero'; - if ((n % 10) == 1 && (n % 100) != 11) - return 'one'; - return 'other'; - }, - '7': function(n) { - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '8': function(n) { - if ((isBetween(n, 3, 6))) - return 'few'; - if ((isBetween(n, 7, 10))) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '9': function(n) { - if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) - return 'few'; - if (n == 1) - return 'one'; - return 'other'; - }, - '10': function(n) { - if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) - return 'few'; - if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) - return 'one'; - return 'other'; - }, - '11': function(n) { - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) - return 'few'; - if ((n % 10) === 0 || - (isBetween((n % 10), 5, 9)) || - (isBetween((n % 100), 11, 14))) - return 'many'; - if ((n % 10) == 1 && (n % 100) != 11) - return 'one'; - return 'other'; - }, - '12': function(n) { - if ((isBetween(n, 2, 4))) - return 'few'; - if (n == 1) - return 'one'; - return 'other'; - }, - '13': function(n) { - if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) - return 'few'; - if (n != 1 && (isBetween((n % 10), 0, 1)) || - (isBetween((n % 10), 5, 9)) || - (isBetween((n % 100), 12, 14))) - return 'many'; - if (n == 1) - return 'one'; - return 'other'; - }, - '14': function(n) { - if ((isBetween((n % 100), 3, 4))) - return 'few'; - if ((n % 100) == 2) - return 'two'; - if ((n % 100) == 1) - return 'one'; - return 'other'; - }, - '15': function(n) { - if (n === 0 || (isBetween((n % 100), 2, 10))) - return 'few'; - if ((isBetween((n % 100), 11, 19))) - return 'many'; - if (n == 1) - return 'one'; - return 'other'; - }, - '16': function(n) { - if ((n % 10) == 1 && n != 11) - return 'one'; - return 'other'; - }, - '17': function(n) { - if (n == 3) - return 'few'; - if (n === 0) - return 'zero'; - if (n == 6) - return 'many'; - if (n == 2) - return 'two'; - if (n == 1) - return 'one'; - return 'other'; - }, - '18': function(n) { - if (n === 0) - return 'zero'; - if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) - return 'one'; - return 'other'; - }, - '19': function(n) { - if ((isBetween(n, 2, 10))) - return 'few'; - if ((isBetween(n, 0, 1))) - return 'one'; - return 'other'; - }, - '20': function(n) { - if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( - isBetween((n % 100), 10, 19) || - isBetween((n % 100), 70, 79) || - isBetween((n % 100), 90, 99) - )) - return 'few'; - if ((n % 1000000) === 0 && n !== 0) - return 'many'; - if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) - return 'two'; - if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) - return 'one'; - return 'other'; - }, - '21': function(n) { - if (n === 0) - return 'zero'; - if (n == 1) - return 'one'; - return 'other'; - }, - '22': function(n) { - if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) - return 'one'; - return 'other'; - }, - '23': function(n) { - if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) - return 'one'; - return 'other'; - }, - '24': function(n) { - if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) - return 'few'; - if (isIn(n, [2, 12])) - return 'two'; - if (isIn(n, [1, 11])) - return 'one'; - return 'other'; - } - }; - - // return a function that gives the plural form name for a given integer - var index = locales2rules[lang.replace(/-.*$/, '')]; - if (!(index in pluralRules)) { - console.warn('plural form unknown for [' + lang + ']'); - return function() { return 'other'; }; - } - return pluralRules[index]; - } - - /** - * pre-defined 'plural' macro - */ - html10n.macros.plural = function(key, param, opts) { - var str - , n = parseFloat(param); - if (isNaN(n)) - return; - - // initialize _pluralRules - if (!("_pluralRules" in this)) - this._pluralRules = getPluralRules(html10n.language); - var index = this._pluralRules(n); - - // try to find a [zero|one|two] key if it's defined - if (n === 0 && ('zero') in opts) { - str = opts['zero']; - } else if (n == 1 && ('one') in opts) { - str = opts['one']; - } else if (n == 2 && ('two') in opts) { - str = opts['two']; - } else if (index in opts) { - str = opts[index]; - } - - return str; - }; - - /** - * Localize a document - * @param langs An array of lang codes defining fallbacks - */ - html10n.localize = function(langs) { - var that = this - // if only one string => create an array - if ('string' == typeof langs) langs = [langs] - - // Expand two-part locale specs - var i=0 - langs.forEach(function(lang) { - if(!lang) return; - langs[i++] = lang; - if(~lang.indexOf('-')) langs[i++] = lang.substr(0, lang.indexOf('-')); - }) - - this.build(langs, function(er, translations) { - html10n.translations = translations - html10n.translateElement(translations) - that.trigger('localized') - }) - } - - /** - * Triggers the translation process - * for an element - * @param translations A hash of all translation strings - * @param element A DOM element, if omitted, the document element will be used - */ - html10n.translateElement = function(translations, element) { - element = element || document.documentElement - - var children = element? getTranslatableChildren(element) : document.childNodes; - for (var i=0, n=children.length; i < n; i++) { - this.translateNode(translations, children[i]) - } - - // translate element itself if necessary - this.translateNode(translations, element) - } - - function asyncForEach(list, iterator, cb) { - var i = 0 - , n = list.length - iterator(list[i], i, function each(err) { - if(err) console.error(err) - i++ - if (i < n) return iterator(list[i],i, each); - cb() - }) - } - - function getTranslatableChildren(element) { - if(!document.querySelectorAll) { - if (!element) return [] - var nodes = element.getElementsByTagName('*') - , l10nElements = [] - for (var i=0, n=nodes.length; i < n; i++) { - if (nodes[i].getAttribute('data-l10n-id')) - l10nElements.push(nodes[i]); - } - return l10nElements - } - return element.querySelectorAll('*[data-l10n-id]') - } - - html10n.get = function(id, args) { - var translations = html10n.translations - if(!translations) return console.warn('No translations available (yet)') - if(!translations[id]) return console.warn('Could not find string '+id) - - // apply macros - var str = translations[id] - - str = substMacros(id, str, args) - - // apply args - str = substArguments(str, args) - - return str - } - - // replace {{arguments}} with their values or the - // associated translation string (based on its key) - function substArguments(str, args) { - var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/ - , match - var translations = html10n.translations; - while (match = reArgs.exec(str)) { - if (!match || match.length < 2) - return str // argument key not found - - var arg = match[1] - , sub = '' - if (args && arg in args) { - sub = args[arg] - } else if (translations && arg in translations) { - sub = translations[arg] - } else { - console.warn('Could not find argument {{' + arg + '}}') - return str - } - - str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length) - } - - return str - } - - // replace {[macros]} with their values - function substMacros(key, str, args) { - var regex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/ //.exec('{[ plural(n) other: are {{n}}, one: is ]}') - , match - - while(match = regex.exec(str)) { - // a macro has been found - // Note: at the moment, only one parameter is supported - var macroName = match[1] - , paramName = match[2] - , optv = match[3] - , opts = {} - - if (!(macroName in html10n.macros)) continue - - if(optv) { - optv.match(/(?=\s*)([a-zA-Z]+)\: ?([ a-zA-Z{}]+)(?=,?)/g).forEach(function(arg) { - var parts = arg.split(':') - , name = parts[0] - , value = parts[1].trim() - opts[name] = value - }) - } - - var param - if (args && paramName in args) { - param = args[paramName] - } else if (paramName in html10n.translations) { - param = translations[paramName] - } - - // there's no macro parser: it has to be defined in html10n.macros - var macro = html10n.macros[macroName] - str = str.substr(0, match.index) + macro(key, param, opts) + str.substr(match.index+match[0].length) - } - - return str - } - - /** - * Applies translations to a DOM node (recursive) - */ - html10n.translateNode = function(translations, node) { - var str = {} - - // get id - str.id = node.getAttribute('data-l10n-id') - if (!str.id) return - - if(!translations[str.id]) return console.warn('Couldn\'t find translation key '+str.id) - - // get args - if(window.JSON) { - str.args = JSON.parse(node.getAttribute('data-l10n-args')) - }else{ - try{ - str.args = eval(node.getAttribute('data-l10n-args')) - }catch(e) { - console.warn('Couldn\'t parse args for '+str.id) - } - } - - str.str = html10n.get(str.id, str.args) - - // get attribute name to apply str to - var prop - , index = str.id.lastIndexOf('.') - , attrList = // allowed attributes - { "title": 1 - , "innerHTML": 1 - , "alt": 1 - , "textContent": 1 - , "value": 1 - , "placeholder": 1 - } - if (index > 0 && str.id.substr(index + 1) in attrList) { - // an attribute has been specified (example: "my_translation_key.placeholder") - prop = str.id.substr(index + 1) - } else { // no attribute: assuming text content by default - prop = document.body.textContent ? 'textContent' : 'innerText' - } - - // Apply translation - if (node.children.length === 0 || prop != 'textContent') { - node[prop] = str.str - node.setAttribute("aria-label", str.str); // Sets the aria-label - // The idea of the above is that we always have an aria value - // This might be a bit of an abrupt solution but let's see how it goes - } else { - var children = node.childNodes, - found = false - for (var i=0, n=children.length; i < n; i++) { - if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) { - if (!found) { - children[i].nodeValue = str.str - found = true - } else { - children[i].nodeValue = '' - } - } - } - if (!found) { - console.warn('Unexpected error: could not translate element content for key '+str.id, node) - } - } - } - - /** - * Builds a translation object from a list of langs (loads the necessary translations) - * @param langs Array - a list of langs sorted by priority (default langs should go last) - */ - html10n.build = function(langs, cb) { - var that = this - , build = {} - - asyncForEach(langs, function (lang, i, next) { - if(!lang) return next(); - that.loader.load(lang, next) - }, function() { - var lang - langs.reverse() - - // loop through the priority array... - for (var i=0, n=langs.length; i < n; i++) { - lang = langs[i] - - if(!lang) continue; - if(!(lang in that.loader.langs)) {// uh, we don't have this lang availbable.. - // then check for related langs - if(~lang.indexOf('-')) lang = lang.split('-')[0]; - for(var l in that.loader.langs) { - if(lang != l && l.indexOf(lang) === 0) { - lang = l - break; - } - } - if(lang != l) continue; - } - - // ... and apply all strings of the current lang in the list - // to our build object - for (var string in that.loader.langs[lang]) { - build[string] = that.loader.langs[lang][string] - } - - // the last applied lang will be exposed as the - // lang the page was translated to - that.language = lang - } - cb(null, build) - }) - } - - /** - * Returns the language that was last applied to the translations hash - * thus overriding most of the formerly applied langs - */ - html10n.getLanguage = function() { - return this.language; - } - - /** - * Returns the direction of the language returned be html10n#getLanguage - */ - html10n.getDirection = function() { - if(!this.language) return - var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-')) - return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl' - } - - /** - * Index all s - */ - html10n.index = function () { - // Find all s - var links = document.getElementsByTagName('link') - , resources = [] - for (var i=0, n=links.length; i < n; i++) { - if (links[i].type != 'application/l10n+json') - continue; - resources.push(links[i].href) - } - this.loader = new Loader(resources) - this.trigger('indexed') - } - - if (document.addEventListener) // modern browsers and IE9+ - document.addEventListener('DOMContentLoaded', function() { - html10n.index() - }, false) - else if (window.attachEvent) - window.attachEvent('onload', function() { - html10n.index() - }, false) - - // gettext-like shortcut - if (window._ === undefined) - window._ = html10n.get; - - return html10n -})(window, document) - -export default html10n - -window.html10n = html10n diff --git a/src/static/js/vendors/html10n.ts b/src/static/js/vendors/html10n.ts new file mode 100644 index 000000000..90fd4d1ae --- /dev/null +++ b/src/static/js/vendors/html10n.ts @@ -0,0 +1,992 @@ +import {Func} from "mocha"; + + +type PluralFunc = (n: number) => string + +export class Html10n { + public language?: string + private rtl: string[] + private _pluralRules?: PluralFunc + public mt: MicroEvent + private loader: Loader + private translations: Map + private macros: Map + + constructor() { + this.language = undefined + this.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"] + this.mt = new MicroEvent() + this.loader = new Loader([]) + this.translations = new Map() + this.macros = new Map() + + this.macros.set('plural', (_key: string, param:string, opts: any)=>{ + let str + , n = parseFloat(param); + if (isNaN(n)) + return; + + // initialize _pluralRules + if (this._pluralRules === undefined) { + this._pluralRules = this.getPluralRules(this.language!); + } + let index = this._pluralRules!(n); + + // try to find a [zero|one|two] key if it's defined + if (n === 0 && ('zero') in opts) { + str = opts['zero']; + } else if (n == 1 && ('one') in opts) { + str = opts['one']; + } else if (n == 2 && ('two') in opts) { + str = opts['two']; + } else if (index in opts) { + str = opts[index]; + } + + return str; + }) + + document.addEventListener('DOMContentLoaded', ()=> { + this.index() + }, false) + } + + bind(event: string, fct: Func) { + this.mt.bind(event, fct) + } + + /** + * Get rules for plural forms (shared with JetPack), see: + * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p + * + * @param {string} lang + * locale (language) used. + * + * @return {PluralFunc} + * returns a function that gives the plural form name for a given integer: + * var fun = getPluralRules('en'); + * fun(1) -> 'one' + * fun(0) -> 'other' + * fun(1000) -> 'other'. + */ + getPluralRules(lang: string): PluralFunc { + const locales2rules = new Map([ + ['af', 3], + ['ak', 4], + ['am', 4], + ['ar', 1], + ['asa', 3], + ['az', 0], + ['be', 11], + ['bem', 3], + ['bez', 3], + ['bg', 3], + ['bh', 4], + ['bm', 0], + ['bn', 3], + ['bo', 0], + ['br', 20], + ['brx', 3], + ['bs', 11], + ['ca', 3], + ['cgg', 3], + ['chr', 3], + ['cs', 12], + ['cy', 17], + ['da', 3], + ['de', 3], + ['dv', 3], + ['dz', 0], + ['ee', 3], + ['el', 3], + ['en', 3], + ['eo', 3], + ['es', 3], + ['et', 3], + ['eu', 3], + ['fa', 0], + ['ff', 5], + ['fi', 3], + ['fil', 4], + ['fo', 3], + ['fr', 5], + ['fur', 3], + ['fy', 3], + ['ga', 8], + ['gd', 24], + ['gl', 3], + ['gsw', 3], + ['gu', 3], + ['guw', 4], + ['gv', 23], + ['ha', 3], + ['haw', 3], + ['he', 2], + ['hi', 4], + ['hr', 11], + ['hu', 0], + ['id', 0], + ['ig', 0], + ['ii', 0], + ['is', 3], + ['it', 3], + ['iu', 7], + ['ja', 0], + ['jmc', 3], + ['jv', 0], + ['ka', 0], + ['kab', 5], + ['kaj', 3], + ['kcg', 3], + ['kde', 0], + ['kea', 0], + ['kk', 3], + ['kl', 3], + ['km', 0], + ['kn', 0], + ['ko', 0], + ['ksb', 3], + ['ksh', 21], + ['ku', 3], + ['kw', 7], + ['lag', 18], + ['lb', 3], + ['lg', 3], + ['ln', 4], + ['lo', 0], + ['lt', 10], + ['lv', 6], + ['mas', 3], + ['mg', 4], + ['mk', 16], + ['ml', 3], + ['mn', 3], + ['mo', 9], + ['mr', 3], + ['ms', 0], + ['mt', 15], + ['my', 0], + ['nah', 3], + ['naq', 7], + ['nb', 3], + ['nd', 3], + ['ne', 3], + ['nl', 3], + ['nn', 3], + ['no', 3], + ['nr', 3], + ['nso', 4], + ['ny', 3], + ['nyn', 3], + ['om', 3], + ['or', 3], + ['pa', 3], + ['pap', 3], + ['pl', 13], + ['ps', 3], + ['pt', 3], + ['rm', 3], + ['ro', 9], + ['rof', 3], + ['ru', 11], + ['rwk', 3], + ['sah', 0], + ['saq', 3], + ['se', 7], + ['seh', 3], + ['ses', 0], + ['sg', 0], + ['sh', 11], + ['shi', 19], + ['sk', 12], + ['sl', 14], + ['sma', 7], + ['smi', 7], + ['smj', 7], + ['smn', 7], + ['sms', 7], + ['sn', 3], + ['so', 3], + ['sq', 3], + ['sr', 11], + ['ss', 3], + ['ssy', 3], + ['st', 3], + ['sv', 3], + ['sw', 3], + ['syr', 3], + ['ta', 3], + ['te', 3], + ['teo', 3], + ['th', 0], + ['ti', 4], + ['tig', 3], + ['tk', 3], + ['tl', 4], + ['tn', 3], + ['to', 0], + ['tr', 0], + ['ts', 3], + ['tzm', 22], + ['uk', 11], + ['ur', 3], + ['ve', 3], + ['vi', 0], + ['vun', 3], + ['wa', 4], + ['wae', 3], + ['wo', 0], + ['xh', 3], + ['xog', 3], + ['yo', 0], + ['zh', 0], + ['zu', 3] + ]) + + function isIn(n: number, list: number[]) { + return list.indexOf(n) !== -1; + } + function isBetween(n: number, start: number, end: number) { + return start <= n && n <= end; + } + + type PluralFunc = (n: number) => string + + + const pluralRules: { + [key: string]: PluralFunc + } = { + '0': function() { + return 'other'; + }, + '1': function(n: number) { + if ((isBetween((n % 100), 3, 10))) + return 'few'; + if (n === 0) + return 'zero'; + if ((isBetween((n % 100), 11, 99))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '2': function(n: number) { + if (n !== 0 && (n % 10) === 0) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '3': function(n: number) { + if (n == 1) + return 'one'; + return 'other'; + }, + '4': function(n: number) { + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '5': function(n: number) { + if ((isBetween(n, 0, 2)) && n != 2) + return 'one'; + return 'other'; + }, + '6': function(n: number) { + if (n === 0) + return 'zero'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '7': function(n: number) { + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '8': function(n: number) { + if ((isBetween(n, 3, 6))) + return 'few'; + if ((isBetween(n, 7, 10))) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '9': function(n: number) { + if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '10': function(n: number) { + if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19))) + return 'few'; + if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19))) + return 'one'; + return 'other'; + }, + '11': function(n: number) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if ((n % 10) === 0 || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 11, 14))) + return 'many'; + if ((n % 10) == 1 && (n % 100) != 11) + return 'one'; + return 'other'; + }, + '12': function(n: number) { + if ((isBetween(n, 2, 4))) + return 'few'; + if (n == 1) + return 'one'; + return 'other'; + }, + '13': function(n: number) { + if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14))) + return 'few'; + if (n != 1 && (isBetween((n % 10), 0, 1)) || + (isBetween((n % 10), 5, 9)) || + (isBetween((n % 100), 12, 14))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '14': function(n: number) { + if ((isBetween((n % 100), 3, 4))) + return 'few'; + if ((n % 100) == 2) + return 'two'; + if ((n % 100) == 1) + return 'one'; + return 'other'; + }, + '15': function(n: number) { + if (n === 0 || (isBetween((n % 100), 2, 10))) + return 'few'; + if ((isBetween((n % 100), 11, 19))) + return 'many'; + if (n == 1) + return 'one'; + return 'other'; + }, + '16': function(n: number) { + if ((n % 10) == 1 && n != 11) + return 'one'; + return 'other'; + }, + '17': function(n: number) { + if (n == 3) + return 'few'; + if (n === 0) + return 'zero'; + if (n == 6) + return 'many'; + if (n == 2) + return 'two'; + if (n == 1) + return 'one'; + return 'other'; + }, + '18': function(n: number) { + if (n === 0) + return 'zero'; + if ((isBetween(n, 0, 2)) && n !== 0 && n != 2) + return 'one'; + return 'other'; + }, + '19': function(n: number) { + if ((isBetween(n, 2, 10))) + return 'few'; + if ((isBetween(n, 0, 1))) + return 'one'; + return 'other'; + }, + '20': function(n: number) { + if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !( + isBetween((n % 100), 10, 19) || + isBetween((n % 100), 70, 79) || + isBetween((n % 100), 90, 99) + )) + return 'few'; + if ((n % 1000000) === 0 && n !== 0) + return 'many'; + if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92])) + return 'two'; + if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91])) + return 'one'; + return 'other'; + }, + '21': function(n: number) { + if (n === 0) + return 'zero'; + if (n == 1) + return 'one'; + return 'other'; + }, + '22': function(n: number) { + if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99))) + return 'one'; + return 'other'; + }, + '23': function(n: number) { + if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0) + return 'one'; + return 'other'; + }, + '24': function(n: number) { + if ((isBetween(n, 3, 10) || isBetween(n, 13, 19))) + return 'few'; + if (isIn(n, [2, 12])) + return 'two'; + if (isIn(n, [1, 11])) + return 'one'; + return 'other'; + } + }; + + const index = locales2rules.get(lang.replace(/-.*$/, '')); + // @ts-ignore + if (!(index in pluralRules)) { + console.warn('plural form unknown for [' + lang + ']'); + return function() { return 'other'; }; + } + // @ts-ignore + return pluralRules[index]; + } + + getTranslatableChildren(element: HTMLElement) { + if(!document.querySelectorAll) { + if (!element) return [] + const nodes = element.getElementsByTagName('*') + , l10nElements = [] + for (let i=0, n=nodes.length; i < n; i++) { + if (nodes[i].getAttribute('data-l10n-id')) + l10nElements.push(nodes[i]); + } + return l10nElements + } + return element.querySelectorAll('*[data-l10n-id]') + } + + localize(langs: (string|undefined)[]|string) { + if ('string' === typeof langs) { + langs = [langs]; + } + let i = 0 + langs.forEach((lang) => { + if(!lang) return; + langs[i++] = lang; + if(~lang.indexOf('-')) langs[i++] = lang.substring(0, lang.indexOf('-')); + }) + + this.build(langs, (er: null, translations: Map) =>{ + this.translations = translations + this.translateElement(translations) + this.mt.trigger('localized') + }) + } + + /** + * Triggers the translation process + * for an element + * @param translations A hash of all translation strings + * @param element A DOM element, if omitted, the document element will be used + */ + translateElement(translations: Map, element?: HTMLElement) { + element = element || document.documentElement + const children = element ? this.getTranslatableChildren(element): document.childNodes + + for (let child of children) { + this.translateNode(translations, child as HTMLElement) + } + + // translate element itself if necessary + this.translateNode(translations, element) + } + + asyncForEach(list: (string|undefined)[], iterator: any, cb: Function) { + let i = 0 + , n = list.length + iterator(list[i], i, function each(err?: string) { + if(err) console.error(err) + i++ + if (i < n) return iterator(list[i],i, each); + cb() + }) + } + + /** + * Builds a translation object from a list of langs (loads the necessary translations) + * @param langs Array - a list of langs sorted by priority (default langs should go last) + * @param cb Function - a callback that will be called once all langs have been loaded + */ + build(langs: (string|undefined)[], cb: Function) { + + const that = this; + const build = new Map() + + this.asyncForEach(langs, function (lang: string, _i: number, next:LoaderFunc) { + if(!lang) return next(); + that.loader.load(lang, next) + }, () =>{ + let lang; + langs.reverse() + + // loop through the priority array... + for (let i=0, n=langs.length; i < n; i++) { + lang = langs[i] + + if(!lang) continue; + if(!(lang in that.loader.langs)) {// uh, we don't have this lang availbable.. + // then check for related langs + if(~lang.indexOf('-')) lang = lang.split('-')[0]; + let l + for(l in that.loader.langs) { + if(lang != l && l.indexOf(lang) === 0) { + lang = l + break; + } + } + if(lang != l) continue; + } + + // ... and apply all strings of the current lang in the list + // to our build object + for (let string in that.loader.langs.get(lang)) { + build.set(string,that.loader.langs.get(lang).get(string)) + } + + // the last applied lang will be exposed as the + // lang the page was translated to + that.language = lang + } + cb(null, build) + }) + } + + /** + * Returns the language that was last applied to the translations hash + * thus overriding most of the formerly applied langs + */ + getLanguage() { + return this.language + } + + /** + * Returns the direction of the language returned be html10n#getLanguage + */ + getDirection() { + if(!this.language) return + const langCode = this.language.indexOf('-') == -1? this.language : this.language.substring(0, this.language.indexOf('-')) + return this.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl' + } + + + /** + * Index all s + */ + index() { + // Find all s + const links = document.getElementsByTagName('link') + , resources = [] + for (let i=0, n=links.length; i < n; i++) { + if (links[i].type != 'application/l10n+json') + continue; + resources.push(links[i].href) + } + this.loader = new Loader(resources) + this.mt.trigger('indexed') + } + + translateNode(translations: Map, node: HTMLElement) { + const str: { + id?: string, + args?: any, + str?: string + + } = {} + + // get id + str.id = node.getAttribute('data-l10n-id') as string + if (!str.id) return + + if(!translations.get(str.id)) return console.warn('Couldn\'t find translation key '+str.id) + + // get args + if(window.JSON) { + str.args = JSON.parse(node.getAttribute('data-l10n-args') as string) + }else{ + try{ + str.args = eval(node.getAttribute('data-l10n-args') as string) + }catch(e) { + console.warn('Couldn\'t parse args for '+str.id) + } + } + + str.str = this.get(str.id, str.args) + + // get attribute name to apply str to + let prop + , index = str.id.lastIndexOf('.') + , attrList = // allowed attributes + { "title": 1 + , "innerHTML": 1 + , "alt": 1 + , "textContent": 1 + , "value": 1 + , "placeholder": 1 + } + if (index > 0 && str.id.substring(index + 1) in attrList) { + // an attribute has been specified (example: "my_translation_key.placeholder") + prop = str.id.substring(index + 1) + } else { // no attribute: assuming text content by default + prop = document.body.textContent ? 'textContent' : 'innerText' + } + + // Apply translation + if (node.children.length === 0 || prop != 'textContent') { + // @ts-ignore + node[prop] = str.str! + node.setAttribute("aria-label", str.str!); // Sets the aria-label + // The idea of the above is that we always have an aria value + // This might be a bit of an abrupt solution but let's see how it goes + } else { + let children = node.childNodes, + found = false + let i = 0, n = children.length; + for (; i < n; i++) { + if (children[i].nodeType === 3 && /\S/.test(children[i].textContent!)) { + if (!found) { + children[i].nodeValue = str.str! + found = true + } else { + children[i].nodeValue = '' + } + } + } + if (!found) { + console.warn('Unexpected error: could not translate element content for key '+str.id, node) + } + } + } + + get(id: string, args?:any) { + let translations = this.translations + if(!translations) return console.warn('No translations available (yet)') + if(!translations.get(id)) return console.warn('Could not find string '+id) + + // apply macros + let str = translations.get(id) + + str = this.substMacros(id, str, args) + + // apply args + str = this.substArguments(str, args) + + return str + } + + substMacros(key: string, str:string, args:any) { + let regex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/ //.exec('{[ plural(n) other: are {{n}}, one: is ]}') + , match + + while(match = regex.exec(str)) { + // a macro has been found + // Note: at the moment, only one parameter is supported + let macroName = match[1] + , paramName = match[2] + , optv = match[3] + , opts: {[key:string]:any} = {} + + if (!(this.macros.has(macroName))) continue + + if(optv) { + optv.match(/(?=\s*)([a-zA-Z]+)\: ?([ a-zA-Z{}]+)(?=,?)/g)!.forEach(function(arg) { + const parts = arg.split(':') + , name = parts[0]; + opts[name] = parts[1].trim() + }) + } + + let param + if (args && paramName in args) { + param = args[paramName] + } else if (paramName in this.translations) { + param = this.translations.get(paramName) + } + + // there's no macro parser: it has to be defined in html10n.macros + let macro = this.macros.get(macroName)! + str = str.substring(0, match.index) + macro(key, param, opts) + str.substring(match.index+match[0].length) + } + + return str + } + + substArguments(str: string, args:any) { + let reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/ + , match + let translations = this.translations; + while (match = reArgs.exec(str)) { + if (!match || match.length < 2) + return str // argument key not found + + let arg = match[1] + , sub = '' + if (args && arg in args) { + sub = args[arg] + } else if (translations && arg in translations) { + sub = translations.get(arg) + } else { + console.warn('Could not find argument {{' + arg + '}}') + return str + } + + str = str.substring(0, match.index) + sub + str.substring(match.index + match[0].length) + } + + return str + } + +} + + +class MicroEvent { + private events: Map + + constructor() { + this.events = new Map(); + } + + bind(event: string, fct: Func) { + if (this.events.get(event) === undefined) { + this.events.set(event, []); + } + + this.events.get(event)!.push(fct); + } + + unbind(event: string, fct: Func) { + if (this.events.get(event) === undefined) { + return; + } + + const index = this.events.get(event)!.indexOf(fct); + if (index !== -1) { + this.events.get(event)!.splice(index, 1); + } + } + + trigger(event: string, ...args: any[]) { + if (this.events.get(event) === undefined) { + return; + } + + for (const fct of this.events.get(event)!) { + fct(...args); + } + } + + mixin(destObject: any) { + const props = ['bind', 'unbind', 'trigger']; + if (destObject !== undefined) { + for (const prop of props) { + // @ts-ignore + destObject[prop] = this[prop]; + } + } + } +} + +type LoaderFunc = () => void + +type ErrorFunc = (data?:any)=>void + +class Loader { + private resources: any + private cache: Map + langs: Map + + constructor(resources: any) { + this.resources = resources; + this.cache = new Map(); + this.langs = new Map(); + } + + load(lang: string, callback: LoaderFunc) { + if (this.langs.get(lang) !== undefined) { + callback(); + return; + } + + if (this.resources.length > 0) { + let reqs = 0 + for (const resource of this.resources) { + this.fetch(resource, lang, (e)=> { + reqs++; + if (e) console.warn(e) + + if (reqs < this.resources.length) return;// Call back once all reqs are completed + callback && callback() + }) + } + } + } + + fetch(href: string, lang: string, callback: ErrorFunc) { + const that = this; + + if (this.cache.get(href)) { + this.parse(lang, href, this.cache.get(href), callback) + return; + } + + const xhr = new XMLHttpRequest(); + xhr.open('GET', href, /*async: */true) + if (xhr.overrideMimeType) { + xhr.overrideMimeType('application/json; charset=utf-8'); + } + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200 || xhr.status === 0) { + const data = JSON.parse(xhr.responseText); + that.cache.set(href, data) + // Pass on the contents for parsing + that.parse(lang, href, data, callback) + } else { + callback(new Error('Failed to load '+href)) + } + } + }; + xhr.send(null); + } + + parse(lang: string, href: string, data: { + [key: string]: string + }, callback: ErrorFunc) { + if ('object' !== typeof data) { + callback(new Error('A file couldn\'t be parsed as json.')) + return + } + + function getBcp47LangCode(browserLang: string) { + const bcp47Lang = browserLang.toLowerCase(); + + // Browser => BCP 47 + const langCodeMap = new Map([ + ['zh-cn', 'zh-hans-cn'], + ['zh-hk', 'zh-hant-hk'], + ['zh-mo', 'zh-hant-mo'], + ['zh-my', 'zh-hans-my'], + ['zh-sg', 'zh-hans-sg'], + ['zh-tw', 'zh-hant-tw'], + ]) + + return langCodeMap.get(bcp47Lang) ?? bcp47Lang; + } + + // Issue #6129: Fix exceptions + // NOTE: translatewiki.net use all lowercase form by default ('en-gb' insted of 'en-GB') + function getJsonLangCode(bcp47Lang: string) { + const jsonLang = bcp47Lang.toLowerCase(); + // BCP 47 => JSON + const langCodeMap = new Map([ + ['sr-ec', 'sr-cyrl'], + ['sr-el', 'sr-latn'], + ['zh-hk', 'zh-hant-hk'], + ]) + + return langCodeMap.get(jsonLang) ?? jsonLang; + } + + let bcp47LangCode = getBcp47LangCode(lang); + let jsonLangCode = getJsonLangCode(bcp47LangCode); + + if (!data[jsonLangCode]) { + // lang not found + // This may be due to formatting (expected 'ru' but browser sent 'ru-RU') + // Set err msg before mutating lang (we may need this later) + const msg = 'Couldn\'t find translations for ' + lang + + '(lowercase BCP 47 lang tag ' + bcp47LangCode + + ', JSON lang code ' + jsonLangCode + ')'; + // Check for '-' (BCP 47 'ROOT-SCRIPT-REGION-VARIANT') and fallback until found data or ROOT + // - 'ROOT-SCRIPT-REGION': 'zh-Hans-CN' + // - 'ROOT-SCRIPT': 'zh-Hans' + // - 'ROOT-REGION': 'en-GB' + // - 'ROOT-VARIANT': 'be-tarask' + while (!data[jsonLangCode] && bcp47LangCode.lastIndexOf('-') > -1) { + // ROOT-SCRIPT-REGION-VARIANT formatting detected + bcp47LangCode = bcp47LangCode.substring(0, bcp47LangCode.lastIndexOf('-')); // set lang to ROOT lang + jsonLangCode = getJsonLangCode(bcp47LangCode); + } + + if (!data[jsonLangCode]) { + // ROOT lang not found. (e.g 'zh') + // Loop through langs data. Maybe we have a variant? e.g (zh-hans) + let l; // langs item. Declare outside of loop + + for (l in data) { + // Is not ROOT? + // And is variant of ROOT? + // (NOTE: index of ROOT equals 0 would cause unexpected ISO 639-1 vs. 639-3 issues, + // so append dash into query string) + // And is known lang? + if (bcp47LangCode != l && l.indexOf(lang + '-') === 0 && data[l]) { + bcp47LangCode = l; // set lang to ROOT-SCRIPT (e.g 'zh-hans') + jsonLangCode = getJsonLangCode(bcp47LangCode); + break; + } + } + + // Did we find a variant? If not, return err. + if (bcp47LangCode != l) { + return callback(new Error(msg)); + } + } + + } + + + lang = jsonLangCode + + if('string' === typeof data[lang]) { + // Import rule + + // absolute path + let importUrl = data[lang]; + + // relative path + if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) { + importUrl = href+"/../"+data[lang] + } + + this.fetch(importUrl, lang, callback) + return + } + + if ('object' != typeof data[lang]) { + callback(new Error('Translations should be specified as JSON objects!')) + return + } + + this.langs.set(lang,data[lang]) + // TODO: Also store accompanying langs + callback() + } +} + +export default new Html10n() diff --git a/src/templates/padViteBootstrap.js b/src/templates/padViteBootstrap.js new file mode 100644 index 000000000..05f759077 --- /dev/null +++ b/src/templates/padViteBootstrap.js @@ -0,0 +1,41 @@ +window.$ = window.jQuery = await import('../../src/static/js/rjquery').jQuery; +await import('../../src/static/js/l10n') + +window.clientVars = { + // This is needed to fetch /pluginfw/plugin-definitions.json, which happens before the server + // sends the CLIENT_VARS message. + randomVersionString: "7a7bdbad", +}; + +(async () => { + // Allow other frames to access this frame's modules. + //window.require.resolveTmp = require.resolve('ep_etherpad-lite/static/js/pad_cookie'); + + const basePath = new URL('..', window.location.href).pathname; + window.browser = require('../../src/static/js/vendors/browser'); + const pad = require('../../src/static/js/pad'); + pad.baseURL = basePath; + window.plugins = require('../../src/static/js/pluginfw/client_plugins'); + const hooks = require('../../src/static/js/pluginfw/hooks'); + + // TODO: These globals shouldn't exist. + window.pad = pad.pad; + window.chat = require('../../src/static/js/chat').chat; + window.padeditbar = require('../../src/static/js/pad_editbar').padeditbar; + window.padimpexp = require('../../src/static/js/pad_impexp').padimpexp; + require('../../src/static/js/skin_variants'); + require('../../src/static/js/basic_error_handler') + + window.plugins.baseURL = basePath; + await window.plugins.update(new Map([ + + ])); + // Mechanism for tests to register hook functions (install fake plugins). + window._postPluginUpdateForTestingDone = false; + if (window._postPluginUpdateForTesting != null) window._postPluginUpdateForTesting(); + window._postPluginUpdateForTestingDone = true; + window.pluginDefs = require('../../src/static/js/pluginfw/plugin_defs'); + pad.init(); + await new Promise((resolve) => $(resolve)); + await hooks.aCallAll('documentReady'); +})(); diff --git a/ui/package.json b/ui/package.json index 64a8f7508..e3d6de4ce 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,7 +9,12 @@ "preview": "vite preview" }, "devDependencies": { + "ep_etherpad-lite": "workspace:../src", "typescript": "^5.5.3", - "vite": "^5.3.3" + "vite": "^5.3.3", + "vite-plugin-require": "^1.2.14" + }, + "dependencies": { + "@originjs/vite-plugin-commonjs": "^1.0.3" } } diff --git a/ui/pad.html b/ui/pad.html new file mode 100644 index 000000000..6b34d7e9a --- /dev/null +++ b/ui/pad.html @@ -0,0 +1,686 @@ + + + + + + Etherpad + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + +
+ + + + + + + +
+ +
+ +
+

+ You do not have permission to access this pad +

+
+ + +

+
+ Loading... +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + 0 +
+ +
+
+
+

+ + █   +
+
+
+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + + +
+ + + + + + + + + + + + + + diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 2ce13f8f7..11fb71d46 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -1,10 +1,18 @@ // vite.config.js import { resolve } from 'path' import { defineConfig } from 'vite' +import vitePluginRequire from 'vite-plugin-require'; +import { viteCommonjs } from '@originjs/vite-plugin-commonjs' export default defineConfig({ - base: '/views/', + base: '/views/', + plugins: [ + viteCommonjs(), + ], build: { + commonjsOptions:{ + transformMixedEsModules: true, + }, outDir: resolve(__dirname, '../src/static/oidc'), rollupOptions: { input: { @@ -14,4 +22,31 @@ export default defineConfig({ }, emptyOutDir: true, }, + server:{ + proxy:{ + '/static':{ + target: 'http://localhost:9001', + changeOrigin: true, + secure: false, + }, + '/views/manifest.json':{ + target: 'http://localhost:9001', + changeOrigin: true, + secure: false, + rewrite: (path) => path.replace(/^\/views/, ''), + }, + '/locales.json':{ + target: 'http://localhost:9001', + changeOrigin: true, + secure: false, + rewrite: (path) => path.replace(/^\/views/, ''), + }, + '/locales':{ + target: 'http://localhost:9001', + changeOrigin: true, + secure: false, + rewrite: (path) => path.replace(/^\/views/, ''), + }, + } + } })