fix: add CJK-safe slugify with nanoid fallback
slugify strips all CJK characters, producing empty strings for filenames and folder names that contain only CJK text. Replace all usages with safeSlugify which falls back to a 12-char lowercase alphanumeric nanoid when slugify returns an empty result. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import { useState } from "react";
|
||||
|
||||
import { useTeam } from "@/context/team-context";
|
||||
import { PlanEnum } from "@/ee/stripe/constants";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { toast } from "sonner";
|
||||
import { mutate } from "swr";
|
||||
import { z } from "zod";
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
FolderColorId,
|
||||
FolderIconId,
|
||||
} from "@/lib/constants/folder-constants";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { useAnalytics } from "@/lib/analytics";
|
||||
import { usePlan } from "@/lib/swr/use-billing";
|
||||
|
||||
@@ -65,8 +65,8 @@ export function AddFolderModal({
|
||||
|
||||
const folderPath =
|
||||
isDataroom && dataroomId
|
||||
? `/datarooms/${dataroomId}/documents/${currentFolderPath ? currentFolderPath?.join("/") : ""}/${"/" + slugify(folderName.trim())}`
|
||||
: `/documents/tree/${currentFolderPath ? currentFolderPath?.join("/") : ""}${"/" + slugify(folderName.trim())}`;
|
||||
? `/datarooms/${dataroomId}/documents/${currentFolderPath ? currentFolderPath?.join("/") : ""}/${"/" + safeSlugify(folderName.trim())}`
|
||||
: `/documents/tree/${currentFolderPath ? currentFolderPath?.join("/") : ""}${"/" + safeSlugify(folderName.trim())}`;
|
||||
|
||||
const addFolderSchema = z.object({
|
||||
name: z
|
||||
|
||||
@@ -6,10 +6,10 @@ import {
|
||||
} from "@/ee/features/templates/constants/dataroom-templates";
|
||||
import { applyTemplateSchema } from "@/ee/features/templates/schemas/dataroom-templates";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import prisma from "@/lib/prisma";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
|
||||
export default async function handle(
|
||||
@@ -81,7 +81,7 @@ export default async function handle(
|
||||
parentId: string | null = null,
|
||||
): Promise<void> => {
|
||||
for (const folder of folders) {
|
||||
const folderPath = parentPath + "/" + slugify(folder.name);
|
||||
const folderPath = parentPath + "/" + safeSlugify(folder.name);
|
||||
|
||||
// Create the folder
|
||||
const createdFolder = await tx.dataroomFolder.create({
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
import { generateDataroomSchema } from "@/ee/features/templates/schemas/dataroom-templates";
|
||||
import { getLimits } from "@/ee/limits/server";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import { newId } from "@/lib/id-helper";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
|
||||
@@ -127,7 +127,7 @@ export default async function handle(
|
||||
parentId: string | null = null,
|
||||
): Promise<void> => {
|
||||
for (const folder of folders) {
|
||||
const folderPath = parentPath + "/" + slugify(folder.name);
|
||||
const folderPath = parentPath + "/" + safeSlugify(folder.name);
|
||||
|
||||
// Create the folder
|
||||
const createdFolder = await tx.dataroomFolder.create({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
|
||||
export interface FolderInput {
|
||||
id: string;
|
||||
@@ -28,7 +28,7 @@ export function buildFolderPathsFromHierarchy(
|
||||
// Prevent infinite loops from circular parentId references
|
||||
if (visited.has(folderId)) {
|
||||
const folder = folderById.get(folderId);
|
||||
const fallbackPath = `/${slugify(folder?.name ?? folderId)}`;
|
||||
const fallbackPath = `/${safeSlugify(folder?.name ?? folderId)}`;
|
||||
pathCache.set(folderId, fallbackPath);
|
||||
return fallbackPath;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export function buildFolderPathsFromHierarchy(
|
||||
parentPath = computePath(folder.parentId, visited);
|
||||
}
|
||||
|
||||
const path = `${parentPath}/${slugify(folder.name)}`;
|
||||
const path = `${parentPath}/${safeSlugify(folder.name)}`;
|
||||
pathCache.set(folderId, path);
|
||||
return path;
|
||||
}
|
||||
|
||||
+15
-1
@@ -351,6 +351,20 @@ export const nanoid = customAlphabet(
|
||||
7,
|
||||
); // 7-character random string
|
||||
|
||||
const nanoidSlug = customAlphabet(
|
||||
"0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
12,
|
||||
);
|
||||
|
||||
/**
|
||||
* CJK-safe slugify: falls back to a nanoid when @sindresorhus/slugify
|
||||
* strips all characters (e.g. for purely CJK filenames / folder names).
|
||||
*/
|
||||
export function safeSlugify(input: string): string {
|
||||
const slug = slugify(input);
|
||||
return slug.length > 0 ? slug : nanoidSlug();
|
||||
}
|
||||
|
||||
export const daysLeft = (
|
||||
accountCreationDate: Date,
|
||||
maxDays: number,
|
||||
@@ -635,7 +649,7 @@ export const getBreadcrumbPath = (path: string[]) => {
|
||||
return [
|
||||
{ name: "Home", pathLink: "/documents" },
|
||||
...segments.map((segment, index) => {
|
||||
currentPath += `/${slugify(segment)}`;
|
||||
currentPath += `/${safeSlugify(segment)}`;
|
||||
return {
|
||||
name: segment,
|
||||
pathLink: currentPath,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { getTeamStorageConfigById } from "@/ee/features/storage/config";
|
||||
import { ItemType, ViewType } from "@prisma/client";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
import { verifyDataroomSessionInPagesRouter } from "@/lib/auth/dataroom-auth";
|
||||
import {
|
||||
@@ -13,6 +12,7 @@ import { notifyDocumentDownload } from "@/lib/integrations/slack/events";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { downloadJobStore } from "@/lib/redis-download-job-store";
|
||||
import { bulkDownloadTask } from "@/lib/trigger/bulk-download";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { getIpAddress } from "@/lib/utils/ip";
|
||||
|
||||
export const config = {
|
||||
@@ -272,7 +272,7 @@ export default async function handler(
|
||||
relativePath = match ? match[1] : "";
|
||||
}
|
||||
|
||||
const pathParts = [slugify(rootFolderInfo.name)];
|
||||
const pathParts = [safeSlugify(rootFolderInfo.name)];
|
||||
if (relativePath) {
|
||||
pathParts.push(...relativePath.split("/").filter(Boolean));
|
||||
}
|
||||
@@ -357,10 +357,10 @@ export default async function handler(
|
||||
}
|
||||
}
|
||||
|
||||
const rootPath = "/" + slugify(rootFolder.name);
|
||||
const rootPath = "/" + safeSlugify(rootFolder.name);
|
||||
if (!folderStructure[rootPath]) {
|
||||
folderStructure[rootPath] = {
|
||||
name: slugify(rootFolder.name),
|
||||
name: safeSlugify(rootFolder.name),
|
||||
path: rootPath,
|
||||
files: [],
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { DefaultPermissionStrategy, ItemType } from "@prisma/client";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import prisma from "@/lib/prisma";
|
||||
@@ -491,7 +491,7 @@ export default async function handle(
|
||||
const basePath =
|
||||
pathSegments.length > 0 ? "/" + pathSegments.join("/") + "/" : "/";
|
||||
|
||||
let childFolderPath = basePath + slugify(folderName);
|
||||
let childFolderPath = basePath + safeSlugify(folderName);
|
||||
|
||||
while (counter <= MAX_RETRIES) {
|
||||
const existingFolder = await prisma.dataroomFolder.findUnique({
|
||||
@@ -504,7 +504,7 @@ export default async function handle(
|
||||
});
|
||||
if (!existingFolder) break;
|
||||
folderName = `${name} (${counter})`;
|
||||
childFolderPath = basePath + slugify(folderName);
|
||||
childFolderPath = basePath + safeSlugify(folderName);
|
||||
counter++;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import { errorhandler } from "@/lib/errorHandler";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
|
||||
@@ -50,7 +50,7 @@ async function createDataroomStructure(
|
||||
parentPath: string = "",
|
||||
parentFolderId?: string,
|
||||
): Promise<void> {
|
||||
const currentPath = `${parentPath}/${slugify(folder.name)}`;
|
||||
const currentPath = `${parentPath}/${safeSlugify(folder.name)}`;
|
||||
|
||||
const dataroomFolder = await prisma.dataroomFolder.create({
|
||||
data: {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import { errorhandler } from "@/lib/errorHandler";
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import {
|
||||
ALLOWED_FOLDER_COLORS,
|
||||
ALLOWED_FOLDER_ICONS,
|
||||
} from "@/lib/constants/folder-constants";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { errorhandler } from "@/lib/errorHandler";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
@@ -91,7 +91,7 @@ export default async function handle(
|
||||
const oldPath = folder.path;
|
||||
const newPathParts = folder.path.split("/");
|
||||
newPathParts.pop();
|
||||
newPathParts.push(slugify(name.trim()));
|
||||
newPathParts.push(safeSlugify(name.trim()));
|
||||
const newPath = newPathParts.join("/");
|
||||
|
||||
// Build update data object with only changed fields
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import prisma from "@/lib/prisma";
|
||||
@@ -97,8 +97,8 @@ export default async function handle(
|
||||
foldersToMove.forEach((folder) => {
|
||||
const newPath =
|
||||
selectedFolderPath !== "/" && selectedFolderPath !== undefined
|
||||
? `${selectedFolderPath}/${slugify(folder.name)}`
|
||||
: `/${slugify(folder.name)}`;
|
||||
? `${selectedFolderPath}/${safeSlugify(folder.name)}`
|
||||
: `/${safeSlugify(folder.name)}`;
|
||||
|
||||
folderPathUpdates.set(folder.id, newPath);
|
||||
});
|
||||
|
||||
@@ -4,10 +4,10 @@ import { isTeamPausedById } from "@/ee/features/billing/cancellation/lib/is-team
|
||||
import { FolderTemplate } from "@/ee/features/templates/constants/dataroom-templates";
|
||||
import { getLimits } from "@/ee/limits/server";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import { newId } from "@/lib/id-helper";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
|
||||
@@ -182,7 +182,7 @@ export default async function handle(
|
||||
parentId: string | null = null,
|
||||
): Promise<void> => {
|
||||
for (const folder of folders) {
|
||||
const folderPath = parentPath + "/" + slugify(folder.name);
|
||||
const folderPath = parentPath + "/" + safeSlugify(folder.name);
|
||||
|
||||
// Create the folder
|
||||
const createdFolder = await tx.dataroomFolder.create({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import prisma from "@/lib/prisma";
|
||||
@@ -152,8 +152,8 @@ export default async function handle(
|
||||
const MAX_RETRIES = 50;
|
||||
|
||||
let childFolderPath = path
|
||||
? "/" + path + "/" + slugify(folderName)
|
||||
: "/" + slugify(folderName);
|
||||
? "/" + path + "/" + safeSlugify(folderName)
|
||||
: "/" + safeSlugify(folderName);
|
||||
|
||||
while (counter <= MAX_RETRIES) {
|
||||
const existingFolder = await prisma.folder.findUnique({
|
||||
@@ -169,8 +169,8 @@ export default async function handle(
|
||||
|
||||
folderName = `${name} (${counter})`;
|
||||
childFolderPath = path
|
||||
? "/" + path + "/" + slugify(folderName)
|
||||
: "/" + slugify(folderName);
|
||||
? "/" + path + "/" + safeSlugify(folderName)
|
||||
: "/" + safeSlugify(folderName);
|
||||
counter++;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import { errorhandler } from "@/lib/errorHandler";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
|
||||
@@ -50,7 +50,7 @@ async function createDataroomStructure(
|
||||
parentPath: string = "",
|
||||
parentFolderId?: string,
|
||||
): Promise<void> {
|
||||
const currentPath = `${parentPath}/${slugify(folder.name)}`;
|
||||
const currentPath = `${parentPath}/${safeSlugify(folder.name)}`;
|
||||
|
||||
const dataroomFolder = await prisma.dataroomFolder.create({
|
||||
data: {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import {
|
||||
ALLOWED_FOLDER_COLORS,
|
||||
ALLOWED_FOLDER_ICONS,
|
||||
} from "@/lib/constants/folder-constants";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { errorhandler } from "@/lib/errorHandler";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { CustomUser } from "@/lib/types";
|
||||
@@ -87,7 +87,7 @@ export default async function handle(
|
||||
const oldPath = folder.path;
|
||||
const newPathParts = folder.path.split("/");
|
||||
newPathParts.pop();
|
||||
newPathParts.push(slugify(name.trim()));
|
||||
newPathParts.push(safeSlugify(name.trim()));
|
||||
const newPath = newPathParts.join("/");
|
||||
|
||||
// Build update data object with only changed fields
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { safeSlugify } from "@/lib/utils";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
|
||||
import prisma from "@/lib/prisma";
|
||||
@@ -85,8 +85,8 @@ export default async function handle(
|
||||
foldersToMove.forEach((folder) => {
|
||||
const newPath =
|
||||
selectedFolderPath !== "/"
|
||||
? `${selectedFolderPath}/${slugify(folder.name)}`
|
||||
: `/${slugify(folder.name)}`;
|
||||
? `${selectedFolderPath}/${safeSlugify(folder.name)}`
|
||||
: `/${safeSlugify(folder.name)}`;
|
||||
|
||||
folderPathUpdates.set(folder.id, newPath);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { isTeamPausedById } from "@/ee/features/billing/cancellation/lib/is-team-paused";
|
||||
import { LinkPreset } from "@prisma/client";
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { put } from "@vercel/blob";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { z } from "zod";
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
convertDataUrlToBuffer,
|
||||
generateEncrpytedPassword,
|
||||
isDataUrl,
|
||||
safeSlugify,
|
||||
uploadImage,
|
||||
} from "@/lib/utils";
|
||||
import {
|
||||
@@ -1319,7 +1319,7 @@ async function createDataroomFoldersRecursive(
|
||||
parentId: string | null = null,
|
||||
): Promise<void> {
|
||||
for (const folder of folders) {
|
||||
const folderPath = parentPath + "/" + slugify(folder.name);
|
||||
const folderPath = parentPath + "/" + safeSlugify(folder.name);
|
||||
|
||||
// Create the folder
|
||||
const createdFolder = await prisma.dataroomFolder.create({
|
||||
|
||||
Reference in New Issue
Block a user