diff --git a/components/dashboard/Nav.vue b/components/dashboard/Nav.vue index 2c1fa5d..814eec0 100644 --- a/components/dashboard/Nav.vue +++ b/components/dashboard/Nav.vue @@ -3,7 +3,7 @@ const route = useRoute() - + - + diff --git a/components/dashboard/links/Index.vue b/components/dashboard/links/Index.vue index 35b6611..1ae1edf 100644 --- a/components/dashboard/links/Index.vue +++ b/components/dashboard/links/Index.vue @@ -42,9 +42,12 @@ function updateLinkList(link, type) { - - - + + + + + + +import { useMagicKeys } from '@vueuse/core' +import { useFuse } from '@vueuse/integrations/useFuse' + +const router = useRouter() + +const isOpen = ref(false) +const searchTerm = ref('') +const selectedLink = ref(null) + +const links = await useAPI('/api/link/search') + +const { results: filteredLinks } = useFuse(searchTerm, links, { + fuseOptions: { + keys: ['slug', 'url', 'comment'], + }, + resultLimit: 20, +}) + +const { Meta_K, Ctrl_K } = useMagicKeys({ + passive: false, + onEventFired(e) { + if (e.key === 'k' && (e.metaKey || e.ctrlKey)) + e.preventDefault() + }, +}) + +watch([Meta_K, Ctrl_K], (v) => { + if (v[0] || v[1]) + isOpen.value = true +}) + +function selectLink(link) { + isOpen.value = false + router.push({ + path: '/dashboard/link', + query: { slug: link.slug }, + }) +} + + + + + + Search Links... + Search + + ⌘K + + + + + + + + + No links found. + + + + + + + {{ link.item?.slug }} + + + ({{ link.item?.url }}) + + + + + {{ link.item?.comment }} + + + + + + + + + + + diff --git a/components/ui/badge/Badge.vue b/components/ui/badge/Badge.vue new file mode 100644 index 0000000..cf514e7 --- /dev/null +++ b/components/ui/badge/Badge.vue @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/ui/badge/index.ts b/components/ui/badge/index.ts new file mode 100644 index 0000000..35e7a1a --- /dev/null +++ b/components/ui/badge/index.ts @@ -0,0 +1,25 @@ +import { cva, type VariantProps } from 'class-variance-authority' + +export { default as Badge } from './Badge.vue' + +export const badgeVariants = cva( + 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', + secondary: + 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', + destructive: + 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + outline: 'text-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) + +export type BadgeVariants = VariantProps diff --git a/components/ui/command/Command.vue b/components/ui/command/Command.vue new file mode 100644 index 0000000..38bc390 --- /dev/null +++ b/components/ui/command/Command.vue @@ -0,0 +1,30 @@ + + + + + + + diff --git a/components/ui/command/CommandDialog.vue b/components/ui/command/CommandDialog.vue new file mode 100644 index 0000000..6620ad3 --- /dev/null +++ b/components/ui/command/CommandDialog.vue @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/components/ui/command/CommandEmpty.vue b/components/ui/command/CommandEmpty.vue new file mode 100644 index 0000000..c590564 --- /dev/null +++ b/components/ui/command/CommandEmpty.vue @@ -0,0 +1,20 @@ + + + + + + + diff --git a/components/ui/command/CommandGroup.vue b/components/ui/command/CommandGroup.vue new file mode 100644 index 0000000..e9e9306 --- /dev/null +++ b/components/ui/command/CommandGroup.vue @@ -0,0 +1,29 @@ + + + + + + {{ heading }} + + + + diff --git a/components/ui/command/CommandInput.vue b/components/ui/command/CommandInput.vue new file mode 100644 index 0000000..9354876 --- /dev/null +++ b/components/ui/command/CommandInput.vue @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/components/ui/command/CommandItem.vue b/components/ui/command/CommandItem.vue new file mode 100644 index 0000000..687895d --- /dev/null +++ b/components/ui/command/CommandItem.vue @@ -0,0 +1,26 @@ + + + + + + + diff --git a/components/ui/command/CommandList.vue b/components/ui/command/CommandList.vue new file mode 100644 index 0000000..bd8144a --- /dev/null +++ b/components/ui/command/CommandList.vue @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/components/ui/command/CommandSeparator.vue b/components/ui/command/CommandSeparator.vue new file mode 100644 index 0000000..2276983 --- /dev/null +++ b/components/ui/command/CommandSeparator.vue @@ -0,0 +1,23 @@ + + + + + + + diff --git a/components/ui/command/CommandShortcut.vue b/components/ui/command/CommandShortcut.vue new file mode 100644 index 0000000..ab79309 --- /dev/null +++ b/components/ui/command/CommandShortcut.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/ui/command/index.ts b/components/ui/command/index.ts new file mode 100644 index 0000000..0e35f4b --- /dev/null +++ b/components/ui/command/index.ts @@ -0,0 +1,9 @@ +export { default as Command } from './Command.vue' +export { default as CommandDialog } from './CommandDialog.vue' +export { default as CommandEmpty } from './CommandEmpty.vue' +export { default as CommandGroup } from './CommandGroup.vue' +export { default as CommandInput } from './CommandInput.vue' +export { default as CommandItem } from './CommandItem.vue' +export { default as CommandList } from './CommandList.vue' +export { default as CommandSeparator } from './CommandSeparator.vue' +export { default as CommandShortcut } from './CommandShortcut.vue' diff --git a/package.json b/package.json index 3ec4b63..15a0bde 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@unovis/vue": "^1.4.4", "@vee-validate/zod": "^4.13.2", "@vueuse/core": "^11.0.0", + "fuse.js": "^7.0.0", "intl-parse-accept-language": "^1.0.0", "lucide-vue-next": "^0.428.0", "mysql-bricks": "^1.1.2", @@ -46,6 +47,7 @@ "@nuxthub/core": "^0.7.3", "@nuxtjs/color-mode": "^3.4.4", "@nuxtjs/tailwindcss": "^6.12.1", + "@vueuse/integrations": "^12.2.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "eslint": "^9.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1765f0d..6f5b733 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@vueuse/core': specifier: ^11.0.0 version: 11.0.0(vue@3.4.38(typescript@5.4.5)) + fuse.js: + specifier: ^7.0.0 + version: 7.0.0 intl-parse-accept-language: specifier: ^1.0.0 version: 1.0.0 @@ -62,7 +65,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^2.26.0 - version: 2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5))(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5) + version: 2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5))(@vue/compiler-sfc@3.5.13)(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5) '@nuxt/eslint': specifier: ^0.5.0 version: 0.5.0(eslint@9.9.0(jiti@1.21.6))(magicast@0.3.4)(rollup@4.18.0)(typescript@5.4.5)(vite@5.4.1(@types/node@20.12.11)(terser@5.31.0)) @@ -78,6 +81,9 @@ importers: '@nuxtjs/tailwindcss': specifier: ^6.12.1 version: 6.12.1(magicast@0.3.4)(rollup@4.18.0) + '@vueuse/integrations': + specifier: ^12.2.0 + version: 12.2.0(fuse.js@7.0.0)(typescript@5.4.5) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -268,10 +274,18 @@ packages: resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.7': resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} engines: {node: '>=6.9.0'} @@ -289,6 +303,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-proposal-decorators@7.24.1': resolution: {integrity: sha512-zPEvzFijn+hRvJuX2Vu3KbEBN39LN3f7tW3MQO2LsIs57B26KU+kUc82BdAktS1VCM6libzh45eKGI65lg0cpA==} engines: {node: '>=6.9.0'} @@ -350,6 +369,10 @@ packages: resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + engines: {node: '>=6.9.0'} + '@clack/core@0.3.4': resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==} @@ -1974,15 +1997,27 @@ packages: '@vue/compiler-core@3.4.38': resolution: {integrity: sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==} + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} + '@vue/compiler-dom@3.4.38': resolution: {integrity: sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==} + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} + '@vue/compiler-sfc@3.4.38': resolution: {integrity: sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==} + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + '@vue/compiler-ssr@3.4.38': resolution: {integrity: sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==} + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} @@ -2009,38 +2044,105 @@ packages: '@vue/reactivity@3.4.38': resolution: {integrity: sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==} + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} + '@vue/runtime-core@3.4.38': resolution: {integrity: sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==} + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} + '@vue/runtime-dom@3.4.38': resolution: {integrity: sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==} + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} + '@vue/server-renderer@3.4.38': resolution: {integrity: sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==} peerDependencies: vue: 3.4.38 + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} + peerDependencies: + vue: 3.5.13 + '@vue/shared@3.4.38': resolution: {integrity: sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==} + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} + '@vueuse/core@10.11.1': resolution: {integrity: sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==} '@vueuse/core@11.0.0': resolution: {integrity: sha512-shibzNGjmRjZucEm97B8V0NO5J3vPHMCE/mltxQ3vHezbDoFQBMtK11XsfwfPionxSbo+buqPmsCljtYuXIBpw==} + '@vueuse/core@12.2.0': + resolution: {integrity: sha512-jksyNu+5EGwggNkRWd6xX+8qBkYbmrwdFQMgCABsz+wq8bKF6w3soPFLB8vocFp3wFIzn0OYkSPM9JP+AFKwsg==} + + '@vueuse/integrations@12.2.0': + resolution: {integrity: sha512-Bc0unXiGNZ0w7xqSvzCuP7AflBRKcZX6ib7tGi7vAjOxhLd6GtN3J8qizIbSPnI62XyPy5fauOkq2i2HUPq6MQ==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + '@vueuse/metadata@10.11.1': resolution: {integrity: sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==} '@vueuse/metadata@11.0.0': resolution: {integrity: sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==} + '@vueuse/metadata@12.2.0': + resolution: {integrity: sha512-x6zynZtTh1l52m0y8d/EgzpshnMjg8cNZ2KWoncJ62Z5qPSGoc4FUunmMVrrRM/I/5542rTEY89CGftngZvrkQ==} + '@vueuse/shared@10.11.1': resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} '@vueuse/shared@11.0.0': resolution: {integrity: sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==} + '@vueuse/shared@12.2.0': + resolution: {integrity: sha512-SRr4AZwv/giS+EmyA1ZIzn3/iALjjnWAGaBNmoDTMEob9JwQaevAocuaMDnPAvU7Z35Y5g3CFRusCWgp1gVJ3Q==} + abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -3299,6 +3401,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + fuse.js@7.0.0: + resolution: {integrity: sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==} + engines: {node: '>=10'} + gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} @@ -4352,6 +4458,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -4593,6 +4702,10 @@ packages: resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + potpack@1.0.2: resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} @@ -4927,6 +5040,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -5578,6 +5695,14 @@ packages: typescript: optional: true + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -5726,7 +5851,7 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/eslint-config@2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5))(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5)': + '@antfu/eslint-config@2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5))(@vue/compiler-sfc@3.5.13)(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5)': dependencies: '@antfu/install-pkg': 0.3.5 '@clack/prompts': 0.7.0 @@ -5754,7 +5879,7 @@ snapshots: eslint-plugin-unused-imports: 4.1.3(@typescript-eslint/eslint-plugin@8.1.0(@typescript-eslint/parser@8.1.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5))(eslint@9.9.0(jiti@1.21.6))(typescript@5.4.5))(eslint@9.9.0(jiti@1.21.6)) eslint-plugin-vue: 9.27.0(eslint@9.9.0(jiti@1.21.6)) eslint-plugin-yml: 1.14.0(eslint@9.9.0(jiti@1.21.6)) - eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6)) + eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.5.13)(eslint@9.9.0(jiti@1.21.6)) globals: 15.9.0 jsonc-eslint-parser: 2.4.0 local-pkg: 0.5.0 @@ -5922,8 +6047,12 @@ snapshots: '@babel/helper-string-parser@7.24.7': {} + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-option@7.24.7': {} '@babel/helpers@7.24.7': @@ -5942,6 +6071,10 @@ snapshots: dependencies: '@babel/types': 7.24.7 + '@babel/parser@7.26.3': + dependencies: + '@babel/types': 7.26.3 + '@babel/plugin-proposal-decorators@7.24.1(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -6019,6 +6152,11 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@babel/types@7.26.3': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@clack/core@0.3.4': dependencies: picocolors: 1.0.1 @@ -7780,11 +7918,24 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.0 + '@vue/compiler-core@3.5.13': + dependencies: + '@babel/parser': 7.26.3 + '@vue/shared': 3.5.13 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + '@vue/compiler-dom@3.4.38': dependencies: '@vue/compiler-core': 3.4.38 '@vue/shared': 3.4.38 + '@vue/compiler-dom@3.5.13': + dependencies: + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 + '@vue/compiler-sfc@3.4.38': dependencies: '@babel/parser': 7.24.7 @@ -7797,11 +7948,28 @@ snapshots: postcss: 8.4.41 source-map-js: 1.2.0 + '@vue/compiler-sfc@3.5.13': + dependencies: + '@babel/parser': 7.26.3 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + estree-walker: 2.0.2 + magic-string: 0.30.11 + postcss: 8.4.49 + source-map-js: 1.2.0 + '@vue/compiler-ssr@3.4.38': dependencies: '@vue/compiler-dom': 3.4.38 '@vue/shared': 3.4.38 + '@vue/compiler-ssr@3.5.13': + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 + '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 @@ -7851,11 +8019,20 @@ snapshots: dependencies: '@vue/shared': 3.4.38 + '@vue/reactivity@3.5.13': + dependencies: + '@vue/shared': 3.5.13 + '@vue/runtime-core@3.4.38': dependencies: '@vue/reactivity': 3.4.38 '@vue/shared': 3.4.38 + '@vue/runtime-core@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 + '@vue/runtime-dom@3.4.38': dependencies: '@vue/reactivity': 3.4.38 @@ -7863,14 +8040,29 @@ snapshots: '@vue/shared': 3.4.38 csstype: 3.1.3 + '@vue/runtime-dom@3.5.13': + dependencies: + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 + csstype: 3.1.3 + '@vue/server-renderer@3.4.38(vue@3.4.38(typescript@5.4.5))': dependencies: '@vue/compiler-ssr': 3.4.38 '@vue/shared': 3.4.38 vue: 3.4.38(typescript@5.4.5) + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.4.5))': + dependencies: + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.4.5) + '@vue/shared@3.4.38': {} + '@vue/shared@3.5.13': {} + '@vueuse/core@10.11.1(vue@3.4.38(typescript@5.4.5))': dependencies: '@types/web-bluetooth': 0.0.20 @@ -7891,10 +8083,31 @@ snapshots: - '@vue/composition-api' - vue + '@vueuse/core@12.2.0(typescript@5.4.5)': + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 12.2.0 + '@vueuse/shared': 12.2.0(typescript@5.4.5) + vue: 3.5.13(typescript@5.4.5) + transitivePeerDependencies: + - typescript + + '@vueuse/integrations@12.2.0(fuse.js@7.0.0)(typescript@5.4.5)': + dependencies: + '@vueuse/core': 12.2.0(typescript@5.4.5) + '@vueuse/shared': 12.2.0(typescript@5.4.5) + vue: 3.5.13(typescript@5.4.5) + optionalDependencies: + fuse.js: 7.0.0 + transitivePeerDependencies: + - typescript + '@vueuse/metadata@10.11.1': {} '@vueuse/metadata@11.0.0': {} + '@vueuse/metadata@12.2.0': {} + '@vueuse/shared@10.11.1(vue@3.4.38(typescript@5.4.5))': dependencies: vue-demi: 0.14.10(vue@3.4.38(typescript@5.4.5)) @@ -7909,6 +8122,12 @@ snapshots: - '@vue/composition-api' - vue + '@vueuse/shared@12.2.0(typescript@5.4.5)': + dependencies: + vue: 3.5.13(typescript@5.4.5) + transitivePeerDependencies: + - typescript + abbrev@1.1.1: {} abort-controller@3.0.0: @@ -9078,9 +9297,9 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.38)(eslint@9.9.0(jiti@1.21.6)): + eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.5.13)(eslint@9.9.0(jiti@1.21.6)): dependencies: - '@vue/compiler-sfc': 3.4.38 + '@vue/compiler-sfc': 3.5.13 eslint: 9.9.0(jiti@1.21.6) eslint-scope@7.2.2: @@ -9310,6 +9529,8 @@ snapshots: function-bind@1.1.2: {} + fuse.js@7.0.0: {} + gauge@3.0.2: dependencies: aproba: 2.0.0 @@ -10569,6 +10790,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} picomatch@4.0.2: {} @@ -10788,6 +11011,12 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + postcss@8.4.49: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + potpack@1.0.2: {} prelude-ls@1.2.1: {} @@ -11151,6 +11380,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -11818,6 +12049,16 @@ snapshots: optionalDependencies: typescript: 5.4.5 + vue@3.5.13(typescript@5.4.5): + dependencies: + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.4.5)) + '@vue/shared': 3.5.13 + optionalDependencies: + typescript: 5.4.5 + webidl-conversions@3.0.1: {} webpack-sources@3.2.3: {} diff --git a/server/api/link/create.post.ts b/server/api/link/create.post.ts index 7734e67..7797220 100644 --- a/server/api/link/create.post.ts +++ b/server/api/link/create.post.ts @@ -20,6 +20,8 @@ export default eventHandler(async (event) => { expiration, metadata: { expiration, + url: link.url, + comment: link.comment, }, }) setResponseStatus(event, 201) diff --git a/server/api/link/edit.put.ts b/server/api/link/edit.put.ts index 2b05285..d278fd5 100644 --- a/server/api/link/edit.put.ts +++ b/server/api/link/edit.put.ts @@ -27,10 +27,12 @@ export default eventHandler(async (event) => { expiration, metadata: { expiration, + url: newLink.url, + comment: newLink.comment, }, }) setResponseStatus(event, 201) - const shortLink = `${getRequestProtocol(event)}://${getRequestHost(event)}/${link.slug}` + const shortLink = `${getRequestProtocol(event)}://${getRequestHost(event)}/${newLink.slug}` return { link: newLink, shortLink } } }) diff --git a/server/api/link/search.get.ts b/server/api/link/search.get.ts new file mode 100644 index 0000000..83694ae --- /dev/null +++ b/server/api/link/search.get.ts @@ -0,0 +1,73 @@ +interface Link { + slug: string + url: string + comment?: string +} + +export default eventHandler(async (event) => { + const { cloudflare } = event.context + const { KV } = cloudflare.env + const list: Link[] = [] + let finalCursor: string | undefined + + try { + while (true) { + const { keys, list_complete, cursor } = await KV.list({ + prefix: `link:`, + limit: 1, + cursor: finalCursor, + }) + + finalCursor = cursor + + if (Array.isArray(keys)) { + for (const key of keys) { + try { + if (key.metadata?.url) { + list.push({ + slug: key.name.replace('link:', ''), + url: key.metadata.url, + comment: key.metadata.comment, + }) + } + else { + // Forward compatible with links without metadata + const { metadata, value: link } = await KV.getWithMetadata(key.name, { type: 'json' }) + if (link) { + list.push({ + slug: key.name.replace('link:', ''), + url: link.url, + comment: link.comment, + }) + await KV.put(key.name, JSON.stringify(link), { + expiration: metadata?.expiration, + metadata: { + ...metadata, + url: link.url, + comment: link.comment, + }, + }) + } + } + } + catch (err) { + console.error(`Error processing key ${key.name}:`, err) + continue // Skip this key and continue with the next one + } + } + } + + if (!keys || list_complete) { + break + } + } + return list + } + catch (err) { + console.error('Error fetching link list:', err) + throw createError({ + statusCode: 500, + message: 'Failed to fetch link list', + }) + } +})