silk-compose 0.0.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/README.md +124 -0
- package/dist/index.d.ts +97 -0
- package/dist/silk.css +1 -0
- package/dist/silk.js +1764 -0
- package/package.json +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Silk
|
|
2
|
+
|
|
3
|
+
An opinionated, batteries-included rich text editor for React, built on [Lexical](https://lexical.dev).
|
|
4
|
+
|
|
5
|
+
Silk ships as a single component with styles, sensible defaults, and a curated feature set — so you can drop it into a project and have a polished editing experience without wiring together a dozen plugins.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install silk-compose lexical @lexical/react @lexical/rich-text @lexical/code \
|
|
11
|
+
@lexical/code-shiki @lexical/history @lexical/link @lexical/list \
|
|
12
|
+
@lexical/markdown @lexical/selection @lexical/dragon
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { SilkEditor } from "silk-compose";
|
|
19
|
+
import "silk-compose/styles";
|
|
20
|
+
|
|
21
|
+
function App() {
|
|
22
|
+
return <SilkEditor />;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That's it. You get a fully functional editor with formatting, code blocks, lists, links, images, and more.
|
|
27
|
+
|
|
28
|
+
## Saving and restoring content
|
|
29
|
+
|
|
30
|
+
Silk uses Lexical's native serialization. Pass an `onChange` callback to receive the editor state as a JSON string on every content change, and `initialEditorState` to restore it.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
function App() {
|
|
34
|
+
const [saved, setSaved] = useState<string | undefined>(
|
|
35
|
+
() => localStorage.getItem("doc") ?? undefined,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<SilkEditor
|
|
40
|
+
initialEditorState={saved}
|
|
41
|
+
onChange={(json) => localStorage.setItem("doc", json)}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The JSON string is a complete snapshot of the document — text, formatting, images, code blocks, everything. Store it however you like (database, local storage, file) and pass it back to restore the editor exactly as it was.
|
|
48
|
+
|
|
49
|
+
## Props
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | Description |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `editable` | `boolean` | `true` | Toggle between edit and read-only mode at runtime. |
|
|
54
|
+
| `onChange` | `(json: string) => void` | — | Called on every content change with the serialized editor state. |
|
|
55
|
+
| `initialEditorState` | `string` | — | JSON string from a previous `onChange` to restore content. |
|
|
56
|
+
| `features` | `SilkFeatures` | All enabled | Toggle feature groups on/off. |
|
|
57
|
+
| `namespace` | `string` | `"silk-composeor"` | Lexical editor namespace. |
|
|
58
|
+
| `className` | `string` | — | Additional CSS class on the container. |
|
|
59
|
+
| `theme` | `EditorThemeClasses` | — | Lexical theme overrides (deep-merged with defaults). |
|
|
60
|
+
| `onError` | `(error: Error) => void` | `console.error` | Error handler for Lexical. |
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
Everything is enabled by default. Disable individual features via the `features` prop:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<SilkEditor features={{ code: false, dragon: false }} />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Feature | Key | What it includes |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| **Rich text** | `richText` | Bold, italic, underline, inline code, headings (H1/H2), block quotes |
|
|
73
|
+
| **Code blocks** | `code` | Syntax-highlighted code blocks powered by Shiki, with a language selector |
|
|
74
|
+
| **Lists** | `lists` | Ordered and unordered lists with Tab/Shift+Tab indentation. Nested ordered lists cycle through numbers, uppercase letters, and lowercase letters. |
|
|
75
|
+
| **History** | `history` | Undo/redo |
|
|
76
|
+
| **Dragon** | `dragon` | Speech dictation accessibility support |
|
|
77
|
+
|
|
78
|
+
### Always-on capabilities
|
|
79
|
+
|
|
80
|
+
These are not feature-gated and are always available:
|
|
81
|
+
|
|
82
|
+
- **Static toolbar** — formatting, font size, color, font family, links, quotes
|
|
83
|
+
- **Floating toolbar** — appears on text selection with the same controls
|
|
84
|
+
- **Links** — insert via toolbar or `/` menu; Cmd/Ctrl+click to open in edit mode, regular click in read-only mode; always opens in a new tab
|
|
85
|
+
- **Images** — paste or drag-and-drop any image into the editor; click to select, drag corners to resize; images are serialized as base64 in the document JSON
|
|
86
|
+
- **Note blocks** — info, question, and error callout blocks with colored left borders
|
|
87
|
+
- **Horizontal rules** — section dividers
|
|
88
|
+
- **Slash commands** — type `/` to insert headings, code blocks, lists, notes, links, dividers
|
|
89
|
+
- **Markdown shortcuts** — `*`/`-` for bullet lists, `1.` for numbered lists, common text format triggers
|
|
90
|
+
- **Font controls** — size (10–36), family (Inter, SF Mono, Space Grotesk), and a curated color palette
|
|
91
|
+
- **Read-only mode** — pass `editable={false}` to disable editing; toolbars hide, links become directly clickable
|
|
92
|
+
|
|
93
|
+
## Styling
|
|
94
|
+
|
|
95
|
+
Silk ships a single CSS file with all styles. Import it once:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import "silk-compose/styles";
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
All CSS classes are prefixed with `silk-` to avoid collisions. The default theme uses a warm, neutral palette with Inter for body text, Space Grotesk for headings, and SF Mono for code and technical labels.
|
|
102
|
+
|
|
103
|
+
To customize the Lexical theme (class names applied to nodes), pass the `theme` prop — it's deep-merged with the defaults.
|
|
104
|
+
|
|
105
|
+
## Exports
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// Component
|
|
109
|
+
import { SilkEditor } from "silk-compose";
|
|
110
|
+
import type { SilkEditorProps, SilkFeatures } from "silk-compose";
|
|
111
|
+
|
|
112
|
+
// Nodes (for advanced Lexical integrations)
|
|
113
|
+
import { NoteNode, $createNoteNode, $isNoteNode } from "silk-compose";
|
|
114
|
+
import { ImageNode, $createImageNode, $isImageNode } from "silk-compose";
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Requirements
|
|
118
|
+
|
|
119
|
+
- React 18 or 19
|
|
120
|
+
- Lexical 0.41+
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { DecoratorNode } from 'lexical';
|
|
2
|
+
import { DOMConversionMap } from 'lexical';
|
|
3
|
+
import { DOMExportOutput } from 'lexical';
|
|
4
|
+
import { EditorConfig } from 'lexical';
|
|
5
|
+
import { EditorThemeClasses } from 'lexical';
|
|
6
|
+
import { ElementNode } from 'lexical';
|
|
7
|
+
import { JSX } from 'react/jsx-runtime';
|
|
8
|
+
import { JSX as JSX_2 } from 'react';
|
|
9
|
+
import { LexicalEditor } from 'lexical';
|
|
10
|
+
import { LexicalNode } from 'lexical';
|
|
11
|
+
import { NodeKey } from 'lexical';
|
|
12
|
+
import { RangeSelection } from 'lexical';
|
|
13
|
+
import { SerializedElementNode } from 'lexical';
|
|
14
|
+
import { SerializedLexicalNode } from 'lexical';
|
|
15
|
+
import { Spread } from 'lexical';
|
|
16
|
+
|
|
17
|
+
export declare function $createImageNode(src: string, altText?: string, width?: number, height?: number): ImageNode;
|
|
18
|
+
|
|
19
|
+
export declare function $createNoteNode(noteType?: NoteType): NoteNode;
|
|
20
|
+
|
|
21
|
+
export declare function $isImageNode(node: LexicalNode | null | undefined): node is ImageNode;
|
|
22
|
+
|
|
23
|
+
export declare function $isNoteNode(node: LexicalNode | null | undefined): node is NoteNode;
|
|
24
|
+
|
|
25
|
+
export declare class ImageNode extends DecoratorNode<JSX_2.Element> {
|
|
26
|
+
__src: string;
|
|
27
|
+
__altText: string;
|
|
28
|
+
__width: number | undefined;
|
|
29
|
+
__height: number | undefined;
|
|
30
|
+
static getType(): string;
|
|
31
|
+
static clone(node: ImageNode): ImageNode;
|
|
32
|
+
constructor(src: string, altText: string, width?: number, height?: number, key?: NodeKey);
|
|
33
|
+
createDOM(): HTMLElement;
|
|
34
|
+
updateDOM(): false;
|
|
35
|
+
isInline(): boolean;
|
|
36
|
+
isKeyboardSelectable(): boolean;
|
|
37
|
+
static importJSON(serializedNode: SerializedImageNode): ImageNode;
|
|
38
|
+
exportJSON(): SerializedImageNode;
|
|
39
|
+
exportDOM(): DOMExportOutput;
|
|
40
|
+
static importDOM(): DOMConversionMap | null;
|
|
41
|
+
decorate(_editor: LexicalEditor): JSX_2.Element;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export declare class NoteNode extends ElementNode {
|
|
45
|
+
__noteType: NoteType;
|
|
46
|
+
static getType(): string;
|
|
47
|
+
static clone(node: NoteNode): NoteNode;
|
|
48
|
+
constructor(noteType?: NoteType, key?: NodeKey);
|
|
49
|
+
createDOM(config: EditorConfig): HTMLElement;
|
|
50
|
+
updateDOM(prevNode: NoteNode, dom: HTMLElement): boolean;
|
|
51
|
+
setNoteType(noteType: NoteType): this;
|
|
52
|
+
getNoteType(): NoteType;
|
|
53
|
+
insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): LexicalNode | null;
|
|
54
|
+
collapseAtStart(): boolean;
|
|
55
|
+
static importJSON(serializedNode: SerializedNoteNode): NoteNode;
|
|
56
|
+
exportJSON(): SerializedNoteNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare type NoteType = "info" | "warning" | "error";
|
|
60
|
+
|
|
61
|
+
export declare type SerializedImageNode = Spread<{
|
|
62
|
+
src: string;
|
|
63
|
+
altText: string;
|
|
64
|
+
width?: number;
|
|
65
|
+
height?: number;
|
|
66
|
+
}, SerializedLexicalNode>;
|
|
67
|
+
|
|
68
|
+
declare type SerializedNoteNode = Spread<{
|
|
69
|
+
noteType: NoteType;
|
|
70
|
+
}, SerializedElementNode>;
|
|
71
|
+
|
|
72
|
+
declare function SilkEditor({ features: userFeatures, namespace, className, theme: themeOverrides, editable, onError, onChange, initialEditorState, }: SilkEditorProps): JSX.Element;
|
|
73
|
+
export { SilkEditor }
|
|
74
|
+
export default SilkEditor;
|
|
75
|
+
|
|
76
|
+
export declare interface SilkEditorProps {
|
|
77
|
+
features?: SilkFeatures;
|
|
78
|
+
namespace?: string;
|
|
79
|
+
className?: string;
|
|
80
|
+
theme?: EditorThemeClasses;
|
|
81
|
+
editable?: boolean;
|
|
82
|
+
onError?: (error: Error) => void;
|
|
83
|
+
/** Called on every content change with the serialized editor state as a JSON string. */
|
|
84
|
+
onChange?: (serializedEditorState: string) => void;
|
|
85
|
+
/** JSON string from a previous `onChange` call to restore editor content. */
|
|
86
|
+
initialEditorState?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export declare interface SilkFeatures {
|
|
90
|
+
history?: boolean;
|
|
91
|
+
richText?: boolean;
|
|
92
|
+
dragon?: boolean;
|
|
93
|
+
code?: boolean;
|
|
94
|
+
lists?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { }
|
package/dist/silk.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap";.silk-editor-container{position:relative;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:#1a1a1a}.silk-root{position:relative;min-height:150px;outline:none;background-color:#faf9f7}.silk-content-editable{min-height:150px;outline:none;padding:8px 12px}.silk-content-editable:focus{outline:none}.silk-placeholder{position:absolute;top:8px;left:12px;color:#999;pointer-events:none;-webkit-user-select:none;user-select:none}.silk-paragraph{margin:0 0 8px;line-height:1.6}.silk-text-bold{font-weight:600}.silk-text-italic{font-style:italic}.silk-text-underline{text-decoration:underline}.silk-text-strikethrough{text-decoration:line-through}.silk-text-code{font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;background-color:#0000000a;border:1px solid rgba(0,0,0,.1);padding:2px 5px;border-radius:0;font-size:.875em}.silk-heading-h1{font-family:Space Grotesk,Inter,sans-serif;font-size:1.875em;font-weight:600;margin:0 0 12px;line-height:1.3;letter-spacing:-.02em}.silk-heading-h2{font-family:Space Grotesk,Inter,sans-serif;font-size:1.375em;font-weight:600;margin:0 0 10px;line-height:1.35;letter-spacing:-.01em}.silk-heading-h3{font-family:Space Grotesk,Inter,sans-serif;font-size:1.125em;font-weight:600;margin:0 0 8px;line-height:1.4}.silk-quote{margin:12px 0;padding:20px 24px;border-left:4px solid #ac3521;background-color:#f0efeb;color:#31332c;line-height:1.6}.silk-list-ul{margin:0 0 8px;padding-left:24px;list-style-type:disc}.silk-list-ol{margin:0 0 8px;padding-left:48px;list-style:none;counter-reset:silk-ol}.silk-list-item{margin:2px 0;line-height:1.6}.silk-list-ol>.silk-list-item:not(.silk-list-item-nested){counter-increment:silk-ol;position:relative;margin:14px 0}.silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,decimal);position:absolute;left:-40px;color:#ac3521;font-weight:600;font-feature-settings:"tnum";width:28px;text-align:right}.silk-list-item-nested{list-style-type:none}.silk-list-ul .silk-list-ul{list-style-type:circle}.silk-list-ul .silk-list-ul .silk-list-ul{list-style-type:square}.silk-list-ol .silk-list-ol{counter-reset:silk-ol}.silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,upper-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,lower-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,decimal)}.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,upper-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol>.silk-list-item:not(.silk-list-item-nested):before{content:counter(silk-ol,lower-alpha)}.silk-list-ol .silk-list-ol .silk-list-ol,.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol,.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol,.silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol .silk-list-ol{counter-reset:silk-ol}.silk-code-block{display:block;background-color:#1e1e1e;color:#d4d4d4;font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;font-size:.8125em;line-height:1.6;padding:12px;margin-top:40px;margin-bottom:8px;border-radius:0;border:1px solid #333;border-top:none;overflow-x:auto;-moz-tab-size:2;tab-size:2;white-space:pre;min-height:1.6em}.silk-code-header{display:flex;align-items:center;height:32px;padding:0 8px;border:1px solid #333;border-bottom:none;background-color:#2d2d2d;border-radius:0;-webkit-user-select:none;user-select:none;z-index:1;box-sizing:border-box}.silk-code-language-select{display:flex;align-items:center;gap:4px;background:none;border:none;color:#999;font-size:12px;font-family:inherit;padding:2px 6px;border-radius:0;line-height:1}.silk-code-language-select:hover{background-color:#ffffff14;color:#ccc}.silk-code-language-chevron{font-size:10px;color:#666}.silk-code-language-dropdown{position:absolute;top:100%;left:4px;margin-top:4px;background:#2d2d2d;border:1px solid #444;border-radius:0;padding:4px;min-width:120px;z-index:10;box-shadow:0 4px 12px #0006}.silk-code-language-option{display:block;width:100%;text-align:left;background:none;border:none;color:#ccc;padding:4px 8px;border-radius:0;font-size:12px;cursor:pointer;font-family:inherit}.silk-code-language-option:hover{background:#ffffff1a}.silk-code-language-option--active{color:#fff;background:#ffffff1f}.silk-hr{border:none;margin:28px 0;padding:0;display:flex;align-items:center;gap:16px}.silk-hr:before{content:"";flex:1;height:0;border-top:1px solid #e0ded9}.silk-hr:after{content:"SECTION_START";flex-shrink:0;font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;font-size:10px;letter-spacing:.08em;color:#c5c3be}.silk-image-wrapper{margin:12px 0;line-height:0}.silk-image-container{position:relative;display:inline-block;max-width:100%}.silk-image-container--selected{outline:2px solid #ac3521;outline-offset:2px}.silk-image{max-width:100%;height:auto;display:block;cursor:default}.silk-image-handle{position:absolute;width:10px;height:10px;background:#ac3521;border:1px solid #fbf9f4;z-index:2}.silk-image-handle--nw{top:-5px;left:-5px;cursor:nw-resize}.silk-image-handle--ne{top:-5px;right:-5px;cursor:ne-resize}.silk-image-handle--sw{bottom:-5px;left:-5px;cursor:sw-resize}.silk-image-handle--se{bottom:-5px;right:-5px;cursor:se-resize}.silk-note{margin:8px 0;border-left:4px solid;padding:14px 16px 14px 44px;position:relative;line-height:1.6}.silk-note:before{content:"";position:absolute;left:16px;top:16px;width:18px;height:18px;background-size:contain;background-repeat:no-repeat;background-position:center}.silk-note--info{background-color:#efeeea;border-color:#7d7870}.silk-note--info:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10' fill='%237d7870'/%3E%3Ccircle cx='10' cy='6.5' r='1.5' fill='white'/%3E%3Crect x='8.5' y='9.5' width='3' height='6.5' rx='1' fill='white'/%3E%3C/svg%3E")}.silk-note--warning{background-color:#f5ebe0;border-color:#96734c}.silk-note--warning:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 22 20'%3E%3Cpolygon points='11,0.5 21.5,19.5 0.5,19.5' fill='%23a07850'/%3E%3Ctext x='11' y='16' text-anchor='middle' fill='white' font-family='Inter,sans-serif' font-weight='700' font-size='13'%3E%3F%3C/text%3E%3C/svg%3E")}.silk-note--error{background-color:#fde8e8;border-color:#c44}.silk-note--error:before{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23cc4444'/%3E%3Crect x='8.75' y='4' width='2.5' height='8' rx='1' fill='white'/%3E%3Ccircle cx='10' cy='14.5' r='1.5' fill='white'/%3E%3C/svg%3E")}.silk-note .silk-paragraph{margin:0}.silk-note-icon-trigger{width:24px;height:24px;background:none;border:none;border-radius:0;cursor:pointer;padding:0;opacity:0;transition:opacity .15s}.silk-note-icon-trigger:hover{background-color:#00000014;opacity:1}.silk-note-icon-dropdown{position:absolute;top:28px;left:0;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:4px;min-width:120px;z-index:10;box-shadow:0 4px 12px #0000001a}.silk-note-type-option{display:flex;align-items:center;gap:6px;width:100%;text-align:left;background:none;border:none;color:#333;padding:5px 8px;border-radius:0;font-size:12px;cursor:pointer;font-family:inherit}.silk-note-type-option:hover{background:#f0f0f0}.silk-note-type-option--active{background:#e8e8e8}.silk-note-type-option--info{color:#2563eb}.silk-note-type-option--warning{color:#a16207}.silk-note-type-option--error{color:#dc2626}.silk-slash-anchor{z-index:1000}.silk-slash-menu{background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:4px;min-width:220px;max-width:300px;box-shadow:0 4px 16px #0000001f;z-index:1000;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.silk-slash-menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:6px 10px;border:none;background:none;border-radius:0;cursor:pointer;text-align:left;font-family:inherit}.silk-slash-menu-item:hover,.silk-slash-menu-item--active{background:#faf9f7}.silk-slash-menu-icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;background:#f5f5f5;border:1px solid #eee;border-radius:0;color:#555;flex-shrink:0}.silk-slash-menu-text{display:flex;flex-direction:column;min-width:0}.silk-slash-menu-name{font-size:13px;font-weight:500;color:#1a1a1a}.silk-slash-menu-description{font-size:11px;color:#888;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.silk-link{color:inherit;text-decoration:underline;text-decoration-color:#00000059;text-underline-offset:2px;text-decoration-thickness:1px;cursor:pointer;transition:text-decoration-thickness .1s,text-decoration-color .1s}.silk-link:hover{text-decoration-thickness:2px;text-decoration-color:#0009}.silk-link-dialog-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000001f;z-index:1100;display:flex;align-items:center;justify-content:center;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.silk-link-dialog{background:#fff;border-radius:0;padding:20px;width:360px;box-shadow:0 8px 30px #00000026}.silk-link-dialog-field{margin-bottom:14px}.silk-link-dialog-label{display:block;font-size:12px;font-weight:500;color:#666;margin-bottom:5px}.silk-link-dialog-input{width:100%;padding:8px 10px;border:1px solid #e2e2e2;border-radius:0;font-size:14px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .15s}.silk-link-dialog-input:focus{border-color:#999}.silk-link-dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}.silk-link-dialog-cancel{padding:7px 14px;border:1px solid #e2e2e2;border-radius:0;background:#fff;font-size:13px;font-family:inherit;cursor:pointer;color:#333}.silk-link-dialog-cancel:hover{background:#f5f5f5}.silk-link-dialog-insert{padding:7px 14px;border:none;border-radius:0;background:#1a1a1a;color:#fff;font-size:13px;font-family:inherit;cursor:pointer}.silk-link-dialog-insert:hover{background:#333}.silk-link-dialog-input--readonly{background:#f9f9f9;color:#888}.silk-link-dialog-insert:disabled{opacity:.4;cursor:not-allowed}.silk-toolbar{display:flex;align-items:center;padding:6px 8px;gap:1px;background:#fbf9f4;border-bottom:1px solid rgba(121,124,115,.12);font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;-webkit-user-select:none;user-select:none}.silk-toolbar .silk-ft-color-grid,.silk-toolbar .silk-ft-font-dropdown{bottom:auto;top:100%;margin-bottom:0;margin-top:6px}.silk-floating-toolbar{position:absolute;display:flex;align-items:center;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:3px;box-shadow:0 4px 16px #0000001f;z-index:50;gap:1px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.silk-ft-btn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:none;border-radius:0;cursor:pointer;font-size:13px;color:#444;padding:0;flex-shrink:0}.silk-ft-btn:hover{background:#f0f0f0}.silk-ft-btn--active{background:#e8e8e8;color:#1a1a1a}.silk-ft-btn--sm{width:22px;height:22px;font-size:14px}.silk-ft-btn--sm:disabled{opacity:.3;cursor:default}.silk-ft-btn--mono{font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;font-size:11px}.silk-ft-sep{width:1px;height:18px;background:#e2e2e2;margin:0 3px;flex-shrink:0}.silk-ft-font-size{display:flex;align-items:center;gap:2px}.silk-ft-font-size-value{font-size:12px;color:#444;min-width:20px;text-align:center;-webkit-user-select:none;user-select:none}.silk-ft-color-wrap{position:relative}.silk-ft-btn--color{font-size:14px;font-weight:600}.silk-ft-color-indicator{border-bottom:2.5px solid;padding-bottom:1px;line-height:1}.silk-ft-color-grid{position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:6px;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:6px;display:grid;grid-template-columns:repeat(6,1fr);gap:1px;box-shadow:0 4px 16px #0000001f;z-index:10}.silk-ft-color-swatch{width:22px;height:22px;border-radius:0;border:2px solid transparent;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;color:#999}.silk-ft-color-swatch:hover{border-color:#ccc}.silk-ft-color-swatch--active{border-color:#666}.silk-ft-font-wrap{position:relative}.silk-ft-font-dropdown{position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:6px;background:#fff;border:1px solid #e2e2e2;border-radius:0;padding:4px;min-width:160px;box-shadow:0 4px 16px #0000001f;z-index:10}.silk-ft-font-option{display:block;width:100%;text-align:left;background:none;border:none;padding:5px 8px;border-radius:0;cursor:pointer;font-size:12px;color:#444}.silk-ft-font-option:hover{background:#f0f0f0}.silk-ft-font-option--active{background:#e8e8e8;color:#1a1a1a}
|