Getting the new new new rich text editor working. I think it is working... Yay Shad Editor that uses TipTap and Shadcn.

This commit is contained in:
Scott Idem
2024-12-02 18:56:45 -05:00
parent 940a7e9a21
commit 44907bc01f
42 changed files with 2095 additions and 572 deletions

View File

@@ -61,7 +61,9 @@ async function handle_load_ae_obj_id__site_domain(
log_lvl?: number
}
) {
console.log(`*** handle_load_ae_obj_id__site_domain() *** fqdn=${fqdn}`);
if (log_lvl) {
console.log(`*** handle_load_ae_obj_id__site_domain() *** api.base_url=${api_cfg.base_url}, fqdn=${fqdn}, timeout=${timeout}`);
}
let no_account_id = false;
if (!api_cfg.account_id) {

View File

@@ -0,0 +1,7 @@
[Dolphin]
Timestamp=2024,12,2,17,34,30.327
Version=4
ViewMode=1
[Settings]
HiddenFilesShown=true

View File

@@ -11,7 +11,7 @@
import Code from './icons/code.svelte';
import BlockQuote from './icons/block-quote.svelte';
import Subscript from './icons/subscript.svelte';
import ButtleList from './icons/buttle-list.svelte';
import BulletList from './icons/buttle-list.svelte'; // Typo in the icon name
import OrderedList from './icons/ordered-list.svelte';
import TaskList from './icons/task-list.svelte';
import Highlighter from './icons/highlighter.svelte';
@@ -24,35 +24,114 @@
import Text from './icons/text.svelte';
import SearchReplace from './icons/search-replace.svelte';
interface Props {
editor: Editor;
}
interface Props {
editor: Editor;
show_button_kv: any;
}
let { editor }: Props = $props();
let { editor, show_button_kv }: Props = $props();
let show_button_kv_defaults: any = {
undo: true,
redo: false,
text: true,
bold: true,
italic: true,
underline: true,
strikethrough: true,
align: true,
link: true,
code: false,
blockquote: false,
subscript: false,
superscript: false,
bullet_list: true,
ordered_list: true,
task_list: false,
image: false,
table: false,
text_color: true,
highlighter: true,
quick_color: true,
search_replace: true,
};
if (show_button_kv) {
show_button_kv = {...show_button_kv_defaults, ...show_button_kv};
} else {
show_button_kv = show_button_kv_defaults;
}
</script>
<div class="flex w-full items-center overflow-auto border-b p-1 *:mx-1">
<Undo {editor} />
<Redo {editor} />
<!-- <Separator orientation="vertical" class="h-fit" /> -->
<Text {editor} />
<Bold {editor} />
<Italic {editor} />
<Underline {editor} />
<Strikethrough {editor} />
<Align {editor} />
<Link {editor} />
<Code {editor} />
<BlockQuote {editor} />
<Subscript {editor} />
<Superscript {editor} />
<ButtleList {editor} />
<OrderedList {editor} />
<TaskList {editor} />
<Image {editor} />
<Table {editor} />
<Textcolor {editor} />
<Highlighter {editor} />
<Quickcolor {editor} />
<SearchReplace {editor} />
{#if show_button_kv.undo}
<Undo {editor} />
{/if}
{#if show_button_kv.redo}
<Redo {editor} />
{/if}
<!-- <Separator orientation="vertical" class="h-fit" /> -->
{#if show_button_kv.text}
<Text {editor} />
{/if}
{#if show_button_kv.bold}
<Bold {editor} />
{/if}
{#if show_button_kv.italic}
<Italic {editor} />
{/if}
{#if show_button_kv.underline}
<Underline {editor} />
{/if}
{#if show_button_kv.strikethrough}
<Strikethrough {editor} />
{/if}
{#if show_button_kv.align}
<Align {editor} />
{/if}
{#if show_button_kv.link}
<Link {editor} />
{/if}
{#if show_button_kv.code}
<Code {editor} />
{/if}
{#if show_button_kv.blockquote}
<BlockQuote {editor} />
{/if}
{#if show_button_kv.subscript}
<Subscript {editor} />
{/if}
{#if show_button_kv.superscript}
<Superscript {editor} />
{/if}
{#if show_button_kv.bullet_list}
<BulletList {editor} />
{/if}
{#if show_button_kv.ordered_list}
<OrderedList {editor} />
{/if}
{#if show_button_kv.task_list}
<TaskList {editor} />
{/if}
{#if show_button_kv.image}
<Image {editor} />
{/if}
{#if show_button_kv.table}
<Table {editor} />
{/if}
{#if show_button_kv.text_color}
<Textcolor {editor} />
{/if}
{#if show_button_kv.highlighter}
<Highlighter {editor} />
{/if}
{#if show_button_kv.quick_color}
<Quickcolor {editor} />
{/if}
{#if show_button_kv.search_replace}
<SearchReplace {editor} />
{/if}
</div>

View File

@@ -42,9 +42,17 @@
class?: string;
content?: Content;
showToolbar?: boolean;
// html_text?: string;
new_html?: string;
}
let { class: className = '', content = $bindable(''), showToolbar = true }: Props = $props();
let { class:
className = '',
content = $bindable(''),
showToolbar = true,
// html_text = '',
new_html = $bindable(''),
}: Props = $props();
let editor = $state<Editor>();
let element = $state<HTMLElement>();
@@ -133,6 +141,12 @@
editor = transaction.editor;
console.log(editor.isActive('bold'));
content = editor.getHTML();
console.log(content);
if (content == '<p></p>') {
new_html = '';
} else {
new_html = content ?? '';
}
}
});
});

View File

@@ -0,0 +1,74 @@
<script lang="ts" module>
import type { WithElementRef } from "bits-ui";
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
import { type VariantProps, tv } from "tailwind-variants";
export const buttonVariants = tv({
base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border-input bg-background hover:bg-accent hover:text-accent-foreground border",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
variant?: ButtonVariant;
size?: ButtonSize;
};
</script>
<script lang="ts">
import { cn } from "$lib/utils.js";
let {
class: className,
variant = "default",
size = "default",
ref = $bindable(null),
href = undefined,
type = "button",
children,
...restProps
}: ButtonProps = $props();
</script>
{#if href}
<a
bind:this={ref}
class={cn(buttonVariants({ variant, size, className }))}
{href}
{...restProps}
>
{@render children?.()}
</a>
{:else}
<button
bind:this={ref}
class={cn(buttonVariants({ variant, size, className }))}
{type}
{...restProps}
>
{@render children?.()}
</button>
{/if}

View File

@@ -0,0 +1,9 @@
import Root, { buttonVariants } from "./button.svelte";
export {
Root,
//
Root as Button,
buttonVariants,
};

View File

@@ -0,0 +1,17 @@
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants,
} from "./button.svelte";
export {
Root,
type ButtonProps as Props,
//
Root as Button,
buttonVariants,
type ButtonProps,
type ButtonSize,
type ButtonVariant,
};

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild } from "bits-ui";
import Check from "lucide-svelte/icons/check";
import Minus from "lucide-svelte/icons/minus";
import { cn } from "$lib/utils.js";
import type { Snippet } from "svelte";
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
children: childrenProp,
...restProps
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
children?: Snippet;
} = $props();
</script>
<DropdownMenuPrimitive.CheckboxItem
bind:ref
bind:checked
bind:indeterminate
class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if indeterminate}
<Minus class="size-4" />
{:else}
<Check class={cn("size-4", !checked && "text-transparent")} />
{/if}
</span>
{@render childrenProp?.()}
{/snippet}
</DropdownMenuPrimitive.CheckboxItem>

View File

@@ -0,0 +1,26 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let {
ref = $bindable(null),
sideOffset = 4,
portalProps,
class: className,
...restProps
}: DropdownMenuPrimitive.ContentProps & {
portalProps?: DropdownMenuPrimitive.PortalProps;
} = $props();
</script>
<DropdownMenuPrimitive.Portal {...portalProps}>
<DropdownMenuPrimitive.Content
bind:ref
{sideOffset}
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md outline-none",
className
)}
{...restProps}
/>
</DropdownMenuPrimitive.Portal>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: DropdownMenuPrimitive.GroupHeadingProps & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.GroupHeading
bind:ref
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...restProps}
/>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let {
ref = $bindable(null),
class: className,
inset,
...restProps
}: DropdownMenuPrimitive.ItemProps & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.Item
bind:ref
class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...restProps}
/>

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import { type WithElementRef } from "bits-ui";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
inset?: boolean;
} = $props();
</script>
<div
bind:this={ref}
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...restProps}
>
{@render children?.()}
</div>

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from "bits-ui";
import Circle from "lucide-svelte/icons/circle";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children: childrenProp,
...restProps
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
</script>
<DropdownMenuPrimitive.RadioItem
bind:ref
class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...restProps}
>
{#snippet children({ checked })}
<span class="absolute left-2 flex size-3.5 items-center justify-center">
{#if checked}
<Circle class="size-2 fill-current" />
{/if}
</span>
{@render childrenProp?.({ checked })}
{/snippet}
</DropdownMenuPrimitive.RadioItem>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props();
</script>
<DropdownMenuPrimitive.Separator
bind:ref
class={cn("bg-muted -mx-1 my-1 h-px", className)}
{...restProps}
/>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { type WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
class={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...restProps}
>
{@render children?.()}
</span>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SubContentProps = $props();
</script>
<DropdownMenuPrimitive.SubContent
bind:ref
class={cn(
"bg-popover text-popover-foreground z-50 min-w-[8rem] rounded-md border p-1 shadow-lg focus:outline-none",
className
)}
{...restProps}
/>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: DropdownMenuPrimitive.SubTriggerProps & {
inset?: boolean;
} = $props();
</script>
<DropdownMenuPrimitive.SubTrigger
bind:ref
class={cn(
"data-[highlighted]:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...restProps}
>
{@render children?.()}
<ChevronRight class="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>

View File

@@ -0,0 +1,50 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
import Content from "./dropdown-menu-content.svelte";
import GroupHeading from "./dropdown-menu-group-heading.svelte";
import Item from "./dropdown-menu-item.svelte";
import Label from "./dropdown-menu-label.svelte";
import RadioItem from "./dropdown-menu-radio-item.svelte";
import Separator from "./dropdown-menu-separator.svelte";
import Shortcut from "./dropdown-menu-shortcut.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root;
const Trigger = DropdownMenuPrimitive.Trigger;
const Group = DropdownMenuPrimitive.Group;
const RadioGroup = DropdownMenuPrimitive.RadioGroup;
export {
CheckboxItem,
Content,
Root as DropdownMenu,
CheckboxItem as DropdownMenuCheckboxItem,
Content as DropdownMenuContent,
Group as DropdownMenuGroup,
GroupHeading as DropdownMenuGroupHeading,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
RadioGroup as DropdownMenuRadioGroup,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
Shortcut as DropdownMenuShortcut,
Sub as DropdownMenuSub,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
Trigger as DropdownMenuTrigger,
Group,
GroupHeading,
Item,
Label,
RadioGroup,
RadioItem,
Root,
Separator,
Shortcut,
Sub,
SubContent,
SubTrigger,
Trigger,
};

View File

@@ -0,0 +1,50 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
import Content from "./dropdown-menu-content.svelte";
import GroupHeading from "./dropdown-menu-group-heading.svelte";
import Item from "./dropdown-menu-item.svelte";
import Label from "./dropdown-menu-label.svelte";
import RadioItem from "./dropdown-menu-radio-item.svelte";
import Separator from "./dropdown-menu-separator.svelte";
import Shortcut from "./dropdown-menu-shortcut.svelte";
import SubContent from "./dropdown-menu-sub-content.svelte";
import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
const Sub = DropdownMenuPrimitive.Sub;
const Root = DropdownMenuPrimitive.Root;
const Trigger = DropdownMenuPrimitive.Trigger;
const Group = DropdownMenuPrimitive.Group;
const RadioGroup = DropdownMenuPrimitive.RadioGroup;
export {
CheckboxItem,
Content,
Root as DropdownMenu,
CheckboxItem as DropdownMenuCheckboxItem,
Content as DropdownMenuContent,
Group as DropdownMenuGroup,
GroupHeading as DropdownMenuGroupHeading,
Item as DropdownMenuItem,
Label as DropdownMenuLabel,
RadioGroup as DropdownMenuRadioGroup,
RadioItem as DropdownMenuRadioItem,
Separator as DropdownMenuSeparator,
Shortcut as DropdownMenuShortcut,
Sub as DropdownMenuSub,
SubContent as DropdownMenuSubContent,
SubTrigger as DropdownMenuSubTrigger,
Trigger as DropdownMenuTrigger,
Group,
GroupHeading,
Item,
Label,
RadioGroup,
RadioItem,
Root,
Separator,
Shortcut,
Sub,
SubContent,
SubTrigger,
Trigger,
};

View File

@@ -0,0 +1,7 @@
import Root from "./input.svelte";
export {
Root,
//
Root as Input,
};

View File

@@ -0,0 +1,7 @@
import Root from "./input.svelte";
export {
Root,
//
Root as Input,
};

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
value = $bindable(),
class: className,
...restProps
}: WithElementRef<HTMLInputAttributes> = $props();
</script>
<input
bind:this={ref}
class={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
bind:value
{...restProps}
/>

View File

@@ -0,0 +1,17 @@
import { Popover as PopoverPrimitive } from "bits-ui";
import Content from "./popover-content.svelte";
const Root = PopoverPrimitive.Root;
const Trigger = PopoverPrimitive.Trigger;
const Close = PopoverPrimitive.Close;
export {
Root,
Content,
Trigger,
Close,
//
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
Close as PopoverClose,
};

View File

@@ -0,0 +1,17 @@
import { Popover as PopoverPrimitive } from "bits-ui";
import Content from "./popover-content.svelte";
const Root = PopoverPrimitive.Root;
const Trigger = PopoverPrimitive.Trigger;
const Close = PopoverPrimitive.Close;
export {
Root,
Content,
Trigger,
Close,
//
Root as Popover,
Content as PopoverContent,
Trigger as PopoverTrigger,
Close as PopoverClose,
};

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { cn } from "$lib/utils.js";
import { Popover as PopoverPrimitive } from "bits-ui";
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
align = "center",
portalProps,
...restProps
}: PopoverPrimitive.ContentProps & {
portalProps?: PopoverPrimitive.PortalProps;
} = $props();
</script>
<PopoverPrimitive.Portal {...portalProps}>
<PopoverPrimitive.Content
bind:ref
{sideOffset}
{align}
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none",
className
)}
{...restProps}
/>
</PopoverPrimitive.Portal>

View File

@@ -0,0 +1,7 @@
import Root from "./separator.svelte";
export {
Root,
//
Root as Separator,
};

View File

@@ -0,0 +1,7 @@
import Root from "./separator.svelte";
export {
Root,
//
Root as Separator,
};

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { Separator as SeparatorPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
orientation = "horizontal",
...restProps
}: SeparatorPrimitive.RootProps = $props();
</script>
<SeparatorPrimitive.Root
bind:ref
class={cn(
"bg-border shrink-0",
orientation === "horizontal" ? "h-[1px] w-full" : "min-h-full w-[1px]",
className
)}
{orientation}
{...restProps}
/>

View File

@@ -0,0 +1,18 @@
import { Tooltip as TooltipPrimitive } from "bits-ui";
import Content from "./tooltip-content.svelte";
const Root = TooltipPrimitive.Root;
const Trigger = TooltipPrimitive.Trigger;
const Provider = TooltipPrimitive.Provider;
export {
Root,
Trigger,
Content,
Provider,
//
Root as Tooltip,
Content as TooltipContent,
Trigger as TooltipTrigger,
Provider as TooltipProvider,
};

View File

@@ -0,0 +1,18 @@
import { Tooltip as TooltipPrimitive } from "bits-ui";
import Content from "./tooltip-content.svelte";
const Root = TooltipPrimitive.Root;
const Trigger = TooltipPrimitive.Trigger;
const Provider = TooltipPrimitive.Provider;
export {
Root,
Trigger,
Content,
Provider,
//
Root as Tooltip,
Content as TooltipContent,
Trigger as TooltipTrigger,
Provider as TooltipProvider,
};

View File

@@ -0,0 +1,21 @@
<script lang="ts">
import { Tooltip as TooltipPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
sideOffset = 4,
...restProps
}: TooltipPrimitive.ContentProps = $props();
</script>
<TooltipPrimitive.Content
bind:ref
{sideOffset}
class={cn(
"bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md",
className
)}
{...restProps}
/>

View File

@@ -121,112 +121,112 @@ export let new_html: string = '';
onMount(() => {
editor = new Editor({
element: element,
extensions: [
// StarterKit,
Bold, // part of StarterKit
Code, // part of StarterKit
CodeBlock, // part of StarterKit
Italic, // part of StarterKit
Strike, // part of StarterKit
Underline, // part of StarterKit
BulletList, // part of StarterKit
// Color.configure({ types: [TextStyle.name, ListItem.name] }),
// TextStyle.configure({ types: [ListItem.name] }),
// Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
Highlight,
History.configure({
depth: 100,
newGroupDelay: 500
}),
Link.configure({
openOnClick: false,
autolink: true,
defaultProtocol: 'https',
protocols: ['http', 'https'],
isAllowedUri: (url, ctx) => {
try {
// construct URL
const parsedUrl = url.includes(':') ? new URL(url) : new URL(`${ctx.defaultProtocol}://${url}`)
// element: element,
// extensions: [
// // StarterKit,
// Bold, // part of StarterKit
// Code, // part of StarterKit
// CodeBlock, // part of StarterKit
// Italic, // part of StarterKit
// Strike, // part of StarterKit
// Underline, // part of StarterKit
// BulletList, // part of StarterKit
// // Color.configure({ types: [TextStyle.name, ListItem.name] }),
// // TextStyle.configure({ types: [ListItem.name] }),
// // Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
// Highlight,
// History.configure({
// depth: 100,
// newGroupDelay: 500
// }),
// Link.configure({
// openOnClick: false,
// autolink: true,
// defaultProtocol: 'https',
// protocols: ['http', 'https'],
// isAllowedUri: (url, ctx) => {
// try {
// // construct URL
// const parsedUrl = url.includes(':') ? new URL(url) : new URL(`${ctx.defaultProtocol}://${url}`)
// use default validation
if (!ctx.defaultValidate(parsedUrl.href)) {
return false
}
// // use default validation
// if (!ctx.defaultValidate(parsedUrl.href)) {
// return false
// }
// disallowed protocols
const disallowedProtocols = ['ftp', 'file', 'mailto']
const protocol = parsedUrl.protocol.replace(':', '')
// // disallowed protocols
// const disallowedProtocols = ['ftp', 'file', 'mailto']
// const protocol = parsedUrl.protocol.replace(':', '')
if (disallowedProtocols.includes(protocol)) {
return false
}
// if (disallowedProtocols.includes(protocol)) {
// return false
// }
// only allow protocols specified in ctx.protocols
const allowedProtocols = ctx.protocols.map(p => (typeof p === 'string' ? p : p.scheme))
// // only allow protocols specified in ctx.protocols
// const allowedProtocols = ctx.protocols.map(p => (typeof p === 'string' ? p : p.scheme))
if (!allowedProtocols.includes(protocol)) {
return false
}
// if (!allowedProtocols.includes(protocol)) {
// return false
// }
// disallowed domains
const disallowedDomains = ['example-phishing.com', 'malicious-site.net']
const domain = parsedUrl.hostname
// // disallowed domains
// const disallowedDomains = ['example-phishing.com', 'malicious-site.net']
// const domain = parsedUrl.hostname
if (disallowedDomains.includes(domain)) {
return false
}
// if (disallowedDomains.includes(domain)) {
// return false
// }
// all checks have passed
return true
} catch (error) {
return false
}
},
shouldAutoLink: url => {
try {
// construct URL
const parsedUrl = url.includes(':') ? new URL(url) : new URL(`https://${url}`)
// // all checks have passed
// return true
// } catch (error) {
// return false
// }
// },
// shouldAutoLink: url => {
// try {
// // construct URL
// const parsedUrl = url.includes(':') ? new URL(url) : new URL(`https://${url}`)
// only auto-link if the domain is not in the disallowed list
const disallowedDomains = ['example-no-autolink.com', 'another-no-autolink.com']
const domain = parsedUrl.hostname
// // only auto-link if the domain is not in the disallowed list
// const disallowedDomains = ['example-no-autolink.com', 'another-no-autolink.com']
// const domain = parsedUrl.hostname
return !disallowedDomains.includes(domain)
} catch (error) {
return false
}
},
}),
ListItem,
Document,
OrderedList, // part of StarterKit
Paragraph,
Text,
Typography,
],
content: html_text,
onTransaction: ({ editor, transaction }) => {
// console.log('onTransaction');
// force re-render so `editor.isActive` works as expected
// editor = editor;
// return !disallowedDomains.includes(domain)
// } catch (error) {
// return false
// }
// },
// }),
// ListItem,
// Document,
// OrderedList, // part of StarterKit
// Paragraph,
// Text,
// Typography,
// ],
// content: html_text,
// onTransaction: ({ editor, transaction }) => {
// // console.log('onTransaction');
// // force re-render so `editor.isActive` works as expected
// // editor = editor;
// let updated_html = editor.getHTML();
// if (updated_html == '<p></p>') {
// new_html = '';
// } else {
// new_html = updated_html;
// }
},
onUpdate: ({ editor }) => {
console.log('onUpdate', editor.getHTML());
let updated_html = editor.getHTML();
if (updated_html == '<p></p>') {
new_html = '';
} else {
new_html = updated_html;
}
}
// // let updated_html = editor.getHTML();
// // if (updated_html == '<p></p>') {
// // new_html = '';
// // } else {
// // new_html = updated_html;
// // }
// },
// onUpdate: ({ editor }) => {
// // console.log('onUpdate', editor.getHTML());
// // let updated_html = editor.getHTML();
// // if (updated_html == '<p></p>') {
// // new_html = '';
// // } else {
// // new_html = updated_html;
// // }
// }
});
});
@@ -248,9 +248,11 @@ let mouse_enter_wait: number = 500;
let mouse_leave_wait: number = 2000;
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- class:py-1={show_menu} -->
{#if 1==3}
<div
on:click={() => {
if (default_minimal) {
@@ -285,6 +287,7 @@ let mouse_leave_wait: number = 2000;
// }
}}
class="editor textarea p-1 transition-all duration-1000"
class:hidden={true}
>
<!-- && show_menu -->
@@ -619,8 +622,16 @@ let mouse_leave_wait: number = 2000;
>{placeholder}</span>
</div>
</div>
{/if}
<main class="my-10 flex w-full flex-col items-center justify-center">
<ShadEditor
class="editor textarea p-1 transition-all duration-1000"
bind:content={html_text}
bind:new_html={new_html}
/>
</main>
<style lang="scss">
// :global(.ProseMirror) {

6
src/lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}