@teriusu/rich-editor 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/types.js CHANGED
@@ -4,27 +4,28 @@ export const TOOLBAR_PRESETS = {
4
4
  'bold', 'italic', 'underline', 'strike', 'highlight',
5
5
  'superscript', 'subscript', 'code', 'text-color',
6
6
  'align-left', 'align-center', 'align-right',
7
- 'h1', 'h2', 'h3',
7
+ 'paragraph', 'h1', 'h2', 'h3',
8
8
  'bullet-list', 'ordered-list', 'checklist',
9
9
  'blockquote', 'horizontal-rule', 'toggle',
10
- 'link', 'image', 'pdf', 'youtube', 'video', 'file', 'mbus',
10
+ 'link', 'image', 'pdf', 'file', 'mbus',
11
11
  'columns-2', 'columns-3', 'table', 'code-block',
12
12
  'undo', 'redo',
13
13
  'fixed-toolbar', 'slash-menu',
14
14
  'table-menu', 'character-count', 'upload-overlay'
15
15
  ],
16
16
  standard: [
17
- 'bold', 'italic', 'strike', 'code', 'text-color',
18
- 'h1', 'h2', 'h3',
17
+ 'bold', 'italic', 'underline', 'strike', 'code', 'text-color',
18
+ 'align-left', 'align-center', 'align-right',
19
+ 'paragraph', 'h1', 'h2', 'h3',
19
20
  'bullet-list', 'ordered-list', 'checklist',
20
21
  'blockquote', 'horizontal-rule', 'toggle',
21
- 'link', 'image', 'pdf', 'youtube', 'video', 'file', 'mbus',
22
+ 'link', 'image', 'pdf', 'file', 'mbus',
22
23
  'columns-2', 'columns-3', 'table', 'code-block',
23
24
  'fixed-toolbar', 'slash-menu', 'table-menu'
24
25
  ],
25
26
  minimal: [
26
- 'bold', 'italic', 'strike', 'code',
27
- 'h2', 'h3',
27
+ 'bold', 'italic', 'underline', 'strike', 'code', 'text-color',
28
+ 'paragraph', 'h2', 'h3',
28
29
  'bullet-list', 'ordered-list', 'checklist',
29
30
  'blockquote', 'code-block',
30
31
  'link', 'image', 'file', 'pdf',
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AA4BA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcjD;AA0BD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4DxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAWrE"}
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AA4BA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcjD;AA0BD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsDxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAM,GAAG,MAAM,CAWrE"}
@@ -70,9 +70,6 @@ export function transformLegacyHtml(html) {
70
70
  // <tiptap-collapsable title="X">content</tiptap-collapsable>
71
71
  // → <details><summary>X</summary><div>content</div></details>
72
72
  .replace(/<tiptap-collapsable\s+title="([^"]*)">([\s\S]*?)<\/tiptap-collapsable>/gi, '<details><summary>$1</summary><div>$2</div></details>')
73
- // <lite-youtube videoid="X" ...></lite-youtube>
74
- // → <div data-youtube-video><iframe src="https://www.youtube.com/embed/X" allowfullscreen></iframe></div>
75
- .replace(/<lite-youtube\s+videoid="([^"]*)"[^>]*>(?:<\/lite-youtube>)?/gi, '<div data-youtube-video=""><iframe src="https://www.youtube.com/embed/$1" allowfullscreen></iframe></div>')
76
73
  // <embed src="X" type="application/pdf" ...>
77
74
  // → <div data-pdf-src="X" data-pdf-name="filename">
78
75
  .replace(/<embed\s+[^>]*src="([^"]*)"[^>]*type="application\/pdf"[^>]*\/?>/gi, (_match, src) => {
@@ -36,13 +36,6 @@ describe('transformLegacyHtml', () => {
36
36
  expect(result).toContain('Hidden content');
37
37
  expect(result).not.toContain('tiptap-collapsable');
38
38
  });
39
- it('converts <lite-youtube videoid="X"> to YouTube iframe', () => {
40
- const input = '<lite-youtube videoid="dQw4w9WgXcQ" params=""></lite-youtube>';
41
- const result = transformLegacyHtml(input);
42
- expect(result).toContain('https://www.youtube.com/embed/dQw4w9WgXcQ');
43
- expect(result).toContain('data-youtube-video');
44
- expect(result).not.toContain('lite-youtube');
45
- });
46
39
  it('converts <embed type="application/pdf"> (src before type)', () => {
47
40
  const input = '<embed src="https://example.com/file.pdf" type="application/pdf" />';
48
41
  const result = transformLegacyHtml(input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teriusu/rich-editor",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "TipTap 기반 Notion-style 리치 텍스트 에디터 (Svelte)",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -55,7 +55,6 @@
55
55
  "@tiptap/extension-text-style": "^3.20.4",
56
56
  "@tiptap/extension-typography": "^3.21.0",
57
57
  "@tiptap/extension-underline": "^3.20.4",
58
- "@tiptap/extension-youtube": "^3.21.0",
59
58
  "@tiptap/pm": "^3.20.4",
60
59
  "@tiptap/starter-kit": "^3.22.0",
61
60
  "clsx": "^2.1.1",
@@ -1,16 +0,0 @@
1
- import { Node } from "@tiptap/core";
2
- export interface VideoBlockOptions {
3
- HTMLAttributes: Record<string, unknown>;
4
- }
5
- declare module "@tiptap/core" {
6
- interface Commands<ReturnType> {
7
- videoBlock: {
8
- setVideoBlock: (attrs: {
9
- src: string;
10
- name?: string;
11
- }) => ReturnType;
12
- };
13
- }
14
- }
15
- export declare const VideoBlock: Node<VideoBlockOptions, any>;
16
- //# sourceMappingURL=VideoBlock.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"VideoBlock.d.ts","sourceRoot":"","sources":["../../src/extensions/VideoBlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAED,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,QAAQ,CAAC,UAAU;QAC3B,UAAU,EAAE;YACV,aAAa,EAAE,CAAC,KAAK,EAAE;gBAAE,GAAG,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,KAAK,UAAU,CAAC;SACtE,CAAC;KACH;CACF;AAED,eAAO,MAAM,UAAU,8BAkGrB,CAAC"}
@@ -1,87 +0,0 @@
1
- import { Node, mergeAttributes } from "@tiptap/core";
2
- export const VideoBlock = Node.create({
3
- name: "videoBlock",
4
- group: "block",
5
- atom: true,
6
- draggable: true,
7
- addOptions() {
8
- return { HTMLAttributes: {} };
9
- },
10
- addAttributes() {
11
- return {
12
- src: { default: null },
13
- name: { default: "" },
14
- };
15
- },
16
- parseHTML() {
17
- return [
18
- {
19
- tag: 'div[data-video-src]',
20
- getAttrs: (dom) => {
21
- const el = dom;
22
- return {
23
- src: el.getAttribute("data-video-src"),
24
- name: el.getAttribute("data-video-name") || "",
25
- };
26
- },
27
- },
28
- ];
29
- },
30
- renderHTML({ HTMLAttributes }) {
31
- return [
32
- "div",
33
- mergeAttributes(this.options.HTMLAttributes, {
34
- "data-video-src": HTMLAttributes.src,
35
- "data-video-name": HTMLAttributes.name || "",
36
- }),
37
- ];
38
- },
39
- addNodeView() {
40
- return ({ node, editor }) => {
41
- const dom = document.createElement("div");
42
- dom.style.cssText = "margin:8px 0;position:relative;";
43
- const video = document.createElement("video");
44
- video.src = node.attrs.src;
45
- video.controls = true;
46
- video.preload = "metadata";
47
- video.style.cssText =
48
- "width:100%;max-height:480px;border-radius:8px;background:#0b1020;";
49
- video.setAttribute("controlslist", "nodownload");
50
- video.setAttribute("playsinline", "");
51
- dom.appendChild(video);
52
- if (node.attrs.name) {
53
- const label = document.createElement("p");
54
- label.textContent = node.attrs.name;
55
- label.style.cssText =
56
- "margin:4px 0 0;font-size:12px;color:var(--muted-foreground, #718096);";
57
- dom.appendChild(label);
58
- }
59
- if (editor.isEditable) {
60
- const del = document.createElement("button");
61
- del.type = "button";
62
- del.textContent = "\u00D7";
63
- del.style.cssText =
64
- "position:absolute;top:8px;right:8px;width:28px;height:28px;border:none;background:rgba(0,0,0,0.6);color:#fff;font-size:18px;cursor:pointer;border-radius:50%;display:flex;align-items:center;justify-content:center;z-index:2;";
65
- del.addEventListener("mouseenter", () => {
66
- del.style.background = "var(--destructive, #FF6B6B)";
67
- });
68
- del.addEventListener("mouseleave", () => {
69
- del.style.background = "rgba(0,0,0,0.6)";
70
- });
71
- del.addEventListener("click", () => {
72
- const pos = editor.view.posAtDOM(dom, 0);
73
- editor.chain().focus().deleteRange({ from: pos, to: pos + 1 }).run();
74
- });
75
- dom.appendChild(del);
76
- }
77
- return { dom };
78
- };
79
- },
80
- addCommands() {
81
- return {
82
- setVideoBlock: (attrs) => ({ chain }) => {
83
- return chain().insertContent({ type: this.name, attrs }).run();
84
- },
85
- };
86
- },
87
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=VideoBlock.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"VideoBlock.test.d.ts","sourceRoot":"","sources":["../../src/extensions/VideoBlock.test.ts"],"names":[],"mappings":""}
@@ -1,54 +0,0 @@
1
- import { describe, it, expect, afterEach } from 'vitest';
2
- import { Editor } from '@tiptap/core';
3
- import StarterKit from '@tiptap/starter-kit';
4
- import { VideoBlock } from './VideoBlock';
5
- describe('VideoBlock extension', () => {
6
- let editor;
7
- function createEditor(content = '<p></p>') {
8
- editor = new Editor({
9
- element: document.createElement('div'),
10
- extensions: [StarterKit, VideoBlock],
11
- content
12
- });
13
- return editor;
14
- }
15
- afterEach(() => {
16
- editor?.destroy();
17
- });
18
- it('registers with name "videoBlock"', () => {
19
- createEditor();
20
- const ext = editor.extensionManager.extensions.find((e) => e.name === 'videoBlock');
21
- expect(ext).toBeDefined();
22
- });
23
- it('parses <div data-video-src="url">', () => {
24
- createEditor('<div data-video-src="https://example.com/video.mp4" data-video-name="demo"></div>');
25
- const doc = editor.getJSON();
26
- const videoNode = doc.content?.find((n) => n.type === 'videoBlock');
27
- expect(videoNode).toBeDefined();
28
- expect(videoNode?.attrs?.src).toBe('https://example.com/video.mp4');
29
- expect(videoNode?.attrs?.name).toBe('demo');
30
- });
31
- it('renders correct HTML', () => {
32
- createEditor('<div data-video-src="https://example.com/video.mp4" data-video-name="demo"></div>');
33
- const html = editor.getHTML();
34
- expect(html).toContain('data-video-src="https://example.com/video.mp4"');
35
- expect(html).toContain('data-video-name="demo"');
36
- });
37
- it('defaults name to empty string', () => {
38
- createEditor('<div data-video-src="https://example.com/video.mp4"></div>');
39
- const doc = editor.getJSON();
40
- const videoNode = doc.content?.find((n) => n.type === 'videoBlock');
41
- expect(videoNode?.attrs?.name).toBe('');
42
- });
43
- it('inserts via setVideoBlock command', () => {
44
- createEditor();
45
- editor.commands.setVideoBlock({
46
- src: 'https://example.com/v.mp4',
47
- name: 'My Video'
48
- });
49
- const doc = editor.getJSON();
50
- const videoNode = doc.content?.find((n) => n.type === 'videoBlock');
51
- expect(videoNode).toBeDefined();
52
- expect(videoNode?.attrs?.src).toBe('https://example.com/v.mp4');
53
- });
54
- });