Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
052f39b
feat(examples): shadcn and headless basic examples
SoRobby Mar 22, 2025
49f8dc4
feat(editor): add allowedCommands for EdraToolbar and typo fix
SoRobby Mar 22, 2025
1081e6f
feat(editor): add showSlashCommands property for Edra component
SoRobby Mar 22, 2025
5d3a297
refactor(editor): improved parameter naming convention to match tiptap
SoRobby Mar 22, 2025
70836a7
Merge branch 'Tsuzat:main' into main
SoRobby Mar 22, 2025
004c471
feat(bubble-menu): add allowedBubbleMenuCommands parameter to be pass…
SoRobby Mar 22, 2025
cdaf19a
feat(toolbar): allow for ordered commands based
SoRobby Mar 22, 2025
c625a4d
fix(QuickColor): shadcn QuickColor now closes upon color selection
SoRobby Mar 22, 2025
2cb79c7
docs: new editor features documented
SoRobby Mar 22, 2025
dda430b
refactor(toolbar): simplified logic
SoRobby Mar 22, 2025
4dfdd0f
feat(toolbar): add ability to pass children into toolbar
SoRobby Mar 22, 2025
cd9e6bf
fix(toolbar): let children be optional
SoRobby Mar 22, 2025
6e08dee
feat(editor): focus editor and set cursor to click position, defaulti…
SoRobby Mar 22, 2025
86aaf17
docs: updated docs to reflect recent changes
SoRobby Mar 22, 2025
00d86ca
fix(editor-focus): fix editor focus and bubble menu not showing
SoRobby Mar 22, 2025
32f238f
refactor(focusEditor): moved to utils.ts and removed repetitive code
SoRobby Mar 23, 2025
dee5015
refactor(getOrderedToolbarItems): moved to utils file
SoRobby Mar 23, 2025
001d1cb
Merge remote-tracking branch 'upstream/main'
SoRobby Mar 24, 2025
be99b3a
refactor: clean up names, remove unused util, align with tiptap conve…
SoRobby Mar 24, 2025
5d48fd2
feat(editor): update docs and add bubble menu toggles
SoRobby Mar 25, 2025
d98b6bd
fix: corrected naming, change PlaceHolder to Placeholder
SoRobby Mar 25, 2025
ff17643
fix: correct spelling of HighLighter to Highlighter
SoRobby Mar 25, 2025
315aa23
style: updated style of example pages
SoRobby Mar 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Edra | Best rich text editor for Svelte Developers</title>
<meta name="robots" content="index,follow">
<meta name="description" content="Edra is a best rich text editor made for Svelte developers" />
<meta name="author" content="Alok 'Tsuzat' Singh" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
49 changes: 30 additions & 19 deletions src/lib/components/custom/demo-editor-settings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,55 @@

interface Props {
showToolBar: boolean;
showBubbleMenus: boolean;
showBubbleMenu: boolean;
editable: boolean;
}

let {
showToolBar = $bindable(true),
showBubbleMenus = $bindable(true),
showBubbleMenu = $bindable(true),
editable = $bindable(true)
}: Props = $props();
</script>

<Dialog.Root>
<Dialog.Trigger class={buttonVariants({ variant: 'outline' })}>
<Settings />
<span>Demo Settings</span>
<span>Editor settings</span>
</Dialog.Trigger>
<Dialog.Content class="h-fit max-h-[95vh] w-80 max-w-[95vw] p-4">
<Dialog.Header class="mb-4">
<Dialog.Title>Demo Edra Settings</Dialog.Title>
<Dialog.Title>Edra demo settings</Dialog.Title>
</Dialog.Header>
<div class="flex flex-col items-start gap-6">
<div class="flex w-full items-center gap-2">
<Checkbox id="toolbar" bind:checked={showToolBar} />
<Label for="toolbar" class="w-full cursor-pointer text-sm font-medium leading-none"
>Show Editor Toolbar</Label
>
<div class="space-y-1">
<div class="group flex w-full items-center gap-2">
<Checkbox id="toolbar" bind:checked={showToolBar} />
<Label for="toolbar" class="w-full cursor-pointer text-sm font-medium leading-none"
>Show editor toolbar</Label
>
</div>
<p class="text-xs text-muted-foreground">Toggles the main toolbar above the editor with formatting options like headings, bold, italic, etc.</p>
</div>
<div class="flex w-full items-center gap-2">
<Checkbox id="menus" bind:checked={showBubbleMenus} />
<Label for="menus" class="w-full cursor-pointer text-sm font-medium leading-none"
>Show Editor Menus</Label
>

<div class="space-y-1">
<div class="group flex w-full items-center gap-2">
<Checkbox id="menus" bind:checked={showBubbleMenu} />
<Label for="menus" class="w-full cursor-pointer text-sm font-medium leading-none"
>Show editor bubble menu</Label
>
</div>
<p class="text-xs text-muted-foreground">Displays a floating formatting menu when text is selected in the editor.</p>
</div>
<div class="flex w-full items-center gap-2">
<Checkbox id="editable" bind:checked={editable} />
<Label for="editable" class="w-full cursor-pointer text-sm font-medium leading-none"
>Editable</Label
>

<div class="space-y-1">
<div class="group flex w-full items-center gap-2">
<Checkbox id="editable" bind:checked={editable} />
<Label for="editable" class="w-full cursor-pointer text-sm font-medium leading-none"
>Editable</Label
>
</div>
<p class="text-xs text-muted-foreground">Allows users to directly edit the content. When unchecked, the editor is in read-only mode.</p>
</div>
</div>
</Dialog.Content>
Expand Down
4 changes: 3 additions & 1 deletion src/lib/components/custom/editor-output.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

<Dialog.Root>
<Dialog.Trigger class={buttonVariants({ variant: 'outline' })}>
<FileJson /> Show Output
<FileJson /> Show output
</Dialog.Trigger>
<Dialog.Content
class={cn(
Expand All @@ -47,7 +47,9 @@
<Dialog.Title>JSON Output</Dialog.Title>
<Dialog.Description>Observe the JSON output of the editor content</Dialog.Description>
</Dialog.Header>

<ShikiCode class="size-full overflow-auto" {code} lang="json" />

<Button
variant="outline"
class="ml-auto w-fit"
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/custom/navbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class="sticky top-0 z-50 inline-flex w-full items-center justify-between gap-4 border-b border-border/75 bg-background/75 px-4 py-2 backdrop-blur"
>
<a class="inline-flex items-center gap-2" href="/">
<img src="/favicon.png" alt="logo" class="size-8" />
<img src="/favicon.png" alt="Edra logo" class="size-8" />
<span class="text-xl font-bold">Edra</span>
</a>
<div>
Expand Down
2 changes: 1 addition & 1 deletion src/lib/edra/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import StarterKit from '@tiptap/starter-kit';
import Highlight from '@tiptap/extension-highlight';
import Text from '@tiptap/extension-text';
import { SmilieReplacer } from './extensions/SmilieReplacer.js';
import { ColorHighlighter } from './extensions/ColorHighLighter.js';
import { ColorHighlighter } from './extensions/ColorHighlighter.js';
import AutoJoiner from 'tiptap-extension-auto-joiner';
import { MathExtension } from '@aarkue/tiptap-math-extension';
import { Table, TableCell, TableHeader, TableRow } from './extensions/table/index.js';
Expand Down
2 changes: 1 addition & 1 deletion src/lib/edra/extensions/audio/AudioPlaceholder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ declare module '@tiptap/core' {
}
}

export const AudioPlaceHolder = (
export const AudioPlaceholder = (
component: Component<NodeViewProps>
): Node<AudioPlaceholderOptions> =>
Node.create<AudioPlaceholderOptions>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ declare module '@tiptap/core' {
}
}

export const IFramePlaceHolder = (content: Component<NodeViewProps>) =>
export const IFramePlaceholder = (content: Component<NodeViewProps>) =>
Node.create<IFramePlaceholderOptions>({
name: 'iframe-placeholder',
addOptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ declare module '@tiptap/core' {
}
}

export const ImagePlaceHolder = (
export const ImagePlaceholder = (
component: Component<NodeViewProps>
): Node<ImagePlaceholderOptions> =>
Node.create<ImagePlaceholderOptions>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ declare module '@tiptap/core' {
}
}

export const VideoPlaceHolder = (content: Component<NodeViewProps>) =>
export const VideoPlaceholder = (content: Component<NodeViewProps>) =>
Node.create<VideoPlaceholderOptions>({
name: 'video-placeholder',
addOptions() {
Expand Down
64 changes: 43 additions & 21 deletions src/lib/edra/headless/editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
import '../onedark.css';
import { SvelteNodeViewRenderer } from 'svelte-tiptap';
import CodeExtended from './components/CodeExtended.svelte';
import { AudioPlaceHolder } from '../extensions/audio/AudioPlaceholder.js';
import AudioPlaceHolderComponent from './components/AudioPlaceHolder.svelte';
import { AudioPlaceholder } from '../extensions/audio/AudioPlaceholder.js';
import AudioPlaceholderComponent from './components/AudioPlaceholder.svelte';
import AudioExtendedComponent from './components/AudioExtended.svelte';
import { ImagePlaceHolder } from '../extensions/image/ImagePlaceHolder.js';
import ImagePlaceHolderComponent from './components/ImagePlaceHolder.svelte';
import { VideoPlaceHolder } from '../extensions/video/VideoPlaceHolder.js';
import VideoPlaceHolderComponent from './components/VideoPlaceHolder.svelte';
import { ImagePlaceholder } from '../extensions/image/ImagePlaceholder.js';
import ImagePlaceholderComponent from './components/ImagePlaceholder.svelte';
import { VideoPlaceholder } from '../extensions/video/VideoPlaceholder.js';
import VideoPlaceholderComponent from './components/VideoPlaceholder.svelte';
import { ImageExtended } from '../extensions/image/ImageExtended.js';
import ImageExtendedComponent from './components/ImageExtended.svelte';
import VideoExtendedComponent from './components/VideoExtended.svelte';
Expand All @@ -32,10 +32,10 @@
import SlashCommandList from './components/SlashCommandList.svelte';
import BubbleMenu from './menus/bubble-menu.svelte';
import LoaderCircle from 'lucide-svelte/icons/loader-circle';
import type { EdraProps } from '../utils.js';
import { focusEditor, type EdraProps } from '../utils.js';
import DragHandle from '../drag-handle.svelte';
import IFramePlaceholderComponent from './components/IFramePlaceholder.svelte';
import { IFramePlaceHolder } from '../extensions/iframe/IFramePlaceHolder.js';
import { IFramePlaceholder } from '../extensions/iframe/IFramePlaceholder.js';
import { IFrameExtended } from '../extensions/iframe/IFrameExtended.js';
import IFrameExtendedComponent from './components/IFrameExtended.svelte';

Expand All @@ -45,10 +45,11 @@
class: className = '',
content = undefined,
editable = true,
showBubbleMenus = true,
limit = undefined,
editor = $bindable<Editor | undefined>(),
showSlashCommand = true,
showSlashCommands = true,
showLinkBubbleMenu = true,
showTableBubbleMenu = true,
onUpdate,
children
}: EdraProps = $props();
Expand All @@ -68,15 +69,15 @@
return SvelteNodeViewRenderer(CodeExtended);
}
}),
AudioPlaceHolder(AudioPlaceHolderComponent),
ImagePlaceHolder(ImagePlaceHolderComponent),
IFramePlaceHolder(IFramePlaceholderComponent),
AudioPlaceholder(AudioPlaceholderComponent),
ImagePlaceholder(ImagePlaceholderComponent),
IFramePlaceholder(IFramePlaceholderComponent),
IFrameExtended(IFrameExtendedComponent),
VideoPlaceHolder(VideoPlaceHolderComponent),
VideoPlaceholder(VideoPlaceholderComponent),
AudioExtended(AudioExtendedComponent),
ImageExtended(ImageExtendedComponent),
VideoExtended(VideoExtendedComponent),
...(showSlashCommand ? [slashcommand(SlashCommandList)] : [])
...(showSlashCommands ? [slashcommand(SlashCommandList)] : [])
],
{
editable,
Expand All @@ -97,22 +98,43 @@

<div class={`edra ${className}`}>
{@render children?.()}
{#if editor && showBubbleMenus}
<LinkMenu {editor} />
<TableRowMenu {editor} />
<TableColMenu {editor} />
{#if editor}
{#if showLinkBubbleMenu}
<LinkMenu {editor} />
{/if}
{#if showTableBubbleMenu}
<TableRowMenu {editor} />
<TableColMenu {editor} />
{/if}
{/if}
{#if !editor}
<div class="edra-loading">
<LoaderCircle class="animate-spin" /> Loading...
</div>
{/if}
<div bind:this={element} class="edra-editor"></div>
<div
bind:this={element}
role="button"
tabindex="0"
onclick={(event) => focusEditor(editor, event)}
onkeydown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
focusEditor(editor, event);
}
}}
class="edra-editor"
></div>
</div>

<style>
:global(.ProseMirror) {
all: unset;
min-height: 100%;
position: relative;
word-wrap: break-word;
white-space: pre-wrap;
cursor: auto;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
&:focus {
outline: none;
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/edra/headless/menus/bubble-menu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
...commands.lists.commands
];

const excludeCommands = ['undo-redo', 'media', 'table', 'colors', 'fonts', 'lists'];

const colorCommands = commands.colors.commands;
const fontCommands = commands.fonts.commands;

Expand Down
1 change: 0 additions & 1 deletion src/lib/edra/headless/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
flex-direction: row;
align-items: center;
gap: var(--edra-gap);
border-bottom: 1px solid var(--edra-border-color);
padding: var(--edra-padding);
width: fit-content;
overflow: auto;
Expand Down
7 changes: 4 additions & 3 deletions src/lib/edra/headless/toolbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Editor } from '@tiptap/core';
import { commands } from '../commands/commands.js';
import EdraToolBarIcon from './components/EdraToolBarIcon.svelte';
import SearcnAndReplace from './components/SearcnAndReplace.svelte';
import SearchAndReplace from './components/SearchAndReplace.svelte';
import type { Snippet } from 'svelte';

interface Props {
Expand All @@ -13,8 +13,9 @@

const { class: className = '', editor, children }: Props = $props();

// Special components that are handled separately
const specialComponents = ['fontSize', 'quickColor', 'searchAndReplace'];
let showSearchAndReplace = $state(false);

const colorCommands = commands.colors.commands;
const fontCommands = commands.fonts.commands;
const excludedCommands = ['colors', 'fonts'];
Expand Down Expand Up @@ -73,6 +74,6 @@
}}
/>
{/if}
<SearcnAndReplace {editor} bind:show={showSearchAndReplace} />
<SearchAndReplace {editor} bind:show={showSearchAndReplace} />
{/if}
</div>
Loading
Loading