@eventuras/scribo 0.4.2 → 0.8.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # @eventuras/scribo
2
+
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - chore: update dependencies across frontend packages
8
+
9
+ ## 0.8.0
10
+
11
+ ### Minor Changes
12
+
13
+ ### 🧱 Features
14
+
15
+ - feat(scribo): auto heigh expanding (6ca2322) [@eventuras/scribo]
16
+
17
+ ### 🐞 Bug Fixes
18
+
19
+ - fix(scribo): import MarkdownEditor.css for styling (d1eec7a) [@eventuras/scribo]
20
+ - fix(scribo): disable swc in scribo (ebce6db) [@eventuras/scribo]
21
+
22
+ ### ♻️ Refactoring
23
+
24
+ - refactor(scribo): merge markdowninput into scribo with flexible styling (373587e) [@eventuras/scribo]
25
+
26
+ ### 🧹 Maintenance
27
+
28
+ - chore(scribo): externalize Lexical packages in build configuration (7c7caaf) [@eventuras/scribo]
29
+ - chore(convertoapi,docsite,web,markdown,scribo,sdk): upgrade deps (b2de638) [@eventuras/scribo]
30
+
31
+ ### ⚙️ CI/CD
32
+
33
+ - ci(scribo): implement automated release workflow for Scribo (36d6ba1) [@eventuras/scribo]
34
+
35
+ ## 0.7.1
36
+
37
+ ### Patch Changes
38
+
39
+ - - chore(convertoapi,docsite,web,markdown,scribo,sdk): upgrade deps (51e931b) [@eventuras/scribo]
40
+
41
+ ## 0.7.0
42
+
43
+ ### Minor Changes
44
+
45
+ - ## Initial history (pre-Changesets)
46
+ - chore(scribo): upgrade lexical (f78dfa1)
47
+ - chore(scribo): update packages (3475e0f)
48
+ - feat(scribo): use readme as default content of editor page (428fc2b)
49
+ - fix(scribo): add dependency (101954b)
50
+ - feat(scribo): cache npm files for scribo build (a065b61)
51
+ - feat(scribo): add undo/redo to markdown editor (b2482cc)
52
+ - feat(scribo): adds links and link editing to editor (afe159d)
53
+ - feat(scribo): set up scribo demo site (a467bc2)
package/README.md CHANGED
@@ -1,26 +1,121 @@
1
1
  # Scribo markdown editor
2
2
 
3
- Markdown editor built on [Lexical framework](https://lexical.dev/). Intended to be used as part of eventuras, but also packed as a standalone component. Scribo is a WYSIWYG edito with markdown export.
3
+ Markdown editor built on [Lexical framework](https://lexical.dev/). Intended to be used as part of eventuras, but also packed as a standalone component. Scribo is a WYSIWYG editor with markdown export.
4
4
 
5
5
  The editor should provide simple and intuitive editing of markdown text, with a focus on the most common use cases.
6
6
 
7
+ Test the [scribo editor online demo](https://scribo.losol.no/).
8
+
9
+ > **Lexical version**: we are keeping the lexical version in sync with the [lexical version used in Payload CMS](https://github.com/payloadcms/payload/blob/main/packages/richtext-lexical/package.json), to prevent multiple versions making trouble.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @eventuras/scribo
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ **Important**: You must import the CSS file for Scribo to display correctly:
20
+
21
+ ### MarkdownEditor (WYSIWYG Editor)
22
+
23
+ ```tsx
24
+ import "@eventuras/scribo/style.css";
25
+ import { MarkdownEditor } from "@eventuras/scribo";
26
+
27
+ function MyComponent() {
28
+ return <MarkdownEditor defaultValue="# Hello World" />;
29
+ }
30
+ ```
31
+
32
+ ### MarkdownInput (Form-ready Input)
33
+
34
+ For use with native HTML forms (FormData API):
35
+
36
+ ```tsx
37
+ import "@eventuras/scribo/style.css";
38
+ import { MarkdownInput } from "@eventuras/scribo";
39
+
40
+ function MyForm() {
41
+ return (
42
+ <form>
43
+ <MarkdownInput
44
+ name="description"
45
+ label="Description"
46
+ placeholder="Enter markdown..."
47
+ defaultValue="# Hello"
48
+ maxLength={500}
49
+ className="mb-4"
50
+ labelClassName="font-bold"
51
+ editorClassName="border rounded"
52
+ errorClassName="text-red-500"
53
+ />
54
+ <button type="submit">Submit</button>
55
+ </form>
56
+ );
57
+ }
58
+ ```
59
+
60
+ The `MarkdownInput` component is framework-agnostic and provides:
61
+
62
+ - Hidden input for FormData submission
63
+ - Optional label and validation
64
+ - Flexible styling via className props
65
+ - Character limit validation
66
+
67
+ ### Peer Dependencies
68
+
69
+ Scribo requires the following peer dependencies:
70
+
71
+ ```bash
72
+ pnpm add react react-dom lexical @lexical/code @lexical/hashtag @lexical/link @lexical/list @lexical/mark @lexical/markdown @lexical/overflow @lexical/react @lexical/rich-text @lexical/selection @lexical/table @lexical/utils prismjs
73
+ ```
74
+
75
+ **Prism.js for syntax highlighting:**
76
+ Scribo uses Prism.js for code syntax highlighting but does not bundle it. You need to install `prismjs` and import the language grammars you need in your application.
77
+
78
+ Example:
79
+
80
+ ```tsx
81
+ import "prismjs";
82
+ import "prismjs/components/prism-javascript";
83
+ import "prismjs/components/prism-typescript";
84
+ import "prismjs/components/prism-css";
85
+ // Import other languages as needed
86
+ ```
87
+
7
88
  ## Develop
8
89
 
9
90
  To start developing Scribo, run the following commands:
10
91
 
11
92
  ```bash
12
- npm install
93
+ pnpm install
13
94
  vite dev
14
95
  ```
15
96
 
16
- ## Publish
97
+ ## Release
98
+
99
+ Quick release process:
100
+
101
+ ```bash
102
+ # 1. Create changeset (choose one)
103
+ pnpm changeset # Manual (recommended)
104
+ pnpm changeset:suggest # Auto-generate from commits
105
+
106
+ # 2. Version package
107
+ pnpm changeset:version
17
108
 
18
- To publish a new version of the package:
109
+ # 3. Commit and push (create PR to main)
110
+ git checkout -b release/scribo-v0.x.x
111
+ git add .
112
+ git commit -m "chore(scribo): release v0.x.x"
113
+ git push origin release/scribo-v0.x.x
114
+
115
+ # 4. Merge PR to main, then automated workflow will publish to npm and create GitHub release
116
+ ```
19
117
 
20
- 1. Update version number in package.json
21
- 1. `npm run build`
22
- 1. `npm login --scope @eventuras --auth-type web`
23
- 1. `npm publish --access public`
118
+ For detailed release instructions, including setup and troubleshooting, see [RELEASE.md](./RELEASE.md).
24
119
 
25
120
  ## Credits
26
121
 
package/RELEASE.md ADDED
@@ -0,0 +1,102 @@
1
+ # Release Instructions for @eventuras/scribo
2
+
3
+ This document explains how to configure the automated release workflow for `@eventuras/scribo`.
4
+
5
+ ## Release Process
6
+
7
+ ### Standard Release
8
+
9
+ 1. **Create changesets** for your changes:
10
+
11
+ ```bash
12
+ # Option A: Manual (recommended for better changelog messages)
13
+ pnpm changeset
14
+
15
+ # Option B: Auto-generate from commits (review and edit after)
16
+ pnpm changeset:suggest
17
+ ```
18
+
19
+ > **Tip**: `changeset:suggest` analyzes your git commits since the last release and automatically generates changeset files. Review the generated changesets and edit them for clarity if needed.
20
+
21
+ 2. **Version the package** when ready:
22
+
23
+ ```bash
24
+ pnpm changeset:version
25
+ ```
26
+
27
+ 3. **Commit and push** the version bump:
28
+
29
+ ```bash
30
+ git add .
31
+ git commit -m "chore(scribo): release v0.x.x"
32
+ git push origin main
33
+ ```
34
+
35
+ 4. **Automated workflow** will:
36
+ - ✅ Build the package
37
+ - ✅ Publish to npm with provenance
38
+ - ✅ Create Git tag `@eventuras/scribo@x.x.x`
39
+ - ✅ Create GitHub Release with changelog
40
+
41
+ ### Manual Release
42
+
43
+ If the automated workflow fails or you need to publish manually:
44
+
45
+ ```bash
46
+ cd libs/scribo
47
+ pnpm build
48
+ npm login --scope @eventuras --auth-type web
49
+ npm publish --access public
50
+ ```
51
+
52
+ ## Release Setup
53
+
54
+ ### NPM Trusted Publishing Setup (Recommended)
55
+
56
+ Scribo uses **npm trusted publishing with OIDC** for secure, token-free publishing.
57
+
58
+ #### Configure Trusted Publisher on npmjs.com
59
+
60
+ 1. Log in to [npmjs.com](https://www.npmjs.com)
61
+ 2. Navigate to `@eventuras/scribo` package settings
62
+ 3. Find **Trusted Publisher** section
63
+ 4. Click **GitHub Actions**
64
+ 5. Configure:
65
+ - **Organization or user**: `losol`
66
+ - **Repository**: `eventuras`
67
+ - **Workflow filename**: `scribo-release.yml` (include `.yml` extension)
68
+ - **Environment name**: `scribo-npm`
69
+ 6. Click **Add trusted publisher**
70
+
71
+ #### Set Up GitHub Environment
72
+
73
+ The workflow uses a GitHub environment for deployment protection (optional but recommended):
74
+
75
+ 1. Go to your GitHub repository
76
+ 2. Navigate to **Settings** → **Environments**
77
+ 3. Click **New environment**
78
+ 4. Name: `scribo-npm`
79
+ 5. (Optional) Add protection rules:
80
+ - Required reviewers if you want manual approval
81
+ - Deployment branches: Select "Selected branches" and add `main`
82
+ 6. Click **Create environment**
83
+
84
+ > **Note**: With trusted publishing, you don't need to add any secrets to this environment. The workflow uses OIDC authentication automatically.
85
+
86
+ ### Legacy: Token-Based Publishing (Not Recommended)
87
+
88
+ If you cannot use trusted publishing, you can fall back to traditional npm tokens:
89
+
90
+ 1. Create a granular access token on npmjs.com with write access to `@eventuras/scribo`
91
+ 2. Add it as `NODE_AUTH_TOKEN` secret in the `scribo-npm` environment
92
+ 3. Update the workflow to include `NODE_AUTH_TOKEN` environment variable in the publish step
93
+
94
+ However, trusted publishing is strongly recommended for better security.
95
+
96
+
97
+ ## Workflow Trigger
98
+
99
+ The release workflow (`.github/workflows/scribo-release.yml`) triggers automatically when:
100
+
101
+ - Changes are pushed to the `main` branch
102
+ - The file `libs/scribo/package.json` is modified
package/package.json CHANGED
@@ -1,41 +1,65 @@
1
1
  {
2
2
  "name": "@eventuras/scribo",
3
- "version": "0.4.2",
4
- "main": "src/main.tsx",
3
+ "version": "0.8.1",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/losol/eventuras.git",
7
+ "directory": "libs/scribo"
8
+ },
5
9
  "type": "module",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/main.d.ts",
13
+ "import": "./dist/main.js"
14
+ },
15
+ "./style.css": "./dist/scribo.css"
16
+ },
17
+ "main": "src/main.tsx",
6
18
  "scripts": {
7
19
  "build": "vite build",
8
- "build:site": "BUILD_SITE=true vite build",
20
+ "build:site": "vite build --config vite.config.site.ts",
9
21
  "dev": "vite"
10
22
  },
11
- "dependencies": {
12
- "@lexical/link": "0.28.0",
13
- "@lexical/markdown": "^0.28.0",
14
- "@lexical/react": "^0.28.0",
15
- "lexical": "^0.28.0"
16
- },
17
- "peerDependencies": {
18
- "react": "^19.1.0",
19
- "react-dom": "^19.1.0"
20
- },
21
- "devDependencies": {
22
- "@eventuras/eslint-config": "*",
23
- "@eventuras/typescript-config": "*",
24
- "@swc/core": "^1.13.3",
25
- "@testing-library/react": "^16.3.0",
26
- "@types/react": "^19.1.9",
27
- "@types/react-dom": "^19.1.7",
28
- "@vitejs/plugin-react-swc": "^4.0.0",
29
- "lightningcss": "^1.30.1",
30
- "react": "19.1.1",
31
- "react-dom": "19.1.1",
32
- "typescript": "^5.9.2",
33
- "vite": "^7.1.1",
34
- "vitest": "^3.2.4"
35
- },
36
23
  "overrides": {
37
24
  "vite": {
38
25
  "rollup": "npm:@rollup/wasm-node"
39
26
  }
27
+ },
28
+ "dependencies": {
29
+ "@lexical/code": "0.35.0",
30
+ "@lexical/hashtag": "0.35.0",
31
+ "@lexical/link": "0.35.0",
32
+ "@lexical/list": "0.35.0",
33
+ "@lexical/mark": "0.35.0",
34
+ "@lexical/markdown": "0.35.0",
35
+ "@lexical/overflow": "0.35.0",
36
+ "@lexical/react": "^0.35.0",
37
+ "@lexical/rich-text": "0.35.0",
38
+ "@lexical/selection": "0.35.0",
39
+ "@lexical/table": "0.35.0",
40
+ "@lexical/utils": "0.35.0",
41
+ "lexical": "0.35.0",
42
+ "prismjs": "^1.30.0"
43
+ },
44
+ "devDependencies": {
45
+ "@eventuras/eslint-config": "workspace:*",
46
+ "@eventuras/typescript-config": "workspace:*",
47
+ "@eventuras/vite-config": "workspace:*",
48
+ "@testing-library/react": "^16.3.1",
49
+ "@types/react": "^19.2.7",
50
+ "@types/react-dom": "^19.2.3",
51
+ "@vitejs/plugin-react": "^5.1.2",
52
+ "lightningcss": "^1.30.2",
53
+ "typescript": "^5.9.3",
54
+ "vite": "^7.3.0",
55
+ "vitest": "^4.0.16"
56
+ },
57
+ "peerDependencies": {
58
+ "react": "^19.2.3",
59
+ "react-dom": "^19.2.3"
60
+ },
61
+ "packageManager": "pnpm@10.26.0",
62
+ "engines": {
63
+ "node": ">=22.0.0 <25.0.0"
40
64
  }
41
65
  }
@@ -43,16 +43,64 @@
43
43
  z-index: 0;
44
44
  overflow: auto;
45
45
  resize: vertical;
46
- height: 80vh;
47
46
  }
48
47
 
49
48
  .editor {
50
49
  flex: auto;
51
50
  position: relative;
52
- resize: vertical;
53
51
  z-index: -1;
54
52
  }
55
53
 
54
+ .editor p {
55
+ margin: 0 0 1em 0;
56
+ }
57
+
58
+ .editor h1,
59
+ .editor h2,
60
+ .editor h3,
61
+ .editor h4,
62
+ .editor h5,
63
+ .editor h6 {
64
+ margin: 1em 0 0.5em 0;
65
+ line-height: 1.3;
66
+ }
67
+
68
+ .editor h1 {
69
+ font-size: 1.75em;
70
+ }
71
+
72
+ .editor h2 {
73
+ font-size: 1.5em;
74
+ }
75
+
76
+ .editor h3 {
77
+ font-size: 1.25em;
78
+ }
79
+
80
+ .editor h4 {
81
+ font-size: 1.1em;
82
+ }
83
+
84
+ .editor h5,
85
+ .editor h6 {
86
+ font-size: 1em;
87
+ }
88
+
89
+ .editor h1:first-child,
90
+ .editor h2:first-child,
91
+ .editor h3:first-child,
92
+ .editor h4:first-child,
93
+ .editor h5:first-child,
94
+ .editor h6:first-child {
95
+ margin-top: 0;
96
+ }
97
+
98
+ .editor blockquote {
99
+ margin: 1em 0;
100
+ padding-left: 1em;
101
+ border-left: 3px solid #ddd;
102
+ }
103
+
56
104
  ul {
57
105
  list-style-type: disc;
58
106
  padding: 10px 0 !important;
@@ -18,26 +18,27 @@ import ToolbarPlugin from "./plugins/ToolbarPlugin";
18
18
  import EditorTheme from "./themes/EditorTheme";
19
19
  import ContentEditable from "./ui/ContentEditable";
20
20
  import Placeholder from "./ui/Placeholder";
21
- import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
22
- import "./MarkdownEditor.css";
21
+ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
23
22
  import LinkPlugin from "./plugins/LinkPlugin";
24
23
  import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
25
24
  import AutoLinkPlugin from "./plugins/AutoLinkPlugin";
26
- import {useSharedHistoryContext} from './context/SharedHistoryContext';
25
+ import { useSharedHistoryContext } from './context/SharedHistoryContext';
27
26
 
28
27
  export type onChangeMisc = {
29
- plainText: string
30
- }
28
+ plainText: string;
29
+ };
31
30
  export interface MarkdownEditorProps {
32
31
  initialMarkdown?: string;
33
32
  className?: string;
34
33
  onChange?: (markdown: string, misc: onChangeMisc) => void;
35
34
  onBlur?: () => void;
36
35
  placeholder?: string;
36
+ 'data-testid'?: string;
37
+ id?: string;
37
38
  }
38
39
 
39
40
  const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
40
- const {historyState} = useSharedHistoryContext();
41
+ const { historyState } = useSharedHistoryContext();
41
42
  const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false);
42
43
 
43
44
  const [floatingAnchorElem, setFloatingAnchorElem] =
@@ -52,7 +53,7 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
52
53
  const internalOnChange = (editorState: EditorState) => {
53
54
  editorState.read(() => {
54
55
  const markdown = $convertToMarkdownString(TRANSFORMERS);
55
- const plainText = $getRoot().getTextContent()
56
+ const plainText = $getRoot().getTextContent();
56
57
  props.onChange && props.onChange(markdown, { plainText });
57
58
  });
58
59
  };
@@ -72,21 +73,21 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = (props) => {
72
73
  return (
73
74
  <div className={props.className}>
74
75
  <LexicalComposer initialConfig={initialConfig}>
75
- <div className="editor-shell" onBlur={props.onBlur}>
76
+ <div className="editor-shell" onBlur={props.onBlur} data-testid={props['data-testid']} id={props.id}>
76
77
  <ToolbarPlugin setIsLinkEditMode={setIsLinkEditMode} />
77
78
  <div className="editor-container rich-text">
78
79
  <HistoryPlugin externalHistoryState={historyState} />
79
80
  <AutoLinkPlugin />
80
81
  <LinkPlugin />
81
82
  <FloatingLinkEditorPlugin
82
- anchorElem={floatingAnchorElem}
83
- isLinkEditMode={isLinkEditMode}
84
- setIsLinkEditMode={setIsLinkEditMode}
85
- />
83
+ anchorElem={floatingAnchorElem}
84
+ isLinkEditMode={isLinkEditMode}
85
+ setIsLinkEditMode={setIsLinkEditMode}
86
+ />
86
87
  <RichTextPlugin
87
88
  contentEditable={
88
89
  <div className="editor-scroller">
89
- <div className="editor" ref={onRef}>
90
+ <div className="editor" ref={onRef} id={props.id ? `${props.id}-editable` : undefined}>
90
91
  <ContentEditable />
91
92
  </div>
92
93
  </div>
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+ import MarkdownEditor from './MarkdownEditor';
3
+ import { useRef, useState } from 'react';
4
+
5
+ export type MarkdownInputProps = {
6
+ id?: string;
7
+ name: string;
8
+ maxLength?: number;
9
+ label?: string;
10
+ placeholder?: string;
11
+ defaultValue?: string;
12
+ className?: string;
13
+ labelClassName?: string;
14
+ editorClassName?: string;
15
+ errorClassName?: string;
16
+ onChange?: (value: string) => void;
17
+ 'data-testid'?: string;
18
+ };
19
+
20
+ /**
21
+ * MarkdownInput component that works standalone or with parent form handling
22
+ * Uses onChange callback to notify parent of value changes
23
+ * Framework-agnostic with flexible className props for custom styling
24
+ */
25
+ const MarkdownInput = (props: MarkdownInputProps) => {
26
+ const [error, setError] = useState<string | null>(null);
27
+ const [value, setValue] = useState(props.defaultValue ?? '');
28
+ const plainTextRef = useRef('');
29
+ const id = props.id ?? props.name;
30
+
31
+ const handleChange = (markdown: string, { plainText }: { plainText: string; }) => {
32
+ plainTextRef.current = plainText;
33
+ setValue(markdown);
34
+
35
+ // Notify parent component of the change
36
+ if (props.onChange) {
37
+ props.onChange(markdown);
38
+ }
39
+
40
+ // Validate maxLength if provided
41
+ if (props.maxLength && plainText.length >= props.maxLength) {
42
+ setError(`Maximum ${props.maxLength} characters allowed`);
43
+ } else {
44
+ setError(null);
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div className={props.className}>
50
+ {props.label && (
51
+ <label htmlFor={id} className={props.labelClassName}>
52
+ {props.label}
53
+ </label>
54
+ )}
55
+ {/* Hidden input to store value - controlled by React state */}
56
+ <input
57
+ type="hidden"
58
+ name={props.name}
59
+ value={value}
60
+ />
61
+ <MarkdownEditor
62
+ onChange={handleChange}
63
+ className={props.editorClassName}
64
+ initialMarkdown={props.defaultValue}
65
+ placeholder={props.placeholder}
66
+ data-testid={props['data-testid']}
67
+ id={id}
68
+ />
69
+ {error && (
70
+ <span role="alert" className={props.errorClassName}>
71
+ {error}
72
+ </span>
73
+ )}
74
+ </div>
75
+ );
76
+ };
77
+
78
+ MarkdownInput.displayName = 'MarkdownInput';
79
+ export default MarkdownInput;
package/src/index.tsx CHANGED
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
3
  import { App } from './App'
4
4
  import './App.css'
5
+ import './MarkdownEditor.css'
5
6
 
6
7
  import readmeRaw from '../README.md?raw'
7
8
 
package/src/main.tsx CHANGED
@@ -1,4 +1,6 @@
1
-
2
- import MarkdownEditor from "./MarkdownEditor";
3
-
4
- export default MarkdownEditor;
1
+
2
+ import './MarkdownEditor.css';
3
+ import MarkdownEditor from "./MarkdownEditor";
4
+ import MarkdownInput from "./MarkdownInput";
5
+
6
+ export { MarkdownEditor, MarkdownInput };
@@ -18,6 +18,7 @@ import { HeadingNode, QuoteNode } from "@lexical/rich-text";
18
18
  import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
19
19
  import type { Klass, LexicalNode } from "lexical";
20
20
 
21
+ // Using 'as any' to work around Lexical version conflicts between workspace installs
21
22
  const EditorNodes: Array<Klass<LexicalNode>> = [
22
23
  HeadingNode,
23
24
  ListNode,
@@ -34,6 +35,6 @@ const EditorNodes: Array<Klass<LexicalNode>> = [
34
35
  OverflowNode,
35
36
  HorizontalRuleNode,
36
37
  MarkNode,
37
- ];
38
+ ] as any;
38
39
 
39
40
  export default EditorNodes;
@@ -1,3 +1,5 @@
1
+ // @ts-nocheck
2
+ // https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/FloatingLinkEditorPlugin/index.tsx
1
3
  /**
2
4
  * Copyright (c) Meta Platforms, Inc. and affiliates.
3
5
  *
@@ -5,7 +7,6 @@
5
7
  * LICENSE file in the root directory of this source tree.
6
8
  *
7
9
  */
8
- // https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/FloatingLinkEditorPlugin/index.tsx
9
10
  import type {JSX} from 'react';
10
11
 
11
12
  import './FloatingLinkEditorPlugin.css';
@@ -451,7 +452,7 @@ function useFloatingLinkEditorToolbar(
451
452
  }
452
453
 
453
454
  export default function FloatingLinkEditorPlugin({
454
- anchorElem = document.body,
455
+ anchorElem,
455
456
  isLinkEditMode,
456
457
  setIsLinkEditMode,
457
458
  }: {
@@ -460,9 +461,19 @@ export default function FloatingLinkEditorPlugin({
460
461
  setIsLinkEditMode: Dispatch<boolean>;
461
462
  }): JSX.Element | null {
462
463
  const [editor] = useLexicalComposerContext();
464
+
465
+ // Use document.body as default, but only in browser environment
466
+ const defaultAnchorElem = typeof document !== 'undefined' ? document.body : null;
467
+ const effectiveAnchorElem = anchorElem ?? defaultAnchorElem;
468
+
469
+ // Don't render if we don't have a valid anchor element
470
+ if (!effectiveAnchorElem) {
471
+ return null;
472
+ }
473
+
463
474
  return useFloatingLinkEditorToolbar(
464
475
  editor,
465
- anchorElem,
476
+ effectiveAnchorElem,
466
477
  isLinkEditMode,
467
478
  setIsLinkEditMode,
468
479
  );
@@ -140,7 +140,11 @@ function getChangeType(
140
140
  }
141
141
 
142
142
  const nextDirtyNode = dirtyNodes[0];
143
-
143
+
144
+ if (!nextDirtyNode) {
145
+ return OTHER;
146
+ }
147
+
144
148
  const prevDirtyNode = prevEditorState._nodeMap.get(nextDirtyNode.__key);
145
149
 
146
150
  if (
@@ -299,6 +303,7 @@ function createMergeActionGetter(
299
303
  if (dirtyLeaves.size === 1) {
300
304
  const dirtyLeafKey = Array.from(dirtyLeaves)[0];
301
305
  if (
306
+ dirtyLeafKey &&
302
307
  isTextNodeUnchanged(dirtyLeafKey, prevEditorState, nextEditorState)
303
308
  ) {
304
309
  return HISTORY_MERGE;
@@ -9,13 +9,16 @@
9
9
  .PlaygroundEditorTheme__ltr {
10
10
  text-align: left;
11
11
  }
12
+
12
13
  .PlaygroundEditorTheme__rtl {
13
14
  text-align: right;
14
15
  }
16
+
15
17
  .PlaygroundEditorTheme__paragraph {
16
18
  margin: 0;
17
19
  position: relative;
18
20
  }
21
+
19
22
  .PlaygroundEditorTheme__quote {
20
23
  margin: 0;
21
24
  margin-left: 20px;
@@ -27,6 +30,7 @@
27
30
  border-left-style: solid;
28
31
  padding-left: 16px;
29
32
  }
33
+
30
34
  .PlaygroundEditorTheme__h1 {
31
35
  font-size: 32px;
32
36
  line-height: 1.3;
@@ -36,74 +40,90 @@
36
40
  font-weight: 700;
37
41
  margin: 0;
38
42
  }
43
+
39
44
  .PlaygroundEditorTheme__h2 {
40
- font-size: 32px;
45
+ font-size: 24px;
41
46
  color: #406280;
42
47
  padding-top: 2rem;
43
- padding-bottom: 0.5rem;
48
+ padding-bottom: 0.5rem;
44
49
  font-weight: 700;
45
50
  margin: 0;
46
51
  }
52
+
47
53
  .PlaygroundEditorTheme__h3 {
48
54
  font-size: 24px;
49
55
  color: #406280;
50
- padding-top: 2rem;
51
- padding-bottom: 0.5rem;
56
+ padding-top: 2rem;
57
+ padding-bottom: 0.5rem;
52
58
  font-weight: 700;
53
59
  margin: 0;
54
60
  }
61
+
55
62
  .PlaygroundEditorTheme__h4 {
56
63
  font-size: 16px;
57
64
  color: #406280;
58
- padding-top: 1.5rem;
59
- padding-bottom: 0rem;
65
+ padding-top: 1.5rem;
66
+ padding-bottom: 0rem;
60
67
  font-weight: 700;
61
68
  margin: 0;
62
69
  }
70
+
63
71
  .PlaygroundEditorTheme__indent {
64
72
  --lexical-indent-base-value: 40px;
65
73
  }
74
+
66
75
  .PlaygroundEditorTheme__textBold {
67
76
  font-weight: bold;
68
77
  }
78
+
69
79
  .PlaygroundEditorTheme__textItalic {
70
80
  font-style: italic;
71
81
  }
82
+
72
83
  .PlaygroundEditorTheme__textUnderline {
73
84
  text-decoration: underline;
74
85
  }
86
+
75
87
  .PlaygroundEditorTheme__textStrikethrough {
76
88
  text-decoration: line-through;
77
89
  }
90
+
78
91
  .PlaygroundEditorTheme__textUnderlineStrikethrough {
79
92
  text-decoration: underline line-through;
80
93
  }
94
+
81
95
  .PlaygroundEditorTheme__textSubscript {
82
96
  font-size: 0.8em;
83
97
  vertical-align: sub !important;
84
98
  }
99
+
85
100
  .PlaygroundEditorTheme__textSuperscript {
86
101
  font-size: 0.8em;
87
102
  vertical-align: super;
88
103
  }
104
+
89
105
  .PlaygroundEditorTheme__textCode {
90
106
  background-color: rgb(240, 242, 245);
91
107
  padding: 1px 0.25rem;
92
108
  font-family: Menlo, Consolas, Monaco, monospace;
93
109
  font-size: 94%;
94
110
  }
111
+
95
112
  .PlaygroundEditorTheme__hashtag {
96
113
  background-color: rgba(88, 144, 255, 0.15);
97
114
  border-bottom: 1px solid rgba(88, 144, 255, 0.3);
98
115
  }
116
+
99
117
  .PlaygroundEditorTheme__link {
100
118
  color: rgb(33, 111, 219);
101
119
  text-decoration: none;
102
120
  }
121
+
103
122
  .PlaygroundEditorTheme__link:hover {
104
123
  text-decoration: underline;
105
124
  cursor: pointer;
106
125
  }
126
+
107
127
  .PlaygroundEditorTheme__code {
108
128
  background-color: rgb(240, 242, 245);
109
129
  font-family: Menlo, Consolas, Monaco, monospace;
@@ -118,6 +138,7 @@
118
138
  position: relative;
119
139
  tab-size: 2;
120
140
  }
141
+
121
142
  .PlaygroundEditorTheme__code:before {
122
143
  content: attr(data-gutter);
123
144
  position: absolute;
@@ -131,47 +152,56 @@
131
152
  text-align: right;
132
153
  min-width: 25px;
133
154
  }
155
+
134
156
  .PlaygroundEditorTheme__characterLimit {
135
157
  display: inline;
136
158
  background-color: #ffbbbb !important;
137
159
  }
160
+
138
161
  .PlaygroundEditorTheme__ol1 {
139
162
  padding: 0;
140
163
  margin: 0;
141
164
  list-style-position: inside;
142
165
  }
166
+
143
167
  .PlaygroundEditorTheme__ol2 {
144
168
  padding: 0;
145
169
  margin: 0;
146
170
  list-style-type: upper-alpha;
147
171
  list-style-position: inside;
148
172
  }
173
+
149
174
  .PlaygroundEditorTheme__ol3 {
150
175
  padding: 0;
151
176
  margin: 0;
152
177
  list-style-type: lower-alpha;
153
178
  list-style-position: inside;
154
179
  }
180
+
155
181
  .PlaygroundEditorTheme__ol4 {
156
182
  padding: 0;
157
183
  margin: 0;
158
184
  list-style-type: upper-roman;
159
185
  list-style-position: inside;
160
186
  }
187
+
161
188
  .PlaygroundEditorTheme__ol5 {
162
189
  padding: 0;
163
190
  margin: 0;
164
191
  list-style-type: lower-roman;
165
192
  list-style-position: inside;
166
193
  }
194
+
167
195
  .PlaygroundEditorTheme__ul {
168
196
  padding: 0;
169
197
  margin: 0;
170
198
  list-style-position: inside;
171
199
  }
200
+
172
201
  .PlaygroundEditorTheme__listItem {
173
202
  margin: 0 32px;
174
203
  }
204
+
175
205
  .PlaygroundEditorTheme__listItemChecked,
176
206
  .PlaygroundEditorTheme__listItemUnchecked {
177
207
  position: relative;
@@ -182,9 +212,11 @@
182
212
  list-style-type: none;
183
213
  outline: none;
184
214
  }
215
+
185
216
  .PlaygroundEditorTheme__listItemChecked {
186
217
  text-decoration: line-through;
187
218
  }
219
+
188
220
  .PlaygroundEditorTheme__listItemUnchecked:before,
189
221
  .PlaygroundEditorTheme__listItemChecked:before {
190
222
  content: "";
@@ -197,26 +229,31 @@
197
229
  background-size: cover;
198
230
  position: absolute;
199
231
  }
232
+
200
233
  .PlaygroundEditorTheme__listItemUnchecked[dir="rtl"]:before,
201
234
  .PlaygroundEditorTheme__listItemChecked[dir="rtl"]:before {
202
235
  left: auto;
203
236
  right: 0;
204
237
  }
238
+
205
239
  .PlaygroundEditorTheme__listItemUnchecked:focus:before,
206
240
  .PlaygroundEditorTheme__listItemChecked:focus:before {
207
241
  box-shadow: 0 0 0 2px #a6cdfe;
208
242
  border-radius: 2px;
209
243
  }
244
+
210
245
  .PlaygroundEditorTheme__listItemUnchecked:before {
211
246
  border: 1px solid #999;
212
247
  border-radius: 2px;
213
248
  }
249
+
214
250
  .PlaygroundEditorTheme__listItemChecked:before {
215
251
  border: 1px solid rgb(61, 135, 245);
216
252
  border-radius: 2px;
217
253
  background-color: #3d87f5;
218
254
  background-repeat: no-repeat;
219
255
  }
256
+
220
257
  .PlaygroundEditorTheme__listItemChecked:after {
221
258
  content: "";
222
259
  cursor: pointer;
@@ -232,65 +269,83 @@
232
269
  transform: rotate(45deg);
233
270
  border-width: 0 2px 2px 0;
234
271
  }
272
+
235
273
  .PlaygroundEditorTheme__nestedListItem {
236
274
  list-style-type: none;
237
275
  }
276
+
238
277
  .PlaygroundEditorTheme__nestedListItem:before,
239
278
  .PlaygroundEditorTheme__nestedListItem:after {
240
279
  display: none;
241
280
  }
281
+
242
282
  .PlaygroundEditorTheme__tokenComment {
243
283
  color: slategray;
244
284
  }
285
+
245
286
  .PlaygroundEditorTheme__tokenPunctuation {
246
287
  color: #999;
247
288
  }
289
+
248
290
  .PlaygroundEditorTheme__tokenProperty {
249
291
  color: #905;
250
292
  }
293
+
251
294
  .PlaygroundEditorTheme__tokenSelector {
252
295
  color: #690;
253
296
  }
297
+
254
298
  .PlaygroundEditorTheme__tokenOperator {
255
299
  color: #9a6e3a;
256
300
  }
301
+
257
302
  .PlaygroundEditorTheme__tokenAttr {
258
303
  color: #07a;
259
304
  }
305
+
260
306
  .PlaygroundEditorTheme__tokenVariable {
261
307
  color: #e90;
262
308
  }
309
+
263
310
  .PlaygroundEditorTheme__tokenFunction {
264
311
  color: #dd4a68;
265
312
  }
313
+
266
314
  .PlaygroundEditorTheme__mark {
267
315
  background: rgba(255, 212, 0, 0.14);
268
316
  border-bottom: 2px solid rgba(255, 212, 0, 0.3);
269
317
  padding-bottom: 2px;
270
318
  }
319
+
271
320
  .PlaygroundEditorTheme__markOverlap {
272
321
  background: rgba(255, 212, 0, 0.3);
273
322
  border-bottom: 2px solid rgba(255, 212, 0, 0.7);
274
323
  }
324
+
275
325
  .PlaygroundEditorTheme__mark.selected {
276
326
  background: rgba(255, 212, 0, 0.5);
277
327
  border-bottom: 2px solid rgba(255, 212, 0, 1);
278
328
  }
329
+
279
330
  .PlaygroundEditorTheme__markOverlap.selected {
280
331
  background: rgba(255, 212, 0, 0.7);
281
332
  border-bottom: 2px solid rgba(255, 212, 0, 0.7);
282
333
  }
334
+
283
335
  .PlaygroundEditorTheme__embedBlock {
284
336
  user-select: none;
285
337
  }
338
+
286
339
  .PlaygroundEditorTheme__embedBlockFocus {
287
340
  outline: 2px solid rgb(60, 132, 244);
288
341
  }
342
+
289
343
  .PlaygroundEditorTheme__layoutContaner {
290
344
  display: grid;
291
345
  gap: 10px;
292
346
  margin: 10px 0;
293
347
  }
348
+
294
349
  .PlaygroundEditorTheme__layoutItem {
295
350
  border: 1px dashed #ddd;
296
351
  padding: 8px 16px;
@@ -1,19 +1,21 @@
1
- /**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- */
8
- import type {JSX} from 'react';
9
- import "./ContentEditable.css";
10
-
11
- import { ContentEditable } from "@lexical/react/LexicalContentEditable";
12
-
13
- export default function LexicalContentEditable({
14
- className,
15
- }: {
16
- className?: string;
17
- }): JSX.Element {
18
- return <ContentEditable className={className || "ContentEditable__root"} />;
19
- }
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import type { JSX } from 'react';
9
+ import "./ContentEditable.css";
10
+
11
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
12
+
13
+ export default function LexicalContentEditable({
14
+ className,
15
+ id,
16
+ }: {
17
+ className?: string;
18
+ id?: string;
19
+ }): JSX.Element {
20
+ return <ContentEditable className={className || "ContentEditable__root"} id={id} />;
21
+ }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { resolve } from 'path';
4
+
5
+ /**
6
+ * Vite configuration for building the demo site.
7
+ * Used explicitly via: vite build --config vite.config.site.ts
8
+ */
9
+ export default defineConfig({
10
+ plugins: [react()],
11
+ build: {
12
+ outDir: 'dist/site',
13
+ rollupOptions: {
14
+ input: {
15
+ main: resolve(__dirname, 'index.html'),
16
+ },
17
+ },
18
+ },
19
+ });
package/vite.config.ts CHANGED
@@ -1,32 +1,33 @@
1
- import { defineConfig } from 'vite';
2
- import react from '@vitejs/plugin-react-swc';
3
- import { resolve } from 'path';
4
-
5
- // Check build mode
6
- const buildSite = process.env.BUILD_SITE === 'true';
7
-
8
- export default defineConfig({
9
- plugins: [react()],
10
- build: buildSite
11
- ? {
12
- // Build demo site
13
- outDir: 'dist/site',
14
- }
15
- : {
16
- // Build library
17
- lib: {
18
- entry: resolve(__dirname, 'src/main.tsx'),
19
- name: 'Scribo',
20
- fileName: 'scribo',
21
- },
22
- rollupOptions: {
23
- external: ['react', 'react-dom'],
24
- output: {
25
- globals: {
26
- react: 'React',
27
- 'react-dom': 'ReactDOM',
28
- },
29
- },
30
- },
31
- },
32
- });
1
+ import { defineReactLibConfig } from '@eventuras/vite-config/react-lib';
2
+
3
+ /**
4
+ * Vite configuration for building the Scribo library.
5
+ * For the demo site, see vite.config.site.ts
6
+ */
7
+ export default defineReactLibConfig({
8
+ entry: 'src/main.tsx',
9
+ useSWC: false,
10
+ external: [
11
+ // Externalize all Lexical packages to avoid bundling them
12
+ 'lexical',
13
+ '@lexical/code',
14
+ '@lexical/hashtag',
15
+ '@lexical/history',
16
+ '@lexical/link',
17
+ '@lexical/list',
18
+ '@lexical/markdown',
19
+ '@lexical/mark',
20
+ '@lexical/overflow',
21
+ '@lexical/react',
22
+ '@lexical/rich-text',
23
+ '@lexical/selection',
24
+ '@lexical/text',
25
+ '@lexical/utils',
26
+ // Also externalize any sub-exports from @lexical/react
27
+ /^@lexical\//,
28
+ // Externalize Prism.js to avoid bundling all language grammars
29
+ 'prismjs',
30
+ /^prismjs\//,
31
+ ],
32
+ });
33
+