diff --git a/README.md b/README.md index 03f842c16..53fd07abf 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,53 @@ # :wind_face: Keywind -Keywind is a component-based Keycloak Login Theme built with [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) and [Alpine.js](https://github.com/alpinejs/alpine). +Keywind is a component-based Keycloak Login & E-Mail Theme built with [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss), [Mailwind](https://www.mailwind.io/) and [Alpine.js](https://github.com/alpinejs/alpine). -![Preview](./preview.png) +![Preview Login](./preview-login.png) + +![Preview Login](./preview-email.png) + +### Styled Pages + +- Error +- Login +- Login Config TOTP +- Login IDP Link Confirm +- Login OAuth Grant +- Login OTP +- Login Page Expired +- Login Password +- Login Recovery Authn Code Config +- Login Recovery Authn Code Input +- Login Reset Password +- Login Update Password +- Login Update Profile +- Login Username +- Logout Confirm +- Register +- Select Authenticator +- Terms and Conditions +- WebAuthn Authenticate +- WebAuthn Error +- WebAuthn Register + +### Identity Provider Icons + +- Apple +- Bitbucket +- Discord +- Facebook +- GitHub +- GitLab +- Google +- Instagram +- LinkedIn +- Microsoft +- OpenID +- Red Hat OpenShift +- PayPal +- Slack +- Stack Overflow +- Twitter ### Styled Pages @@ -52,8 +97,8 @@ Keywind is a component-based Keycloak Login Theme built with [Tailwind CSS](http Keywind has been designed with component-based architecture from the start, and **you can customize as little or as much Keywind as you need**: -1. [Deploy Keywind Login Theme](https://www.keycloak.org/docs/latest/server_development/#deploying-themes) -2. [Create your own Login Theme](https://www.keycloak.org/docs/latest/server_development/#creating-a-theme) +1. [Deploy Keywind Themes](https://www.keycloak.org/docs/latest/server_development/#deploying-themes) +2. [Create your own Themes](https://www.keycloak.org/docs/latest/server_development/#creating-a-theme) 3. Specify parent theme in [theme properties](https://www.keycloak.org/docs/latest/server_development/#theme-properties): ``` @@ -98,6 +143,10 @@ You can update Keywind components in your own child theme. For example, create a ``` +If you want to customize your own email components, update the files located in: + +`theme/mytheme/email/html-src` + ## Build When you're ready to deploy your own theme, run the build command to generate a static production build. diff --git a/package.json b/package.json index fb0faa4ab..578afb58e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "$schema": "https://json.schemastore.org/package", "name": "keywind", "scripts": { - "build": "tsc && vite build", + "build": "npm run build:email && npm run build:login", + "build:email": "vite-node scripts/build.email", + "build:login": "tsc && vite build", "build:jar": "vite-node scripts/build", "dev": "vite build --watch", "test": "mvn test" @@ -18,6 +20,8 @@ "@types/node": "^20.6.5", "archiver": "^6.0.1", "autoprefixer": "^10.4.16", + "child-process-promise": "^2.2.1", + "mailwind": "^2.2.0", "postcss": "^8.4.30", "tailwindcss": "^3.3.3", "typescript": "^5.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c60020e0..c03d7733f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,6 +31,12 @@ devDependencies: autoprefixer: specifier: ^10.4.16 version: 10.4.16(postcss@8.4.30) + child-process-promise: + specifier: ^2.2.1 + version: 2.2.1 + mailwind: + specifier: ^2.2.0 + version: 2.2.0(tailwindcss@3.3.3) postcss: specifier: ^8.4.30 version: 8.4.30 @@ -354,6 +360,23 @@ packages: '@vue/reactivity': 3.1.5 dev: false + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} dev: true @@ -428,6 +451,10 @@ packages: engines: {node: '>=8'} dev: true + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -477,6 +504,37 @@ packages: resolution: {integrity: sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==} dev: true + /cheerio-select@1.6.0: + resolution: {integrity: sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==} + dependencies: + css-select: 4.3.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + dev: true + + /cheerio@1.0.0-rc.10: + resolution: {integrity: sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 1.6.0 + dom-serializer: 1.4.1 + domhandler: 4.3.1 + htmlparser2: 6.1.0 + parse5: 6.0.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + tslib: 2.6.2 + dev: true + + /child-process-promise@2.2.1: + resolution: {integrity: sha512-Fi4aNdqBsr0mv+jgWxcZ/7rAIC2mgihrptyVI4foh/rrjY/3BNjfP9+oaiFx/fzim+1ZyCNBae0DlyfQhSugog==} + dependencies: + cross-spawn: 4.0.2 + node-version: 1.2.0 + promise-polyfill: 6.1.0 + dev: true + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -492,11 +550,36 @@ packages: fsevents: 2.3.3 dev: true + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} dev: true + /commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + dev: true + /compress-commons@5.0.1: resolution: {integrity: sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==} engines: {node: '>= 12.0.0'} @@ -529,6 +612,28 @@ packages: readable-stream: 3.6.2 dev: true + /cross-spawn@4.0.2: + resolution: {integrity: sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==} + dependencies: + lru-cache: 4.1.5 + which: 1.3.1 + dev: true + + /css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -555,10 +660,52 @@ packages: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} dev: true + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + + /domhandler@3.3.0: + resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + dev: true + /electron-to-chromium@1.4.528: resolution: {integrity: sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==} dev: true + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + dev: true + /esbuild@0.18.20: resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} engines: {node: '>=12'} @@ -594,6 +741,11 @@ packages: engines: {node: '>=6'} dev: true + /escape-goat@3.0.0: + resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==} + engines: {node: '>=10'} + dev: true + /fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} dev: true @@ -642,6 +794,11 @@ packages: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -689,6 +846,24 @@ packages: function-bind: 1.1.1 dev: true + /htmlparser2@5.0.1: + resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==} + dependencies: + domelementtype: 2.3.0 + domhandler: 3.3.0 + domutils: 2.8.0 + entities: 2.2.0 + dev: true + + /htmlparser2@6.1.0: + resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 2.2.0 + dev: true + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -718,6 +893,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -734,6 +914,10 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + /jiti@1.20.0: resolution: {integrity: sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==} hasBin: true @@ -743,6 +927,20 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true + /juice@8.1.0: + resolution: {integrity: sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==} + engines: {node: '>=10.0.0'} + hasBin: true + dependencies: + cheerio: 1.0.0-rc.10 + commander: 6.2.1 + mensch: 0.3.4 + slick: 1.12.2 + web-resource-inliner: 6.0.1 + transitivePeerDependencies: + - encoding + dev: true + /lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} @@ -763,6 +961,30 @@ packages: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true + /lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + dev: true + + /mailwind@2.2.0(tailwindcss@3.3.3): + resolution: {integrity: sha512-DueTDipAb2TVsbDixDZD57E2wE55Uk3i+VsDGF8G46MPeEu5cz2ALdsdgec5PhE2W0Fq0TFl1ihpZuuItdR+bA==} + hasBin: true + peerDependencies: + tailwindcss: 3.x + dependencies: + juice: 8.1.0 + tailwindcss: 3.3.3 + yargs: 17.6.2 + transitivePeerDependencies: + - encoding + dev: true + + /mensch@0.3.4: + resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==} + dev: true + /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -776,6 +998,12 @@ packages: picomatch: 2.3.1 dev: true + /mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + dev: true + /mini-svg-data-uri@1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true @@ -821,10 +1049,27 @@ packages: hasBin: true dev: true + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: true + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: true + /node-version@1.2.0: + resolution: {integrity: sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==} + engines: {node: '>=6.0.0'} + dev: true + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -835,6 +1080,12 @@ packages: engines: {node: '>=0.10.0'} dev: true + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -851,6 +1102,16 @@ packages: wrappy: 1.0.2 dev: true + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + dependencies: + parse5: 6.0.1 + dev: true + + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: true + /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -965,6 +1226,14 @@ packages: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true + /promise-polyfill@6.1.0: + resolution: {integrity: sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==} + dev: true + + /pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -1013,6 +1282,11 @@ packages: picomatch: 2.3.1 dev: true + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + /resolve@1.22.6: resolution: {integrity: sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==} hasBin: true @@ -1053,6 +1327,10 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true + /slick@1.12.2: + resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==} + dev: true + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -1065,6 +1343,15 @@ packages: queue-tick: 1.0.1 dev: true + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: @@ -1077,6 +1364,13 @@ packages: safe-buffer: 5.2.1 dev: true + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + /sucrase@3.34.0: resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==} engines: {node: '>=8'} @@ -1155,10 +1449,18 @@ packages: is-number: 7.0.0 dev: true + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true + /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: true + /typescript@5.2.2: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} @@ -1184,6 +1486,11 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true + /valid-data-url@3.0.1: + resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==} + engines: {node: '>=10'} + dev: true + /vite-node@0.34.5(@types/node@20.6.5): resolution: {integrity: sha512-RNZ+DwbCvDoI5CbCSQSyRyzDTfFvFauvMs6Yq4ObJROKlIKuat1KgSX/Ako5rlDMfVCyMcpMRMTkJBxd6z8YRA==} engines: {node: '>=v14.18.0'} @@ -1242,15 +1549,83 @@ packages: fsevents: 2.3.3 dev: true + /web-resource-inliner@6.0.1: + resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==} + engines: {node: '>=10.0.0'} + dependencies: + ansi-colors: 4.1.3 + escape-goat: 3.0.0 + htmlparser2: 5.0.1 + mime: 2.6.0 + node-fetch: 2.7.0 + valid-data-url: 3.0.1 + transitivePeerDependencies: + - encoding + dev: true + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: true + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: true + + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + dev: true + /yaml@2.3.2: resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==} engines: {node: '>= 14'} dev: true + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs@17.6.2: + resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + /zip-stream@5.0.1: resolution: {integrity: sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==} engines: {node: '>= 12.0.0'} diff --git a/preview-email.png b/preview-email.png new file mode 100644 index 000000000..bae4e8f9c Binary files /dev/null and b/preview-email.png differ diff --git a/preview.png b/preview-login.png similarity index 100% rename from preview.png rename to preview-login.png diff --git a/scripts/build.email.ts b/scripts/build.email.ts new file mode 100644 index 000000000..1e7db6a95 --- /dev/null +++ b/scripts/build.email.ts @@ -0,0 +1,34 @@ +import { exec } from 'child-process-promise'; +import * as path from 'node:path'; +import { readdirSync, readFileSync, writeFileSync } from 'node:fs'; + +//BUILD:Head-Files +const directoryPath = path.join(__dirname, '../theme/keywind/email/html-src'); +const directoryPathComponents = path.join( + __dirname, + '../theme/keywind/email/html-src/components/atoms' +); +const files = [ + ...readdirSync(directoryPath), + ...readdirSync(directoryPathComponents).map((e) => `components/atoms/${e}`), +]; + +for (let file of files) { + if (file.includes('.ftl')) { + // Build using mailwind + await exec( + `mailwind --input-html ${path.join( + __dirname, + `../theme/keywind/email/html-src/${file}` + )} --output-html ${path.join(__dirname, `../theme/keywind/email/html/${file}`)}` + ); + // fix: wrong build result for ftl-file exception + const data = readFileSync(path.join(__dirname, `../theme/keywind/email/html/${file}`)); + const result = data + .toString() + .replaceAll('', '>'); + writeFileSync(path.join(__dirname, `../theme/keywind/email/html/${file}`), result); + } +} diff --git a/theme/keywind/email/html-src/components/atoms/background.ftl b/theme/keywind/email/html-src/components/atoms/background.ftl new file mode 100644 index 000000000..f05db209b --- /dev/null +++ b/theme/keywind/email/html-src/components/atoms/background.ftl @@ -0,0 +1,11 @@ +<#macro kw> + + + + +
+
+ diff --git a/theme/keywind/email/html-src/components/atoms/card.ftl b/theme/keywind/email/html-src/components/atoms/card.ftl new file mode 100644 index 000000000..54899ac5b --- /dev/null +++ b/theme/keywind/email/html-src/components/atoms/card.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ <#nested> +
+ diff --git a/theme/keywind/email/html-src/components/atoms/container.ftl b/theme/keywind/email/html-src/components/atoms/container.ftl new file mode 100644 index 000000000..eab6b693f --- /dev/null +++ b/theme/keywind/email/html-src/components/atoms/container.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ <#nested> +
+ diff --git a/theme/keywind/email/html-src/components/atoms/footer.ftl b/theme/keywind/email/html-src/components/atoms/footer.ftl new file mode 100644 index 000000000..1d04a6fac --- /dev/null +++ b/theme/keywind/email/html-src/components/atoms/footer.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ © ${.now?string('yyyy')} ${kcSanitize(realmName!"")}. +
+ diff --git a/theme/keywind/email/html-src/components/atoms/header.ftl b/theme/keywind/email/html-src/components/atoms/header.ftl new file mode 100644 index 000000000..63d075c5d --- /dev/null +++ b/theme/keywind/email/html-src/components/atoms/header.ftl @@ -0,0 +1,15 @@ +<#macro kw> + + + + +
+ <#if (properties.headingLogoUrl!"") != ""> + logo + <#else> +

+ ${kcSanitize(kcSanitize(realmName!""))} +

+ +
+ diff --git a/theme/keywind/email/html-src/components/atoms/main.ftl b/theme/keywind/email/html-src/components/atoms/main.ftl new file mode 100644 index 000000000..5e64f4cfd --- /dev/null +++ b/theme/keywind/email/html-src/components/atoms/main.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ <#nested> +
+ diff --git a/theme/keywind/email/html-src/document.ftl b/theme/keywind/email/html-src/document.ftl new file mode 100644 index 000000000..2f2485110 --- /dev/null +++ b/theme/keywind/email/html-src/document.ftl @@ -0,0 +1,13 @@ +<#macro kw script=""> + + + + + + + <#if properties.meta?has_content> + <#list properties.meta?split(" ") as meta> + + + + diff --git a/theme/keywind/email/html-src/email-test.ftl b/theme/keywind/email/html-src/email-test.ftl new file mode 100644 index 000000000..bca248043 --- /dev/null +++ b/theme/keywind/email/html-src/email-test.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailTestBodyHtml",realmName))?no_esc} + diff --git a/theme/keywind/email/html-src/email-update-confirmation.ftl b/theme/keywind/email/html-src/email-update-confirmation.ftl new file mode 100644 index 000000000..d618ed7a4 --- /dev/null +++ b/theme/keywind/email/html-src/email-update-confirmation.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailUpdateConfirmationBodyHtml",link, newEmail, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html-src/email-verification-with-code.ftl b/theme/keywind/email/html-src/email-verification-with-code.ftl new file mode 100644 index 000000000..6a73f50ad --- /dev/null +++ b/theme/keywind/email/html-src/email-verification-with-code.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailVerificationBodyCodeHtml",code))?no_esc} + diff --git a/theme/keywind/email/html-src/email-verification.ftl b/theme/keywind/email/html-src/email-verification.ftl new file mode 100644 index 000000000..9a2e318c8 --- /dev/null +++ b/theme/keywind/email/html-src/email-verification.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailVerificationBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html-src/event-login_error.ftl b/theme/keywind/email/html-src/event-login_error.ftl new file mode 100644 index 000000000..7ab9401d8 --- /dev/null +++ b/theme/keywind/email/html-src/event-login_error.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventLoginErrorBodyHtml",event.date,event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html-src/event-remove_totp.ftl b/theme/keywind/email/html-src/event-remove_totp.ftl new file mode 100644 index 000000000..6058bbe00 --- /dev/null +++ b/theme/keywind/email/html-src/event-remove_totp.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventRemoveTotpBodyHtml",event.date, event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html-src/event-update_password.ftl b/theme/keywind/email/html-src/event-update_password.ftl new file mode 100644 index 000000000..8a802ecc4 --- /dev/null +++ b/theme/keywind/email/html-src/event-update_password.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventUpdatePasswordBodyHtml",event.date, event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html-src/event-update_totp.ftl b/theme/keywind/email/html-src/event-update_totp.ftl new file mode 100644 index 000000000..f58c38ab6 --- /dev/null +++ b/theme/keywind/email/html-src/event-update_totp.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventUpdateTotpBodyHtml",event.date, event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html-src/executeActions.ftl b/theme/keywind/email/html-src/executeActions.ftl new file mode 100644 index 000000000..73d407550 --- /dev/null +++ b/theme/keywind/email/html-src/executeActions.ftl @@ -0,0 +1,8 @@ +<#outputformat "plainText"> +<#assign requiredActionsText><#if requiredActions??><#list requiredActions><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, + + +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("executeActionsBodyHtml",link, linkExpiration, realmName, requiredActionsText, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html-src/identity-provider-link.ftl b/theme/keywind/email/html-src/identity-provider-link.ftl new file mode 100644 index 000000000..6b4381e1a --- /dev/null +++ b/theme/keywind/email/html-src/identity-provider-link.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("identityProviderLinkBodyHtml", identityProviderDisplayName, realmName, identityProviderContext.username, link, linkExpiration, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html-src/password-reset.ftl b/theme/keywind/email/html-src/password-reset.ftl new file mode 100644 index 000000000..b0de26329 --- /dev/null +++ b/theme/keywind/email/html-src/password-reset.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("passwordResetBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html-src/template.ftl b/theme/keywind/email/html-src/template.ftl new file mode 100644 index 000000000..838b89997 --- /dev/null +++ b/theme/keywind/email/html-src/template.ftl @@ -0,0 +1,28 @@ +<#import "document.ftl" as document> +<#import "components/atoms/background.ftl" as background> +<#import "components/atoms/card.ftl" as card> +<#import "components/atoms/container.ftl" as container> +<#import "components/atoms/footer.ftl" as footer> +<#import "components/atoms/header.ftl" as header> +<#import "components/atoms/main.ftl" as main> + +<#macro emailLayout> + + + + <@document.kw script=script /> + + + <@background.kw /> + <@container.kw> + <@card.kw> + <@header.kw /> + <@main.kw> + <#nested> + + + <@footer.kw /> + + + + diff --git a/theme/keywind/email/html/components/atoms/background.ftl b/theme/keywind/email/html/components/atoms/background.ftl new file mode 100644 index 000000000..a42ff964e --- /dev/null +++ b/theme/keywind/email/html/components/atoms/background.ftl @@ -0,0 +1,8 @@ +<#macro kw> + + + + +
+
+ diff --git a/theme/keywind/email/html/components/atoms/body.ftl b/theme/keywind/email/html/components/atoms/body.ftl new file mode 100644 index 000000000..a4526d31d --- /dev/null +++ b/theme/keywind/email/html/components/atoms/body.ftl @@ -0,0 +1,10 @@ +<#macro kw> + + +
+ <#nested> +
+ + + + \ No newline at end of file diff --git a/theme/keywind/email/html/components/atoms/card.ftl b/theme/keywind/email/html/components/atoms/card.ftl new file mode 100644 index 000000000..29f40b9de --- /dev/null +++ b/theme/keywind/email/html/components/atoms/card.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ <#nested> +
+ diff --git a/theme/keywind/email/html/components/atoms/container.ftl b/theme/keywind/email/html/components/atoms/container.ftl new file mode 100644 index 000000000..04ee64f64 --- /dev/null +++ b/theme/keywind/email/html/components/atoms/container.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ <#nested> +
+ diff --git a/theme/keywind/email/html/components/atoms/footer.ftl b/theme/keywind/email/html/components/atoms/footer.ftl new file mode 100644 index 000000000..80881690c --- /dev/null +++ b/theme/keywind/email/html/components/atoms/footer.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ © ${.now?string('yyyy')} ${kcSanitize(realmName!"")}. +
+ diff --git a/theme/keywind/email/html/components/atoms/header.ftl b/theme/keywind/email/html/components/atoms/header.ftl new file mode 100644 index 000000000..a8d522c61 --- /dev/null +++ b/theme/keywind/email/html/components/atoms/header.ftl @@ -0,0 +1,15 @@ +<#macro kw> + + + + +
+ <#if (properties.headingLogoUrl!"") != ""> + logo + <#else> +

+ ${kcSanitize(kcSanitize(realmName!""))} +

+ +
+ diff --git a/theme/keywind/email/html/components/atoms/main.ftl b/theme/keywind/email/html/components/atoms/main.ftl new file mode 100644 index 000000000..5b9f05e72 --- /dev/null +++ b/theme/keywind/email/html/components/atoms/main.ftl @@ -0,0 +1,9 @@ +<#macro kw> + + + + +
+ <#nested> +
+ diff --git a/theme/keywind/email/html/document.ftl b/theme/keywind/email/html/document.ftl new file mode 100644 index 000000000..6a0831dfa --- /dev/null +++ b/theme/keywind/email/html/document.ftl @@ -0,0 +1,13 @@ +<#macro kw script=""> + + + + + + + <#if properties.meta?has_content> + <#list properties.meta?split(" ") as meta> + + + + diff --git a/theme/keywind/email/html/email-test.ftl b/theme/keywind/email/html/email-test.ftl new file mode 100644 index 000000000..bca248043 --- /dev/null +++ b/theme/keywind/email/html/email-test.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailTestBodyHtml",realmName))?no_esc} + diff --git a/theme/keywind/email/html/email-update-confirmation.ftl b/theme/keywind/email/html/email-update-confirmation.ftl new file mode 100644 index 000000000..d618ed7a4 --- /dev/null +++ b/theme/keywind/email/html/email-update-confirmation.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailUpdateConfirmationBodyHtml",link, newEmail, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html/email-verification-with-code.ftl b/theme/keywind/email/html/email-verification-with-code.ftl new file mode 100644 index 000000000..6a73f50ad --- /dev/null +++ b/theme/keywind/email/html/email-verification-with-code.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailVerificationBodyCodeHtml",code))?no_esc} + diff --git a/theme/keywind/email/html/email-verification.ftl b/theme/keywind/email/html/email-verification.ftl new file mode 100644 index 000000000..9a2e318c8 --- /dev/null +++ b/theme/keywind/email/html/email-verification.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("emailVerificationBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html/event-login_error.ftl b/theme/keywind/email/html/event-login_error.ftl new file mode 100644 index 000000000..7ab9401d8 --- /dev/null +++ b/theme/keywind/email/html/event-login_error.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventLoginErrorBodyHtml",event.date,event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html/event-remove_totp.ftl b/theme/keywind/email/html/event-remove_totp.ftl new file mode 100644 index 000000000..6058bbe00 --- /dev/null +++ b/theme/keywind/email/html/event-remove_totp.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventRemoveTotpBodyHtml",event.date, event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html/event-update_password.ftl b/theme/keywind/email/html/event-update_password.ftl new file mode 100644 index 000000000..8a802ecc4 --- /dev/null +++ b/theme/keywind/email/html/event-update_password.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventUpdatePasswordBodyHtml",event.date, event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html/event-update_totp.ftl b/theme/keywind/email/html/event-update_totp.ftl new file mode 100644 index 000000000..f58c38ab6 --- /dev/null +++ b/theme/keywind/email/html/event-update_totp.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("eventUpdateTotpBodyHtml",event.date, event.ipAddress))?no_esc} + diff --git a/theme/keywind/email/html/executeActions.ftl b/theme/keywind/email/html/executeActions.ftl new file mode 100644 index 000000000..73d407550 --- /dev/null +++ b/theme/keywind/email/html/executeActions.ftl @@ -0,0 +1,8 @@ +<#outputformat "plainText"> +<#assign requiredActionsText><#if requiredActions??><#list requiredActions><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, + + +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("executeActionsBodyHtml",link, linkExpiration, realmName, requiredActionsText, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html/identity-provider-link.ftl b/theme/keywind/email/html/identity-provider-link.ftl new file mode 100644 index 000000000..6b4381e1a --- /dev/null +++ b/theme/keywind/email/html/identity-provider-link.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("identityProviderLinkBodyHtml", identityProviderDisplayName, realmName, identityProviderContext.username, link, linkExpiration, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html/password-reset.ftl b/theme/keywind/email/html/password-reset.ftl new file mode 100644 index 000000000..b0de26329 --- /dev/null +++ b/theme/keywind/email/html/password-reset.ftl @@ -0,0 +1,4 @@ +<#import "template.ftl" as layout> +<@layout.emailLayout> +${kcSanitize(msg("passwordResetBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc} + diff --git a/theme/keywind/email/html/template.ftl b/theme/keywind/email/html/template.ftl new file mode 100644 index 000000000..838b89997 --- /dev/null +++ b/theme/keywind/email/html/template.ftl @@ -0,0 +1,28 @@ +<#import "document.ftl" as document> +<#import "components/atoms/background.ftl" as background> +<#import "components/atoms/card.ftl" as card> +<#import "components/atoms/container.ftl" as container> +<#import "components/atoms/footer.ftl" as footer> +<#import "components/atoms/header.ftl" as header> +<#import "components/atoms/main.ftl" as main> + +<#macro emailLayout> + + + + <@document.kw script=script /> + + + <@background.kw /> + <@container.kw> + <@card.kw> + <@header.kw /> + <@main.kw> + <#nested> + + + <@footer.kw /> + + + + diff --git a/theme/keywind/email/text/email-test.ftl b/theme/keywind/email/text/email-test.ftl new file mode 100644 index 000000000..f1becdd2a --- /dev/null +++ b/theme/keywind/email/text/email-test.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("emailTestBody", realmName)} \ No newline at end of file diff --git a/theme/keywind/email/text/email-update-confirmation.ftl b/theme/keywind/email/text/email-update-confirmation.ftl new file mode 100644 index 000000000..335d9a848 --- /dev/null +++ b/theme/keywind/email/text/email-update-confirmation.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("emailUpdateConfirmationBody",link, newEmail, realmName, linkExpirationFormatter(linkExpiration))} diff --git a/theme/keywind/email/text/email-verification-with-code.ftl b/theme/keywind/email/text/email-verification-with-code.ftl new file mode 100644 index 000000000..4ffb7d879 --- /dev/null +++ b/theme/keywind/email/text/email-verification-with-code.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("emailVerificationBodyCode",code)} \ No newline at end of file diff --git a/theme/keywind/email/text/email-verification.ftl b/theme/keywind/email/text/email-verification.ftl new file mode 100644 index 000000000..9e3969632 --- /dev/null +++ b/theme/keywind/email/text/email-verification.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("emailVerificationBody",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/theme/keywind/email/text/event-login_error.ftl b/theme/keywind/email/text/event-login_error.ftl new file mode 100644 index 000000000..bfb4036f5 --- /dev/null +++ b/theme/keywind/email/text/event-login_error.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventLoginErrorBody",event.date,event.ipAddress)} \ No newline at end of file diff --git a/theme/keywind/email/text/event-remove_totp.ftl b/theme/keywind/email/text/event-remove_totp.ftl new file mode 100644 index 000000000..a7e3b6860 --- /dev/null +++ b/theme/keywind/email/text/event-remove_totp.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventRemoveTotpBody",event.date, event.ipAddress)} \ No newline at end of file diff --git a/theme/keywind/email/text/event-update_password.ftl b/theme/keywind/email/text/event-update_password.ftl new file mode 100644 index 000000000..2ec7ea01e --- /dev/null +++ b/theme/keywind/email/text/event-update_password.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventUpdatePasswordBody",event.date, event.ipAddress)} \ No newline at end of file diff --git a/theme/keywind/email/text/event-update_totp.ftl b/theme/keywind/email/text/event-update_totp.ftl new file mode 100644 index 000000000..14778b5ff --- /dev/null +++ b/theme/keywind/email/text/event-update_totp.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("eventUpdateTotpBody",event.date, event.ipAddress)} \ No newline at end of file diff --git a/theme/keywind/email/text/executeActions.ftl b/theme/keywind/email/text/executeActions.ftl new file mode 100644 index 000000000..6610c7ada --- /dev/null +++ b/theme/keywind/email/text/executeActions.ftl @@ -0,0 +1,4 @@ +<#ftl output_format="plainText"> +<#assign requiredActionsText><#if requiredActions??><#list requiredActions><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, <#else> + +${msg("executeActionsBody",link, linkExpiration, realmName, requiredActionsText, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/theme/keywind/email/text/identity-provider-link.ftl b/theme/keywind/email/text/identity-provider-link.ftl new file mode 100644 index 000000000..17e3cc45a --- /dev/null +++ b/theme/keywind/email/text/identity-provider-link.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("identityProviderLinkBody", identityProviderDisplayName, realmName, identityProviderContext.username, link, linkExpiration, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/theme/keywind/email/text/password-reset.ftl b/theme/keywind/email/text/password-reset.ftl new file mode 100644 index 000000000..27405c9a4 --- /dev/null +++ b/theme/keywind/email/text/password-reset.ftl @@ -0,0 +1,2 @@ +<#ftl output_format="plainText"> +${msg("passwordResetBody",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration))} \ No newline at end of file diff --git a/theme/keywind/email/theme.properties b/theme/keywind/email/theme.properties new file mode 100644 index 000000000..5012e0a31 --- /dev/null +++ b/theme/keywind/email/theme.properties @@ -0,0 +1,3 @@ +parent=base + +headingLogoUrl=