UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: латинский URL workspace

This commit is contained in:
DCCONSTRUCTIONS 2026-05-12 23:12:07 +03:00
parent 268ab2c9b9
commit a86ed9f5f3
3 changed files with 64 additions and 10 deletions

View File

@ -16,6 +16,7 @@ import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IUser, IWorkspace } from "@plane/types"; import type { IUser, IWorkspace } from "@plane/types";
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
import { cn, validateWorkspaceName, validateSlug } from "@plane/utils"; import { cn, validateWorkspaceName, validateSlug } from "@plane/utils";
import { createWorkspaceSlug } from "@/helpers/workspace-slug";
// hooks // hooks
import { useInstance } from "@/hooks/store/use-instance"; import { useInstance } from "@/hooks/store/use-instance";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
@ -160,7 +161,7 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
onChange={(event) => { onChange={(event) => {
onChange(event.target.value); onChange(event.target.value);
setValue("name", event.target.value); setValue("name", event.target.value);
setValue("slug", event.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"), { setValue("slug", createWorkspaceSlug(event.target.value), {
shouldValidate: true, shouldValidate: true,
}); });
}} }}
@ -215,12 +216,13 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
id="slug" id="slug"
name="slug" name="slug"
type="text" type="text"
value={value.toLocaleLowerCase().trim().replace(/ /g, "-")} value={createWorkspaceSlug(value)}
onChange={(e) => { onChange={(e) => {
const validation = validateSlug(e.target.value); const nextSlug = createWorkspaceSlug(e.target.value);
const validation = validateSlug(nextSlug);
if (validation === true) setInvalidSlug(false); if (validation === true) setInvalidSlug(false);
else setInvalidSlug(true); else setInvalidSlug(true);
onChange(e.target.value.toLowerCase()); onChange(nextSlug);
}} }}
ref={ref} ref={ref}
placeholder={t("workspace_creation.form.url.placeholder")} placeholder={t("workspace_creation.form.url.placeholder")}

View File

@ -17,6 +17,7 @@ import type { IWorkspace } from "@plane/types";
import { Input } from "@plane/ui"; import { Input } from "@plane/ui";
import { validateWorkspaceName, validateSlug } from "@plane/utils"; import { validateWorkspaceName, validateSlug } from "@plane/utils";
import { SelectionDropdown } from "@/components/common/selection-dropdown"; import { SelectionDropdown } from "@/components/common/selection-dropdown";
import { createWorkspaceSlug } from "@/helpers/workspace-slug";
// hooks // hooks
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
@ -61,7 +62,7 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
const inputShellClassName = isNodeDCAuth ? "nodedc-auth-input-shell flex w-full items-center px-4" : "flex flex-col gap-1"; const inputShellClassName = isNodeDCAuth ? "nodedc-auth-input-shell flex w-full items-center px-4" : "flex flex-col gap-1";
const inputClassName = isNodeDCAuth ? "nodedc-auth-input h-12 w-full px-0 py-0 text-14" : "w-full"; const inputClassName = isNodeDCAuth ? "nodedc-auth-input h-12 w-full px-0 py-0 text-14" : "w-full";
const urlShellClassName = isNodeDCAuth const urlShellClassName = isNodeDCAuth
? "nodedc-auth-input-shell flex w-full items-center px-4" ? "nodedc-auth-input-shell flex w-full items-center px-4 text-14"
: "flex w-full items-center rounded-md border border-subtle bg-layer-2 px-3"; : "flex w-full items-center rounded-md border border-subtle bg-layer-2 px-3";
const urlInputClassName = isNodeDCAuth const urlInputClassName = isNodeDCAuth
? "nodedc-auth-input block h-12 w-full border-none bg-transparent !px-0 py-0 text-14" ? "nodedc-auth-input block h-12 w-full border-none bg-transparent !px-0 py-0 text-14"
@ -161,7 +162,7 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
onChange={(e) => { onChange={(e) => {
onChange(e.target.value); onChange(e.target.value);
setValue("name", e.target.value); setValue("name", e.target.value);
setValue("slug", e.target.value.toLocaleLowerCase().trim().replace(/ /g, "-"), { setValue("slug", createWorkspaceSlug(e.target.value), {
shouldValidate: true, shouldValidate: true,
}); });
}} }}
@ -181,7 +182,9 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
{requiredMark} {requiredMark}
</label> </label>
<div className={urlShellClassName} data-error={Boolean(errors.slug) || slugError || invalidSlug}> <div className={urlShellClassName} data-error={Boolean(errors.slug) || slugError || invalidSlug}>
<span className="mr-1 text-12 whitespace-nowrap text-secondary">{window && window.location.host}/</span> <span className={isNodeDCAuth ? "mr-1 whitespace-nowrap text-secondary" : "mr-1 text-12 whitespace-nowrap text-secondary"}>
{window && window.location.host}/
</span>
<Controller <Controller
control={control} control={control}
name="slug" name="slug"
@ -196,12 +199,13 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
<Input <Input
id="workspaceUrl" id="workspaceUrl"
type="text" type="text"
value={value.toLocaleLowerCase().trim().replace(/ /g, "-")} value={createWorkspaceSlug(value)}
onChange={(e) => { onChange={(e) => {
const validation = validateSlug(e.target.value); const nextSlug = createWorkspaceSlug(e.target.value);
const validation = validateSlug(nextSlug);
if (validation === true) setInvalidSlug(false); if (validation === true) setInvalidSlug(false);
else setInvalidSlug(true); else setInvalidSlug(true);
onChange(e.target.value.toLowerCase()); onChange(nextSlug);
}} }}
ref={ref} ref={ref}
hasError={Boolean(errors.slug)} hasError={Boolean(errors.slug)}

View File

@ -0,0 +1,48 @@
const CYRILLIC_TO_LATIN: Record<string, string> = {
а: "a",
б: "b",
в: "v",
г: "g",
д: "d",
е: "e",
ё: "e",
ж: "zh",
з: "z",
и: "i",
й: "y",
к: "k",
л: "l",
м: "m",
н: "n",
о: "o",
п: "p",
р: "r",
с: "s",
т: "t",
у: "u",
ф: "f",
х: "kh",
ц: "ts",
ч: "ch",
ш: "sh",
щ: "shch",
ъ: "",
ы: "y",
ь: "",
э: "e",
ю: "yu",
я: "ya",
};
export const createWorkspaceSlug = (value: string) =>
value
.trim()
.toLocaleLowerCase()
.split("")
.map((character) => CYRILLIC_TO_LATIN[character] ?? character)
.join("")
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.replace(/-{2,}/g, "-");