diff --git a/assets/js/app.js b/assets/js/app.js index d5e278a..aa0af12 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -16,29 +16,42 @@ // // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html" +import "phoenix_html"; // Establish Phoenix Socket and LiveView configuration. -import {Socket} from "phoenix" -import {LiveSocket} from "phoenix_live_view" -import topbar from "../vendor/topbar" +import { Socket } from "phoenix"; +import { LiveSocket } from "phoenix_live_view"; +import topbar from "../vendor/topbar"; -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +window.addEventListener("phx:copy", (event) => { + let button = event.detail.dispatcher; + let text = event.target.innerText; + + navigator.clipboard.writeText(text).then(() => { + button.innerText = "Copied!"; + setTimeout(() => { + button.innerText = "Copy"; + }, 2000); + }); +}); + +let csrfToken = document + .querySelector("meta[name='csrf-token']") + .getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, - params: {_csrf_token: csrfToken} -}) + params: { _csrf_token: csrfToken }, +}); // Show progress bar on live navigation and form submits -topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) -window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) -window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) +topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }); +window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)); +window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); // connect if there are any LiveViews on the page -liveSocket.connect() +liveSocket.connect(); // expose liveSocket on window for web console debug logs and latency simulation: // >> liveSocket.enableDebug() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() -window.liveSocket = liveSocket - +window.liveSocket = liveSocket; diff --git a/assets/package-lock.json b/assets/package-lock.json index 52c356c..ad4288c 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -13,7 +13,12 @@ "phoenix": "^1.7.14", "phoenix_html": "^3.3.4", "phoenix_live_view": "^1.0.0-rc.7", + "shiki": "^1.22.0", "tailwindcss": "^3.4.14" + }, + "devDependencies": { + "@types/phoenix": "^1.6.5", + "@types/phoenix_live_view": "^0.18.5" } }, "node_modules/@alloc/quick-lru": { @@ -530,6 +535,57 @@ "node": ">=14" } }, + "node_modules/@shikijs/core": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.22.0.tgz", + "integrity": "sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==", + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.22.0.tgz", + "integrity": "sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-js": "0.4.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.0.tgz", + "integrity": "sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "node_modules/@shikijs/types": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.22.0.tgz", + "integrity": "sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", + "license": "MIT" + }, "node_modules/@tailwindcss/forms": { "version": "0.5.9", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz", @@ -542,6 +598,53 @@ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/phoenix_live_view": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@types/phoenix_live_view/-/phoenix_live_view-0.18.5.tgz", + "integrity": "sha512-mxj3KVkp+wX+hLFAILTbfIx5Q890TBgs/jxc6nmmVv6bW6Z9qer/5tZtGOcL3IES75QqqOHSTrjwwz0iZBs0lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/phoenix": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "license": "ISC" + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -639,6 +742,36 @@ "node": ">= 6" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -693,6 +826,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -728,6 +871,28 @@ "node": ">=4" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -923,6 +1088,52 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", + "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1040,6 +1251,27 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1049,6 +1281,95 @@ "node": ">= 8" } }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -1151,6 +1472,18 @@ "node": ">= 6" } }, + "node_modules/oniguruma-to-js": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", + "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", + "license": "MIT", + "dependencies": { + "regex": "^4.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -1396,6 +1729,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1437,6 +1780,12 @@ "node": ">=8.10.0" } }, + "node_modules/regex": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.3.tgz", + "integrity": "sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -1508,6 +1857,20 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.22.0.tgz", + "integrity": "sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.22.0", + "@shikijs/engine-javascript": "1.22.0", + "@shikijs/engine-oniguruma": "1.22.0", + "@shikijs/types": "1.22.0", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -1529,6 +1892,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -1588,6 +1961,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -1729,18 +2116,124 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1858,6 +2351,16 @@ "engines": { "node": ">= 14" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/assets/package.json b/assets/package.json index 73a93c5..0cd28ec 100644 --- a/assets/package.json +++ b/assets/package.json @@ -11,9 +11,14 @@ "@esbuild/linux-x64": "^0.24.0", "@tailwindcss/forms": "^0.5.9", "esbuild": "^0.24.0", - "phoenix": "^1.7.14", - "phoenix_html": "^3.3.4", - "phoenix_live_view": "^1.0.0-rc.7", + "phoenix": "file:../deps/phoenix", + "phoenix_html": "file:../deps/phoenix_html", + "phoenix_live_view": "file:../deps/phoenix_live_view", + "shiki": "^1.22.0", "tailwindcss": "^3.4.14" + }, + "devDependencies": { + "@types/phoenix": "^1.6.5", + "@types/phoenix_live_view": "^0.18.5" } } diff --git a/config/config.exs b/config/config.exs index 17cccc6..6ddac3e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -53,6 +53,22 @@ config :tailwind, cd: Path.expand("../assets", __DIR__) ] +config :ex_aws, + access_key_id: System.get_env("MINIO_ACCESS_KEY", "PvV9r38q0lA8CmT5bqpU"), + secret_access_key: + System.get_env("MINIO_SECRET_KEY", "wrEhRQ4ughUPw06lKxRlo9Bv4ciBa7i7BDJsRP0o"), + json_codec: Jason + +config :ex_aws, :s3, + scheme: "https://", + host: "s3.zoeys.computer", + port: 443, + region: "us-east-1" + +config :nanoid, + size: 21, + alphabet: "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + # Configures Elixir's Logger config :logger, :console, format: "$time $metadata[$level] $message\n", diff --git a/deps.nix b/deps.nix index 9361993..2490000 100644 --- a/deps.nix +++ b/deps.nix @@ -154,6 +154,21 @@ let }; }; + certifi = + let + version = "2.12.0"; + in + buildRebar3 { + inherit version; + name = "certifi"; + + src = fetchHex { + inherit version; + pkg = "certifi"; + sha256 = "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"; + }; + }; + comeonin = let version = "5.5.0"; @@ -281,7 +296,7 @@ let sha256 = "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"; }; - beamDeps = [ castore ]; + beamDeps = [ castore certifi ]; }; esbuild = @@ -301,6 +316,40 @@ let beamDeps = [ castore jason ]; }; + ex_aws = + let + version = "2.5.6"; + in + buildMix { + inherit version; + name = "ex_aws"; + + src = fetchHex { + inherit version; + pkg = "ex_aws"; + sha256 = "c69eec59e31fdd89d0beeb1d97e16518dd1b23ad95b3d5c9f1dcfec23d97f960"; + }; + + beamDeps = [ hackney jason mime sweet_xml telemetry ]; + }; + + ex_aws_s3 = + let + version = "2.5.4"; + in + buildMix { + inherit version; + name = "ex_aws_s3"; + + src = fetchHex { + inherit version; + pkg = "ex_aws_s3"; + sha256 = "c06e7f68b33f7c0acba1361dbd951c79661a28f85aa2e0582990fccca4425355"; + }; + + beamDeps = [ ex_aws sweet_xml ]; + }; + expo = let version = "1.1.0"; @@ -350,6 +399,23 @@ let beamDeps = [ expo ]; }; + hackney = + let + version = "1.20.1"; + in + buildRebar3 { + inherit version; + name = "hackney"; + + src = fetchHex { + inherit version; + pkg = "hackney"; + sha256 = "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"; + }; + + beamDeps = [ certifi idna metrics mimerl parse_trans ssl_verify_fun unicode_util_compat ]; + }; + hpax = let version = "1.0.0"; @@ -365,6 +431,23 @@ let }; }; + idna = + let + version = "6.1.1"; + in + buildRebar3 { + inherit version; + name = "idna"; + + src = fetchHex { + inherit version; + pkg = "idna"; + sha256 = "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"; + }; + + beamDeps = [ unicode_util_compat ]; + }; + jason = let version = "1.4.4"; @@ -382,6 +465,21 @@ let beamDeps = [ decimal ]; }; + metrics = + let + version = "1.0.1"; + in + buildRebar3 { + inherit version; + name = "metrics"; + + src = fetchHex { + inherit version; + pkg = "metrics"; + sha256 = "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"; + }; + }; + mime = let version = "2.0.6"; @@ -397,6 +495,21 @@ let }; }; + mimerl = + let + version = "1.3.0"; + in + buildRebar3 { + inherit version; + name = "mimerl"; + + src = fetchHex { + inherit version; + pkg = "mimerl"; + sha256 = "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"; + }; + }; + mint = let version = "1.6.2"; @@ -414,6 +527,21 @@ let beamDeps = [ castore hpax ]; }; + nanoid = + let + version = "2.1.0"; + in + buildMix { + inherit version; + name = "nanoid"; + + src = fetchHex { + inherit version; + pkg = "nanoid"; + sha256 = "ebc7a342d02d213534a7f93a091d569b9fea7f26fcd3a638dc655060fc1f76ac"; + }; + }; + nimble_options = let version = "1.1.1"; @@ -444,6 +572,21 @@ let }; }; + parse_trans = + let + version = "3.4.1"; + in + buildRebar3 { + inherit version; + name = "parse_trans"; + + src = fetchHex { + inherit version; + pkg = "parse_trans"; + sha256 = "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"; + }; + }; + phoenix = let version = "1.7.14"; @@ -608,6 +751,36 @@ let beamDeps = [ db_connection decimal jason ]; }; + ssl_verify_fun = + let + version = "1.1.7"; + in + buildMix { + inherit version; + name = "ssl_verify_fun"; + + src = fetchHex { + inherit version; + pkg = "ssl_verify_fun"; + sha256 = "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"; + }; + }; + + sweet_xml = + let + version = "0.7.4"; + in + buildMix { + inherit version; + name = "sweet_xml"; + + src = fetchHex { + inherit version; + pkg = "sweet_xml"; + sha256 = "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"; + }; + }; + swoosh = let version = "1.17.2"; @@ -622,7 +795,7 @@ let sha256 = "de914359f0ddc134dc0d7735e28922d49d0503f31e4bd66b44e26039c2226d39"; }; - beamDeps = [ bandit finch jason mime plug telemetry ]; + beamDeps = [ bandit ex_aws finch hackney jason mime plug telemetry ]; }; table_rex = @@ -723,6 +896,21 @@ let beamDeps = [ telemetry ]; }; + unicode_util_compat = + let + version = "0.7.0"; + in + buildRebar3 { + inherit version; + name = "unicode_util_compat"; + + src = fetchHex { + inherit version; + pkg = "unicode_util_compat"; + sha256 = "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"; + }; + }; + websock = let version = "0.5.3"; diff --git a/img.png b/img.png new file mode 100644 index 0000000..ddaf45e Binary files /dev/null and b/img.png differ diff --git a/lib/zoeyscomputer/api_keys.ex b/lib/zoeyscomputer/api_keys.ex new file mode 100644 index 0000000..abd9e1f --- /dev/null +++ b/lib/zoeyscomputer/api_keys.ex @@ -0,0 +1,132 @@ +defmodule Zoeyscomputer.ApiKeys do + @moduledoc """ + The ApiKeys context. + """ + require Logger + + import Ecto.Query, warn: false + alias Hex.API.User + alias Zoeyscomputer.ApiKeys.ApiKey + alias Zoeyscomputer.Repo + alias Zoeyscomputer.Users.User + + alias Zoeyscomputer.ApiKeys.ApiKey + + @doc """ + Returns the list of api_keys. + + ## Examples + + iex> list_api_keys() + [%ApiKey{}, ...] + + """ + def list_api_keys do + Repo.all(ApiKey) + end + + @doc """ + Gets a single api_key. + + Raises `Ecto.NoResultsError` if the Api key does not exist. + + ## Examples + + iex> get_api_key!(123) + %ApiKey{} + + iex> get_api_key!(456) + ** (Ecto.NoResultsError) + + """ + def get_api_key!(id), do: Repo.get!(ApiKey, id) + + @doc """ + Gets an API key by its token + """ + def get_api_key_by_token(token) do + Repo.get_by(ApiKey, token: token) + end + + @doc """ + Creates an API key for a user. + """ + def create_api_key(user, attrs \\ %{}) do + # Convert attrs to string keys and add user_id with string key + attrs = Map.put(attrs, "user_id", user.id) + + result = + %ApiKey{} + |> ApiKey.changeset(attrs) + |> Repo.insert() + + result + end + + @doc """ + Updates a api_key. + + ## Examples + + iex> update_api_key(api_key, %{field: new_value}) + {:ok, %ApiKey{}} + + iex> update_api_key(api_key, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_api_key(%ApiKey{} = api_key, attrs) do + api_key + |> ApiKey.changeset(attrs) + |> Repo.update() + end + + @doc """ + List all API Keys for a given user + """ + def list_api_keys(%User{} = user) do + ApiKey + |> where([a], a.user_id == ^user.id) + |> Repo.all() + end + + @doc """ + Deletes a api_key. + + ## Examples + + iex> delete_api_key(api_key) + {:ok, %ApiKey{}} + + iex> delete_api_key(api_key) + {:error, %Ecto.Changeset{}} + + """ + def delete_api_key(%ApiKey{} = api_key) do + Repo.delete(api_key) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking api_key changes. + + ## Examples + + iex> change_api_key(api_key) + %Ecto.Changeset{data: %ApiKey{}} + + """ + def change_api_key(%ApiKey{} = api_key, attrs \\ %{}) do + ApiKey.changeset(api_key, attrs) + end + + @doc """ + Validates an API key token and returns associated user. + Returns nil if token is invalid + """ + def authenticate_api_key(token) when is_binary(token) do + case get_api_key_by_token(token) do + %ApiKey{} = key -> Repo.preload(key, :user).user + nil -> nil + end + end +end diff --git a/lib/zoeyscomputer/api_keys/api_key.ex b/lib/zoeyscomputer/api_keys/api_key.ex new file mode 100644 index 0000000..67e7bef --- /dev/null +++ b/lib/zoeyscomputer/api_keys/api_key.ex @@ -0,0 +1,36 @@ +defmodule Zoeyscomputer.ApiKeys.ApiKey do + use Ecto.Schema + import Ecto.Changeset + alias Zoeyscomputer.Users.User + + schema "api_keys" do + field :name, :string + field :token, :string + belongs_to :user, User + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(api_key, attrs) do + api_key + # Make sure both fields are in cast + |> cast(attrs, [:name, :user_id]) + |> validate_required([:name, :user_id]) + # This needs to happen before validation + |> put_token() + # Add token to required fields + |> validate_required([:token]) + end + + defp put_token(changeset) do + case changeset do + %Ecto.Changeset{valid?: true} -> + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + put_change(changeset, :token, token) + + _ -> + changeset + end + end +end diff --git a/lib/zoeyscomputer/images.ex b/lib/zoeyscomputer/images.ex new file mode 100644 index 0000000..d54db74 --- /dev/null +++ b/lib/zoeyscomputer/images.ex @@ -0,0 +1,125 @@ +defmodule Zoeyscomputer.Images do + @moduledoc """ + The Images context. + """ + + import Ecto.Query, warn: false + alias Ecto.Repo + alias Zoeyscomputer.Users.User + alias Zoeyscomputer.Repo + + alias Zoeyscomputer.Images.Image + + @doc """ + Returns the list of images. + + ## Examples + + iex> list_images() + [%Image{}, ...] + + """ + def list_images do + Repo.all(Image) + end + + @doc """ + List Images uploaded by user + """ + + def list_images_by_user(%User{} = user) do + Image + |> where([a], a.user_id == ^user.id) + |> Repo.all() + end + + @doc """ + Gets a single image. + + Raises `Ecto.NoResultsError` if the Image does not exist. + + ## Examples + + iex> get_image!(123) + %Image{} + + iex> get_image!(456) + ** (Ecto.NoResultsError) + + """ + def get_image!(id), do: Repo.get!(Image, id) + + def get_image_by!(file) do + Image + |> Repo.get_by!(file: file) + |> Repo.preload(:user) + end + + @doc """ + Creates a image. + + ## Examples + + iex> create_image(%{field: value}) + {:ok, %Image{}} + + iex> create_image(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_image(attrs \\ %{}) do + # attrs = Map.put(attrs, "user_id", user.id) + # attrs = Map.put(attrs, "s3_key", s3_key) + + %Image{} + |> Image.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a image. + + ## Examples + + iex> update_image(image, %{field: new_value}) + {:ok, %Image{}} + + iex> update_image(image, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_image(%Image{} = image, attrs) do + image + |> Image.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a image. + + ## Examples + + iex> delete_image(image) + {:ok, %Image{}} + + iex> delete_image(image) + {:error, %Ecto.Changeset{}} + + """ + def delete_image(%Image{} = image) do + Repo.delete(image) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking image changes. + + ## Examples + + iex> change_image(image) + %Ecto.Changeset{data: %Image{}} + + """ + def change_image(%Image{} = image, attrs \\ %{}) do + Image.changeset(image, attrs) + end +end diff --git a/lib/zoeyscomputer/images/image.ex b/lib/zoeyscomputer/images/image.ex new file mode 100644 index 0000000..7d63867 --- /dev/null +++ b/lib/zoeyscomputer/images/image.ex @@ -0,0 +1,20 @@ +defmodule Zoeyscomputer.Images.Image do + use Ecto.Schema + import Ecto.Changeset + + @derive {Jason.Encoder, only: [:id, :file, :inserted_at, :updated_at]} + + schema "images" do + field(:file, :string) + belongs_to :user, Zoeyscomputer.Users.User + + timestamps(type: :utc_datetime) + end + + @doc false + def changeset(image, attrs) do + image + |> cast(attrs, [:file, :user_id]) + |> validate_required([:file, :user_id]) + end +end diff --git a/lib/zoeyscomputer_web/components/core_components.ex b/lib/zoeyscomputer_web/components/core_components.ex index 4468051..615bc5d 100644 --- a/lib/zoeyscomputer_web/components/core_components.ex +++ b/lib/zoeyscomputer_web/components/core_components.ex @@ -50,7 +50,11 @@ defmodule ZoeyscomputerWeb.CoreComponents do data-cancel={JS.exec(@on_cancel, "phx-remove")} class="relative z-50 hidden" > -