mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
Compare commits
4 commits
4c65c015b9
...
a7e76f3c07
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7e76f3c07 | ||
|
|
627d9ad09b | ||
|
|
583d8b2440 | ||
|
|
b18d561361 |
10 changed files with 1044 additions and 28 deletions
4
composer.lock
generated
4
composer.lock
generated
|
|
@ -2842,7 +2842,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/flux",
|
"name": "livewire/flux",
|
||||||
"version": "v2.5.0",
|
"version": "v2.5.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/livewire/flux.git",
|
"url": "https://github.com/livewire/flux.git",
|
||||||
|
|
@ -2902,7 +2902,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/livewire/flux/issues",
|
"issues": "https://github.com/livewire/flux/issues",
|
||||||
"source": "https://github.com/livewire/flux/tree/v2.5.0"
|
"source": "https://github.com/livewire/flux/tree/v2.5.1"
|
||||||
},
|
},
|
||||||
"time": "2025-09-29T21:36:00+00:00"
|
"time": "2025-09-29T21:36:00+00:00"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
296
package-lock.json
generated
296
package-lock.json
generated
|
|
@ -5,9 +5,22 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.9.0",
|
||||||
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
"@codemirror/lang-html": "^6.4.11",
|
||||||
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
|
"@codemirror/lang-liquid": "^6.3.0",
|
||||||
|
"@codemirror/language": "^6.11.3",
|
||||||
|
"@codemirror/search": "^6.5.11",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
|
"@codemirror/view": "^6.38.5",
|
||||||
|
"@fsegurai/codemirror-theme-github-light": "^6.2.2",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
|
"codemirror": "^6.0.2",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"laravel-vite-plugin": "^2.0",
|
"laravel-vite-plugin": "^2.0",
|
||||||
"puppeteer": "24.17.0",
|
"puppeteer": "24.17.0",
|
||||||
|
|
@ -43,6 +56,170 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.19.0.tgz",
|
||||||
|
"integrity": "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-454TVgjhO6cMufsyyGN70rGIfJxJEjcqjBG2x2Y03Y/+Fm99d3O/Kv1QDYWuG6hvxsgmjXmBuATikIIYvERX+w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.27.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css": {
|
||||||
|
"version": "6.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
|
||||||
|
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.2",
|
||||||
|
"@lezer/css": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html": {
|
||||||
|
"version": "6.4.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
|
||||||
|
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/lang-css": "^6.0.0",
|
||||||
|
"@codemirror/lang-javascript": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.4.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/css": "^1.1.0",
|
||||||
|
"@lezer/html": "^1.3.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript": {
|
||||||
|
"version": "6.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
|
||||||
|
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.6.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/javascript": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-json": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@lezer/json": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-liquid": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/lang-html": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
|
||||||
|
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.0.tgz",
|
||||||
|
"integrity": "sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search": {
|
||||||
|
"version": "6.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||||
|
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.38.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.5.tgz",
|
||||||
|
"integrity": "sha512-SFVsNAgsAoou+BjRewMqN+m9jaztB9wCWN9RSRgePqUbq8UVlvJfku5zB2KVhLPgH/h0RLk38tvd4tGeAhygnw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"crelt": "^1.0.6",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.10",
|
"version": "0.25.10",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz",
|
||||||
|
|
@ -459,6 +636,18 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fsegurai/codemirror-theme-github-light": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fsegurai/codemirror-theme-github-light/-/codemirror-theme-github-light-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-YQr5MbhMlhRlAQcSCSbet4NDDkMvd5sbUyk9JmM0vfZhQbatvw4c56gNG/54JKGM0kWY5zRWzgLtFuz6D7yEsw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/fs-minipass": {
|
"node_modules/@isaacs/fs-minipass": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||||
|
|
@ -516,6 +705,80 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/css": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/html": {
|
||||||
|
"version": "1.3.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
|
||||||
|
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/javascript": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.1.3",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/json": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@puppeteer/browsers": {
|
"node_modules/@puppeteer/browsers": {
|
||||||
"version": "2.10.7",
|
"version": "2.10.7",
|
||||||
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.7.tgz",
|
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.7.tgz",
|
||||||
|
|
@ -1485,6 +1748,21 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
|
@ -1565,6 +1843,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/data-uri-to-buffer": {
|
"node_modules/data-uri-to-buffer": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
|
||||||
|
|
@ -3021,6 +3305,12 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "8.1.1",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||||
|
|
@ -3275,6 +3565,12 @@
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
|
|
||||||
13
package.json
13
package.json
|
|
@ -6,9 +6,22 @@
|
||||||
"dev": "vite"
|
"dev": "vite"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/commands": "^6.9.0",
|
||||||
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
"@codemirror/lang-html": "^6.4.11",
|
||||||
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
|
"@codemirror/lang-json": "^6.0.2",
|
||||||
|
"@codemirror/lang-liquid": "^6.3.0",
|
||||||
|
"@codemirror/language": "^6.11.3",
|
||||||
|
"@codemirror/search": "^6.5.11",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
|
"@codemirror/view": "^6.38.5",
|
||||||
|
"@fsegurai/codemirror-theme-github-light": "^6.2.2",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
|
"codemirror": "^6.0.2",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"laravel-vite-plugin": "^2.0",
|
"laravel-vite-plugin": "^2.0",
|
||||||
"puppeteer": "24.17.0",
|
"puppeteer": "24.17.0",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { codeEditorFormComponent } from './codemirror-alpine.js';
|
||||||
|
|
||||||
|
window.codeEditorFormComponent = codeEditorFormComponent;
|
||||||
198
resources/js/codemirror-alpine.js
Normal file
198
resources/js/codemirror-alpine.js
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
import { createCodeMirror, getSystemTheme, watchThemeChange } from './codemirror-core.js';
|
||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alpine.js component for CodeMirror that integrates with textarea and Livewire
|
||||||
|
* Inspired by Filament's approach with proper state entanglement
|
||||||
|
* @param {Object} config - Configuration object
|
||||||
|
* @returns {Object} Alpine.js component object
|
||||||
|
*/
|
||||||
|
export function codeEditorFormComponent(config) {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
textarea: null,
|
||||||
|
isLoading: false,
|
||||||
|
unwatchTheme: null,
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
isDisabled: config.isDisabled || false,
|
||||||
|
language: config.language || 'html',
|
||||||
|
state: config.state || '',
|
||||||
|
textareaId: config.textareaId || null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Wait for textarea if provided
|
||||||
|
if (this.textareaId) {
|
||||||
|
await this.waitForTextarea();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$nextTick();
|
||||||
|
this.createEditor();
|
||||||
|
this.setupEventListeners();
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for textarea to be available in the DOM
|
||||||
|
*/
|
||||||
|
async waitForTextarea() {
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 50; // 5 seconds max wait
|
||||||
|
|
||||||
|
while (attempts < maxAttempts) {
|
||||||
|
this.textarea = document.getElementById(this.textareaId);
|
||||||
|
if (this.textarea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait 100ms before trying again
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`Textarea with ID "${this.textareaId}" not found after ${maxAttempts} attempts`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update both Livewire state and textarea with new value
|
||||||
|
*/
|
||||||
|
updateState(value) {
|
||||||
|
this.state = value;
|
||||||
|
if (this.textarea) {
|
||||||
|
this.textarea.value = value;
|
||||||
|
this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the CodeMirror editor instance
|
||||||
|
*/
|
||||||
|
createEditor() {
|
||||||
|
// Clean up any existing editor first
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveTheme = this.getEffectiveTheme();
|
||||||
|
const initialValue = this.textarea ? this.textarea.value : this.state;
|
||||||
|
|
||||||
|
this.editor = createCodeMirror(this.$refs.editor, {
|
||||||
|
value: initialValue || '',
|
||||||
|
language: this.language,
|
||||||
|
theme: effectiveTheme,
|
||||||
|
readOnly: this.isDisabled,
|
||||||
|
onChange: (value) => this.updateState(value),
|
||||||
|
onUpdate: (value) => this.updateState(value),
|
||||||
|
onBlur: () => {
|
||||||
|
if (this.editor) {
|
||||||
|
this.updateState(this.editor.state.doc.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get effective theme
|
||||||
|
*/
|
||||||
|
getEffectiveTheme() {
|
||||||
|
return getSystemTheme();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update editor content with new value
|
||||||
|
*/
|
||||||
|
updateEditorContent(value) {
|
||||||
|
if (this.editor && value !== this.editor.state.doc.toString()) {
|
||||||
|
this.editor.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: this.editor.state.doc.length,
|
||||||
|
insert: value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup event listeners for theme changes and state synchronization
|
||||||
|
*/
|
||||||
|
setupEventListeners() {
|
||||||
|
// Watch for state changes from Livewire
|
||||||
|
this.$watch('state', (newValue) => {
|
||||||
|
this.updateEditorContent(newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for disabled state changes
|
||||||
|
this.$watch('isDisabled', (newValue) => {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.dispatch({
|
||||||
|
effects: EditorView.editable.reconfigure(!newValue)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for textarea changes (from Livewire updates)
|
||||||
|
if (this.textarea) {
|
||||||
|
this.textarea.addEventListener('input', (event) => {
|
||||||
|
this.updateEditorContent(event.target.value);
|
||||||
|
this.state = event.target.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for Livewire updates that might change the textarea value
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
|
||||||
|
this.updateEditorContent(this.textarea.value);
|
||||||
|
this.state = this.textarea.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(this.textarea, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['value']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for theme changes
|
||||||
|
this.unwatchTheme = watchThemeChange(() => {
|
||||||
|
this.recreateEditor();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreate the editor (useful for theme changes)
|
||||||
|
*/
|
||||||
|
async recreateEditor() {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.destroy();
|
||||||
|
this.editor = null;
|
||||||
|
await this.$nextTick();
|
||||||
|
this.createEditor();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up resources when component is destroyed
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.destroy();
|
||||||
|
this.editor = null;
|
||||||
|
}
|
||||||
|
if (this.unwatchTheme) {
|
||||||
|
this.unwatchTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
255
resources/js/codemirror-core.js
Normal file
255
resources/js/codemirror-core.js
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
import { EditorView, lineNumbers, keymap } from '@codemirror/view';
|
||||||
|
import { ViewPlugin } from '@codemirror/view';
|
||||||
|
import { indentWithTab } from '@codemirror/commands';
|
||||||
|
import { foldGutter, foldKeymap } from '@codemirror/language';
|
||||||
|
import { history, historyKeymap } from '@codemirror/commands';
|
||||||
|
import { html } from '@codemirror/lang-html';
|
||||||
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
|
import { json } from '@codemirror/lang-json';
|
||||||
|
import { css } from '@codemirror/lang-css';
|
||||||
|
import { liquid } from '@codemirror/lang-liquid';
|
||||||
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
|
import { githubLight } from '@fsegurai/codemirror-theme-github-light';
|
||||||
|
|
||||||
|
// Language support mapping
|
||||||
|
const LANGUAGE_MAP = {
|
||||||
|
'javascript': javascript,
|
||||||
|
'js': javascript,
|
||||||
|
'json': json,
|
||||||
|
'css': css,
|
||||||
|
'liquid': liquid,
|
||||||
|
'html': html,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Theme support mapping
|
||||||
|
const THEME_MAP = {
|
||||||
|
'light': githubLight,
|
||||||
|
'dark': oneDark,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get language support based on language parameter
|
||||||
|
* @param {string} language - Language name or comma-separated list
|
||||||
|
* @returns {Array|Extension} Language extension(s)
|
||||||
|
*/
|
||||||
|
function getLanguageSupport(language) {
|
||||||
|
// Handle comma-separated languages
|
||||||
|
if (language.includes(',')) {
|
||||||
|
const languages = language.split(',').map(lang => lang.trim().toLowerCase());
|
||||||
|
const languageExtensions = [];
|
||||||
|
|
||||||
|
languages.forEach(lang => {
|
||||||
|
const languageFn = LANGUAGE_MAP[lang];
|
||||||
|
if (languageFn) {
|
||||||
|
languageExtensions.push(languageFn());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return languageExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle single language
|
||||||
|
const languageFn = LANGUAGE_MAP[language.toLowerCase()] || LANGUAGE_MAP.html;
|
||||||
|
return languageFn();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get theme support
|
||||||
|
* @param {string} theme - Theme name
|
||||||
|
* @returns {Array} Theme extensions
|
||||||
|
*/
|
||||||
|
function getThemeSupport(theme) {
|
||||||
|
const themeFn = THEME_MAP[theme] || THEME_MAP.light;
|
||||||
|
return [themeFn];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a resize plugin that handles container resizing
|
||||||
|
* @returns {ViewPlugin} Resize plugin
|
||||||
|
*/
|
||||||
|
function createResizePlugin() {
|
||||||
|
return ViewPlugin.fromClass(class {
|
||||||
|
constructor(view) {
|
||||||
|
this.view = view;
|
||||||
|
this.resizeObserver = null;
|
||||||
|
this.setupResizeObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupResizeObserver() {
|
||||||
|
const container = this.view.dom.parentElement;
|
||||||
|
if (container) {
|
||||||
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
|
// Use requestAnimationFrame to ensure proper timing
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.view.requestMeasure();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.resizeObserver.observe(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Flux-like theme styling based on theme
|
||||||
|
* @param {string} theme - Theme name ('light', 'dark', or 'auto')
|
||||||
|
* @returns {Object} Theme-specific styling
|
||||||
|
*/
|
||||||
|
function getFluxThemeStyling(theme) {
|
||||||
|
const isDark = theme === 'dark' || (theme === 'auto' && getSystemTheme() === 'dark');
|
||||||
|
|
||||||
|
if (isDark) {
|
||||||
|
return {
|
||||||
|
backgroundColor: 'oklab(0.999994 0.0000455678 0.0000200868 / 0.1)',
|
||||||
|
gutterBackgroundColor: 'oklch(26.9% 0 0)',
|
||||||
|
borderColor: '#374151',
|
||||||
|
focusBorderColor: 'rgb(224 91 68)',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
backgroundColor: '#fff', // zinc-50
|
||||||
|
gutterBackgroundColor: '#fafafa', // zinc-50
|
||||||
|
borderColor: '#e5e7eb', // gray-200
|
||||||
|
focusBorderColor: 'rgb(224 91 68)', // red-500
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create CodeMirror editor instance
|
||||||
|
* @param {HTMLElement} element - DOM element to mount editor
|
||||||
|
* @param {Object} options - Editor options
|
||||||
|
* @returns {EditorView} CodeMirror editor instance
|
||||||
|
*/
|
||||||
|
export function createCodeMirror(element, options = {}) {
|
||||||
|
const {
|
||||||
|
value = '',
|
||||||
|
language = 'html',
|
||||||
|
theme = 'light',
|
||||||
|
readOnly = false,
|
||||||
|
onChange = () => {},
|
||||||
|
onUpdate = () => {},
|
||||||
|
onBlur = () => {}
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// Get language and theme support
|
||||||
|
const languageSupport = getLanguageSupport(language);
|
||||||
|
const themeSupport = getThemeSupport(theme);
|
||||||
|
const fluxStyling = getFluxThemeStyling(theme);
|
||||||
|
|
||||||
|
// Create editor
|
||||||
|
const editor = new EditorView({
|
||||||
|
doc: value,
|
||||||
|
extensions: [
|
||||||
|
lineNumbers(),
|
||||||
|
foldGutter(),
|
||||||
|
history(),
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
createResizePlugin(),
|
||||||
|
...(Array.isArray(languageSupport) ? languageSupport : [languageSupport]),
|
||||||
|
...themeSupport,
|
||||||
|
keymap.of([indentWithTab, ...foldKeymap, ...historyKeymap]),
|
||||||
|
EditorView.theme({
|
||||||
|
'&': {
|
||||||
|
fontSize: '14px',
|
||||||
|
border: `1px solid ${fluxStyling.borderColor}`,
|
||||||
|
borderRadius: '0.375rem',
|
||||||
|
height: '100%',
|
||||||
|
maxHeight: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
backgroundColor: fluxStyling.backgroundColor + ' !important',
|
||||||
|
resize: 'vertical',
|
||||||
|
minHeight: '200px',
|
||||||
|
},
|
||||||
|
'.cm-gutters': {
|
||||||
|
borderTopLeftRadius: '0.375rem',
|
||||||
|
backgroundColor: fluxStyling.gutterBackgroundColor + ' !important',
|
||||||
|
},
|
||||||
|
'.cm-gutter': {
|
||||||
|
backgroundColor: fluxStyling.gutterBackgroundColor + ' !important',
|
||||||
|
},
|
||||||
|
'&.cm-focused': {
|
||||||
|
outline: 'none',
|
||||||
|
borderColor: fluxStyling.focusBorderColor,
|
||||||
|
},
|
||||||
|
'.cm-content': {
|
||||||
|
padding: '12px',
|
||||||
|
},
|
||||||
|
'.cm-scroller': {
|
||||||
|
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
},
|
||||||
|
'.cm-editor': {
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
'.cm-editor .cm-scroller': {
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
},
|
||||||
|
'.cm-foldGutter': {
|
||||||
|
width: '12px',
|
||||||
|
},
|
||||||
|
'.cm-foldGutter .cm-gutterElement': {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#6b7280',
|
||||||
|
},
|
||||||
|
'.cm-foldGutter .cm-gutterElement:hover': {
|
||||||
|
color: '#374151',
|
||||||
|
},
|
||||||
|
'.cm-foldGutter .cm-gutterElement.cm-folded': {
|
||||||
|
color: '#3b82f6',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EditorView.updateListener.of((update) => {
|
||||||
|
if (update.docChanged) {
|
||||||
|
const newValue = update.state.doc.toString();
|
||||||
|
onChange(newValue);
|
||||||
|
onUpdate(newValue);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EditorView.domEventHandlers({
|
||||||
|
blur: onBlur
|
||||||
|
}),
|
||||||
|
EditorView.editable.of(!readOnly),
|
||||||
|
],
|
||||||
|
parent: element
|
||||||
|
});
|
||||||
|
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-detect system theme preference
|
||||||
|
* @returns {string} 'dark' or 'light'
|
||||||
|
*/
|
||||||
|
export function getSystemTheme() {
|
||||||
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
return 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch for system theme changes
|
||||||
|
* @param {Function} callback - Callback function when theme changes
|
||||||
|
* @returns {Function} Unwatch function
|
||||||
|
*/
|
||||||
|
export function watchThemeChange(callback) {
|
||||||
|
if (window.matchMedia) {
|
||||||
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
mediaQuery.addEventListener('change', callback);
|
||||||
|
return () => mediaQuery.removeEventListener('change', callback);
|
||||||
|
}
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
64
resources/views/livewire/codemirror.blade.php
Normal file
64
resources/views/livewire/codemirror.blade.php
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
use Livewire\Attributes\Modelable;
|
||||||
|
|
||||||
|
new class extends Component
|
||||||
|
{
|
||||||
|
#[Modelable]
|
||||||
|
public $model = '';
|
||||||
|
public $language = 'html';
|
||||||
|
public $theme = 'auto';
|
||||||
|
public $readonly = false;
|
||||||
|
public $placeholder = '';
|
||||||
|
public $height = '200px';
|
||||||
|
public $id = '';
|
||||||
|
|
||||||
|
public function mount($language = 'html', $theme = 'auto', $readonly = false, $placeholder = '', $height = '200px', $id = '')
|
||||||
|
{
|
||||||
|
$this->language = $language;
|
||||||
|
$this->theme = $theme;
|
||||||
|
$this->readonly = $readonly;
|
||||||
|
$this->placeholder = $placeholder;
|
||||||
|
$this->height = $height;
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function toJSON()
|
||||||
|
{
|
||||||
|
return json_encode([
|
||||||
|
'model' => $this->model,
|
||||||
|
'language' => $this->language,
|
||||||
|
'theme' => $this->theme,
|
||||||
|
'readonly' => $this->readonly,
|
||||||
|
'placeholder' => $this->placeholder,
|
||||||
|
'height' => $this->height,
|
||||||
|
'id' => $this->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} ?>
|
||||||
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
x-data="codeMirrorComponent(@js($language), @js($theme), @js($readonly), @js($placeholder), @js($height), @js($id ?: uniqid()))"
|
||||||
|
x-init="init()"
|
||||||
|
wire:ignore
|
||||||
|
class="codemirror-container"
|
||||||
|
@if($id) id="{{ $id }}" @endif
|
||||||
|
autocomplete="off"
|
||||||
|
>
|
||||||
|
<!-- Loading state -->
|
||||||
|
<div x-show="isLoading" class="flex items-center justify-center p-4 border border-gray-300 rounded-md" style="height: {{ $height }};">
|
||||||
|
<div class="flex items-center space-x-2 text-gray-500">
|
||||||
|
<svg class="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Loading editor...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Editor container -->
|
||||||
|
<div x-show="!isLoading" x-ref="editor" style="height: {{ $height }};"></div>
|
||||||
|
</div>
|
||||||
|
|
@ -839,6 +839,15 @@ HTML;
|
||||||
</flux:select>
|
</flux:select>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@elseif($field['field_type'] === 'multi_string')
|
||||||
|
<flux:input
|
||||||
|
label="{{ $field['name'] }}"
|
||||||
|
description="{{ $field['description'] ?? '' }}"
|
||||||
|
descriptionTrailing="{{ $field['help_text'] ?? 'Enter multiple values separated by commas' }}"
|
||||||
|
wire:model="configuration.{{ $fieldKey }}"
|
||||||
|
value="{{ $currentValue }}"
|
||||||
|
placeholder="{{ $field['placeholder'] ?? 'value1,value2' }}"
|
||||||
|
/>
|
||||||
@else
|
@else
|
||||||
<flux:callout variant="warning">Field type "{{ $field['field_type'] }}" not yet supported</flux:callout>
|
<flux:callout variant="warning">Field type "{{ $field['field_type'] }}" not yet supported</flux:callout>
|
||||||
@endif
|
@endif
|
||||||
|
|
@ -1022,14 +1031,48 @@ HTML;
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<flux:label>Data Payload</flux:label>
|
<div class="mb-1">
|
||||||
@isset($this->data_payload_updated_at)
|
<flux:label>Data Payload</flux:label>
|
||||||
<flux:badge icon="clock" size="sm" variant="pill" class="ml-2">{{ $this->data_payload_updated_at?->diffForHumans() ?? 'Never' }}</flux:badge>
|
@isset($this->data_payload_updated_at)
|
||||||
@endisset
|
<flux:badge icon="clock" size="sm" variant="pill" class="ml-2">{{ $this->data_payload_updated_at?->diffForHumans() ?? 'Never' }}</flux:badge>
|
||||||
|
@endisset
|
||||||
|
</div>
|
||||||
<flux:error name="data_payload"/>
|
<flux:error name="data_payload"/>
|
||||||
<flux:textarea wire:model="data_payload" id="data_payload"
|
<flux:field>
|
||||||
class="block mt-1 w-full font-mono" type="text" name="data_payload"
|
@php
|
||||||
:readonly="$data_strategy !== 'static'" rows="24"/>
|
$textareaId = 'payload-' . uniqid();
|
||||||
|
@endphp
|
||||||
|
<flux:textarea
|
||||||
|
wire:model="data_payload"
|
||||||
|
id="{{ $textareaId }}"
|
||||||
|
placeholder="Enter your HTML code here..."
|
||||||
|
rows="12"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
x-data="codeEditorFormComponent({
|
||||||
|
isDisabled: @js($data_strategy !== 'static'),
|
||||||
|
language: 'json',
|
||||||
|
state: $wire.entangle('data_payload'),
|
||||||
|
textareaId: @js($textareaId)
|
||||||
|
})"
|
||||||
|
wire:ignore
|
||||||
|
wire:key="cm-{{ $textareaId }}"
|
||||||
|
class="max-w-2xl min-h-[300px] h-[500px] overflow-hidden resize-y"
|
||||||
|
>
|
||||||
|
<!-- Loading state -->
|
||||||
|
<div x-show="isLoading" class="flex items-center justify-center h-full">
|
||||||
|
<div class="flex items-center space-x-2 ">
|
||||||
|
<flux:icon.loading />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Editor container -->
|
||||||
|
<div x-show="!isLoading" x-ref="editor" class="h-full"></div>
|
||||||
|
</div>
|
||||||
|
</flux:field>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<flux:separator class="my-5"/>
|
<flux:separator class="my-5"/>
|
||||||
|
|
@ -1041,15 +1084,43 @@ HTML;
|
||||||
<span class="font-mono text-accent mb-4">{{ $plugin->render_markup_view }}</span> to update.
|
<span class="font-mono text-accent mb-4">{{ $plugin->render_markup_view }}</span> to update.
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 mt-4">
|
<div class="mb-4 mt-4">
|
||||||
<flux:textarea
|
<flux:field>
|
||||||
label="File Content"
|
@php
|
||||||
class="font-mono"
|
$textareaId = 'code-view-' . uniqid();
|
||||||
wire:model="view_content"
|
@endphp
|
||||||
id="view_content"
|
<flux:textarea
|
||||||
name="view_content"
|
wire:model="view_content"
|
||||||
rows="15"
|
id="{{ $textareaId }}"
|
||||||
readonly
|
placeholder="Enter your HTML code here..."
|
||||||
/>
|
rows="25"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
x-data="codeEditorFormComponent({
|
||||||
|
isDisabled: false,
|
||||||
|
language: 'liquid',
|
||||||
|
state: $wire.entangle('markup_code'),
|
||||||
|
textareaId: @js($textareaId)
|
||||||
|
})"
|
||||||
|
wire:ignore
|
||||||
|
wire:key="cm-{{ $textareaId }}"
|
||||||
|
class="min-h-[300px] h-[300px] overflow-hidden resize-y"
|
||||||
|
>
|
||||||
|
<!-- Loading state -->
|
||||||
|
<div x-show="isLoading" class="flex items-center justify-center h-full">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<flux:icon.loading />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Editor container -->
|
||||||
|
<div x-show="!isLoading" x-ref="editor" class="h-full"></div>
|
||||||
|
</div>
|
||||||
|
</flux:field>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="flex items-center gap-6 mb-4 mt-4">
|
<div class="flex items-center gap-6 mb-4 mt-4">
|
||||||
|
|
@ -1071,15 +1142,41 @@ HTML;
|
||||||
@if(!$plugin->render_markup_view)
|
@if(!$plugin->render_markup_view)
|
||||||
<form wire:submit="saveMarkup">
|
<form wire:submit="saveMarkup">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<flux:textarea
|
<flux:field>
|
||||||
label="{{ $markup_language === 'liquid' ? 'Liquid Code' : 'Blade Code' }}"
|
@php
|
||||||
class="font-mono"
|
$textareaId = 'code-' . uniqid();
|
||||||
wire:model="markup_code"
|
@endphp
|
||||||
id="markup_code"
|
<flux:label>{{ $markup_language === 'liquid' ? 'Liquid Code' : 'Blade Code' }}</flux:label>
|
||||||
name="markup_code"
|
<flux:textarea
|
||||||
rows="15"
|
wire:model="markup_code"
|
||||||
placeholder="{{ $markup_language === 'liquid' ? 'Enter your liquid code here...' : 'Enter your blade code here...' }}"
|
id="{{ $textareaId }}"
|
||||||
/>
|
placeholder="Enter your HTML code here..."
|
||||||
|
rows="25"
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
x-data="codeEditorFormComponent({
|
||||||
|
isDisabled: false,
|
||||||
|
language: 'liquid',
|
||||||
|
state: $wire.entangle('markup_code'),
|
||||||
|
textareaId: @js($textareaId)
|
||||||
|
})"
|
||||||
|
wire:ignore
|
||||||
|
wire:key="cm-{{ $textareaId }}"
|
||||||
|
class="min-h-[300px] h-[300px] overflow-hidden resize-y"
|
||||||
|
>
|
||||||
|
<!-- Loading state -->
|
||||||
|
<div x-show="isLoading" class="flex items-center justify-center h-full">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<flux:icon.loading />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Editor container -->
|
||||||
|
<div x-show="!isLoading" x-ref="editor" class="h-full"></div>
|
||||||
|
</div>
|
||||||
|
</flux:field>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
|
|
||||||
|
|
@ -268,3 +268,79 @@ test('hasMissingRequiredConfigurationFields returns false when required xhrSelec
|
||||||
|
|
||||||
expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse();
|
expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('hasMissingRequiredConfigurationFields returns true when required multi_string field is missing', function (): void {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$configurationTemplate = [
|
||||||
|
'custom_fields' => [
|
||||||
|
[
|
||||||
|
'keyname' => 'tags',
|
||||||
|
'field_type' => 'multi_string',
|
||||||
|
'name' => 'Tags',
|
||||||
|
'description' => 'Enter tags separated by commas',
|
||||||
|
// Not marked as optional, so it's required
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'configuration_template' => $configurationTemplate,
|
||||||
|
'configuration' => [], // Empty configuration
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($plugin->hasMissingRequiredConfigurationFields())->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasMissingRequiredConfigurationFields returns false when required multi_string field is set', function (): void {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$configurationTemplate = [
|
||||||
|
'custom_fields' => [
|
||||||
|
[
|
||||||
|
'keyname' => 'tags',
|
||||||
|
'field_type' => 'multi_string',
|
||||||
|
'name' => 'Tags',
|
||||||
|
'description' => 'Enter tags separated by commas',
|
||||||
|
// Not marked as optional, so it's required
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'configuration_template' => $configurationTemplate,
|
||||||
|
'configuration' => [
|
||||||
|
'tags' => 'tag1, tag2, tag3', // Required field is set with comma-separated values
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($plugin->hasMissingRequiredConfigurationFields())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasMissingRequiredConfigurationFields returns true when required multi_string field is empty string', function (): void {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$configurationTemplate = [
|
||||||
|
'custom_fields' => [
|
||||||
|
[
|
||||||
|
'keyname' => 'tags',
|
||||||
|
'field_type' => 'multi_string',
|
||||||
|
'name' => 'Tags',
|
||||||
|
'description' => 'Enter tags separated by commas',
|
||||||
|
// Not marked as optional, so it's required
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'configuration_template' => $configurationTemplate,
|
||||||
|
'configuration' => [
|
||||||
|
'tags' => '', // Empty string
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect($plugin->hasMissingRequiredConfigurationFields())->toBeTrue();
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,18 @@ export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
cors: true,
|
cors: true,
|
||||||
},
|
},
|
||||||
});
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: (id) => {
|
||||||
|
// Create a separate chunk for CodeMirror
|
||||||
|
if (id.includes('codemirror') ||
|
||||||
|
id.includes('@codemirror/') ||
|
||||||
|
id.includes('@fsegurai/codemirror-theme-github-light')) {
|
||||||
|
return 'codemirror';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue