Merge branch 'dev' into preview
This commit is contained in:
commit
fdf7a01e5f
10 changed files with 43 additions and 9 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
// Enable ESlint flat config support
|
// Enable ESlint flat config support
|
||||||
"eslint.experimental.useFlatConfig": true
|
"eslint.useFlatConfig": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
- **URL Shortening:** Compress your URLs to their minimal length.
|
- **URL Shortening:** Compress your URLs to their minimal length.
|
||||||
- **Analytics:** Monitor link analytics and gather insightful statistics.
|
- **Analytics:** Monitor link analytics and gather insightful statistics.
|
||||||
- **Serverless:** Deploy without the need for traditional servers.
|
- **Serverless:** Deploy without the need for traditional servers.
|
||||||
- **Customizable Slug:** Support for personalized slugs.
|
- **Customizable Slug:** Support for personalized slugs and case sensitivity.
|
||||||
- **🪄 AI Slug:** Leverage AI to generate slugs.
|
- **🪄 AI Slug:** Leverage AI to generate slugs.
|
||||||
- **Link Expiration:** Set expiration dates for your links.
|
- **Link Expiration:** Set expiration dates for your links.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,10 +102,12 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { caseSensitive } = useRuntimeConfig()
|
||||||
|
|
||||||
async function onSubmit(formData) {
|
async function onSubmit(formData) {
|
||||||
const link = {
|
const link = {
|
||||||
url: formData.url,
|
url: formData.url,
|
||||||
slug: formData.slug,
|
slug: caseSensitive ? formData.slug : formData.slug.toLowerCase(),
|
||||||
...(formData.optional || []),
|
...(formData.optional || []),
|
||||||
expiration: formData.optional?.expiration ? date2unix(formData.optional?.expiration, 'end') : undefined,
|
expiration: formData.optional?.expiration ? date2unix(formData.optional?.expiration, 'end') : undefined,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const features = ref([
|
||||||
{
|
{
|
||||||
title: 'Customizable Slug',
|
title: 'Customizable Slug',
|
||||||
description:
|
description:
|
||||||
'Support for personalized slugs.',
|
'Support for personalized slugs and case sensitivity.',
|
||||||
icon: Paintbrush,
|
icon: Paintbrush,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
10
docs/faqs.md
10
docs/faqs.md
|
|
@ -32,3 +32,13 @@ Of course. Please set the environment variable `NUXT_HOME_URL` to your blog or o
|
||||||
## 5. Why can't I see statistics after deploying with NuxtHub?
|
## 5. Why can't I see statistics after deploying with NuxtHub?
|
||||||
|
|
||||||
NuxtHub's ANALYTICS points to its dataset, you need to set the `NUXT_DATASET` environment variable to point to the same dataset.
|
NuxtHub's ANALYTICS points to its dataset, you need to set the `NUXT_DATASET` environment variable to point to the same dataset.
|
||||||
|
|
||||||
|
## 6. Why are links always case-insensitive?
|
||||||
|
|
||||||
|
This is a feature of Sink. By default, we automatically convert all links to lowercase to avoid case-sensitive issues and improve usability. This ensures users don’t encounter errors due to accidental capitalization differences.
|
||||||
|
|
||||||
|
However, you can disable this feature by setting the `NUXT_CASE_SENSITIVE` environment variable to `true`.
|
||||||
|
|
||||||
|
### What happens when `NUXT_CASE_SENSITIVE` is `true`?
|
||||||
|
|
||||||
|
Newly generated links will be case-sensitive, treating `MyLink` and `mylink` as distinct. Randomly generated slugs will include both uppercase and lowercase characters, offering a larger pool of unique combinations (but not user-friendly that why we default to non-case-sensitive).
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ export default defineNuxtConfig({
|
||||||
dataset: 'sink',
|
dataset: 'sink',
|
||||||
aiModel: '@cf/meta/llama-3.1-8b-instruct',
|
aiModel: '@cf/meta/llama-3.1-8b-instruct',
|
||||||
aiPrompt: `You are a URL shortening assistant, please shorten the URL provided by the user into a SLUG. The SLUG information must come from the URL itself, do not make any assumptions. A SLUG is human-readable and should not exceed three words and can be validated using regular expressions {slugRegex} . Only the best one is returned, the format must be JSON reference {"slug": "example-slug"}`,
|
aiPrompt: `You are a URL shortening assistant, please shorten the URL provided by the user into a SLUG. The SLUG information must come from the URL itself, do not make any assumptions. A SLUG is human-readable and should not exceed three words and can be validated using regular expressions {slugRegex} . Only the best one is returned, the format must be JSON reference {"slug": "example-slug"}`,
|
||||||
|
caseSensitive: false,
|
||||||
public: {
|
public: {
|
||||||
previewMode: '',
|
previewMode: '',
|
||||||
slugDefaultLength: '6',
|
slugDefaultLength: '6',
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,15 @@ import { z } from 'zod'
|
||||||
import { customAlphabet } from 'nanoid'
|
import { customAlphabet } from 'nanoid'
|
||||||
|
|
||||||
const { slugRegex } = useAppConfig()
|
const { slugRegex } = useAppConfig()
|
||||||
|
const { caseSensitive } = useRuntimeConfig()
|
||||||
|
|
||||||
const slugDefaultLength = +useRuntimeConfig().public.slugDefaultLength
|
const slugDefaultLength = +useRuntimeConfig().public.slugDefaultLength
|
||||||
|
|
||||||
export const nanoid = (length: number = slugDefaultLength) => customAlphabet('23456789abcdefghjkmnpqrstuvwxyz', length)
|
export function nanoid(length: number = slugDefaultLength) {
|
||||||
|
return caseSensitive
|
||||||
|
? customAlphabet('23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ', length)
|
||||||
|
: customAlphabet('23456789abcdefghjkmnpqrstuvwxyz', length)
|
||||||
|
}
|
||||||
|
|
||||||
export const LinkSchema = z.object({
|
export const LinkSchema = z.object({
|
||||||
id: z.string().trim().max(26).default(nanoid(10)),
|
id: z.string().trim().max(26).default(nanoid(10)),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export default eventHandler(async (event) => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
setResponseStatus(event, 201)
|
setResponseStatus(event, 201)
|
||||||
return { link }
|
const shortLink = `${getRequestProtocol(event)}://${getRequestHost(event)}/${link.slug}`
|
||||||
|
return { link, shortLink }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export default eventHandler(async (event) => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
setResponseStatus(event, 201)
|
setResponseStatus(event, 201)
|
||||||
return { link: newLink }
|
const shortLink = `${getRequestProtocol(event)}://${getRequestHost(event)}/${link.slug}`
|
||||||
|
return { link: newLink, shortLink }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import type { LinkSchema } from '@/schemas/link'
|
||||||
export default eventHandler(async (event) => {
|
export default eventHandler(async (event) => {
|
||||||
const { pathname: slug } = parsePath(event.path.replace(/^\/|\/$/g, '')) // remove leading and trailing slashes
|
const { pathname: slug } = parsePath(event.path.replace(/^\/|\/$/g, '')) // remove leading and trailing slashes
|
||||||
const { slugRegex, reserveSlug } = useAppConfig(event)
|
const { slugRegex, reserveSlug } = useAppConfig(event)
|
||||||
const { homeURL, linkCacheTtl, redirectWithQuery } = useRuntimeConfig(event)
|
const { homeURL, linkCacheTtl, redirectWithQuery, caseSensitive } = useRuntimeConfig(event)
|
||||||
const { cloudflare } = event.context
|
const { cloudflare } = event.context
|
||||||
|
|
||||||
if (event.path === '/' && homeURL)
|
if (event.path === '/' && homeURL)
|
||||||
|
|
@ -13,7 +13,21 @@ export default eventHandler(async (event) => {
|
||||||
|
|
||||||
if (slug && !reserveSlug.includes(slug) && slugRegex.test(slug) && cloudflare) {
|
if (slug && !reserveSlug.includes(slug) && slugRegex.test(slug) && cloudflare) {
|
||||||
const { KV } = cloudflare.env
|
const { KV } = cloudflare.env
|
||||||
const link: z.infer<typeof LinkSchema> | null = await KV.get(`link:${slug}`, { type: 'json', cacheTtl: linkCacheTtl })
|
|
||||||
|
let link: z.infer<typeof LinkSchema> | null = null
|
||||||
|
|
||||||
|
const getLink = async (key: string) =>
|
||||||
|
await KV.get(`link:${key}`, { type: 'json', cacheTtl: linkCacheTtl })
|
||||||
|
|
||||||
|
link = await getLink(slug)
|
||||||
|
|
||||||
|
// fallback to lowercase slug if caseSensitive is false and the slug is not found
|
||||||
|
const lowerCaseSlug = slug.toLowerCase()
|
||||||
|
if (!caseSensitive && !link && lowerCaseSlug !== slug) {
|
||||||
|
console.log('lowerCaseSlug fallback:', `slug:${slug} lowerCaseSlug:${lowerCaseSlug}`)
|
||||||
|
link = await getLink(lowerCaseSlug)
|
||||||
|
}
|
||||||
|
|
||||||
if (link) {
|
if (link) {
|
||||||
event.context.link = link
|
event.context.link = link
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue