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:
@@ -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) {
|
||||
|
||||
7
src/lib/components/.directory
Normal file
7
src/lib/components/.directory
Normal file
@@ -0,0 +1,7 @@
|
||||
[Dolphin]
|
||||
Timestamp=2024,12,2,17,34,30.327
|
||||
Version=4
|
||||
ViewMode=1
|
||||
|
||||
[Settings]
|
||||
HiddenFilesShown=true
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ?? '';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
74
src/lib/components/ui/button/button.svelte
Normal file
74
src/lib/components/ui/button/button.svelte
Normal 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}
|
||||
9
src/lib/components/ui/button/index.js
Normal file
9
src/lib/components/ui/button/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import Root, { buttonVariants } from "./button.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
|
||||
//
|
||||
Root as Button,
|
||||
buttonVariants,
|
||||
};
|
||||
17
src/lib/components/ui/button/index.ts
Normal file
17
src/lib/components/ui/button/index.ts
Normal 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,
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
/>
|
||||
@@ -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}
|
||||
/>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
/>
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
/>
|
||||
@@ -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>
|
||||
50
src/lib/components/ui/dropdown-menu/index.js
Normal file
50
src/lib/components/ui/dropdown-menu/index.js
Normal 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,
|
||||
};
|
||||
50
src/lib/components/ui/dropdown-menu/index.ts
Normal file
50
src/lib/components/ui/dropdown-menu/index.ts
Normal 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,
|
||||
};
|
||||
7
src/lib/components/ui/input/index.js
Normal file
7
src/lib/components/ui/input/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./input.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
7
src/lib/components/ui/input/index.ts
Normal file
7
src/lib/components/ui/input/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./input.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
22
src/lib/components/ui/input/input.svelte
Normal file
22
src/lib/components/ui/input/input.svelte
Normal 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}
|
||||
/>
|
||||
17
src/lib/components/ui/popover/index.js
Normal file
17
src/lib/components/ui/popover/index.js
Normal 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,
|
||||
};
|
||||
17
src/lib/components/ui/popover/index.ts
Normal file
17
src/lib/components/ui/popover/index.ts
Normal 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,
|
||||
};
|
||||
28
src/lib/components/ui/popover/popover-content.svelte
Normal file
28
src/lib/components/ui/popover/popover-content.svelte
Normal 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>
|
||||
7
src/lib/components/ui/separator/index.js
Normal file
7
src/lib/components/ui/separator/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
||||
7
src/lib/components/ui/separator/index.ts
Normal file
7
src/lib/components/ui/separator/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Root from "./separator.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Separator,
|
||||
};
|
||||
22
src/lib/components/ui/separator/separator.svelte
Normal file
22
src/lib/components/ui/separator/separator.svelte
Normal 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}
|
||||
/>
|
||||
18
src/lib/components/ui/tooltip/index.js
Normal file
18
src/lib/components/ui/tooltip/index.js
Normal 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,
|
||||
};
|
||||
18
src/lib/components/ui/tooltip/index.ts
Normal file
18
src/lib/components/ui/tooltip/index.ts
Normal 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,
|
||||
};
|
||||
21
src/lib/components/ui/tooltip/tooltip-content.svelte
Normal file
21
src/lib/components/ui/tooltip/tooltip-content.svelte
Normal 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}
|
||||
/>
|
||||
@@ -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
6
src/lib/utils.ts
Normal 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));
|
||||
}
|
||||
Reference in New Issue
Block a user