supamde 0.0.0-stage → 0.1.0

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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1225 -2
  3. package/dist/commands/block.d.ts +20 -0
  4. package/dist/commands/history.d.ts +6 -0
  5. package/dist/commands/indent.d.ts +18 -0
  6. package/dist/commands/inline.d.ts +9 -0
  7. package/dist/commands/keymap.d.ts +8 -0
  8. package/dist/commands/link-image.d.ts +13 -0
  9. package/dist/commands/list.d.ts +20 -0
  10. package/dist/commands/prefixes.d.ts +29 -0
  11. package/dist/commands/queries.d.ts +25 -0
  12. package/dist/commands/table.d.ts +7 -0
  13. package/dist/commands/types.d.ts +17 -0
  14. package/dist/editor/extensions.d.ts +14 -0
  15. package/dist/editor/highlight.d.ts +11 -0
  16. package/dist/editor/link-click.d.ts +53 -0
  17. package/dist/editor/link-hover-cursor.d.ts +44 -0
  18. package/dist/editor/math.d.ts +8 -0
  19. package/dist/editor/setup.d.ts +26 -0
  20. package/dist/editor/theme.d.ts +7 -0
  21. package/dist/editor/tokens.d.ts +16 -0
  22. package/dist/editor/value.d.ts +10 -0
  23. package/dist/features/autosave.d.ts +31 -0
  24. package/dist/features/image-upload.d.ts +73 -0
  25. package/dist/features/storage.d.ts +30 -0
  26. package/dist/features/upload-dom.d.ts +20 -0
  27. package/dist/features/upload-placeholder.d.ts +35 -0
  28. package/dist/features/word-count.d.ts +5 -0
  29. package/dist/i18n/de.d.ts +3 -0
  30. package/dist/i18n/en.d.ts +3 -0
  31. package/dist/i18n/facet.d.ts +9 -0
  32. package/dist/i18n/translator.d.ts +17 -0
  33. package/dist/i18n/types.d.ts +82 -0
  34. package/dist/index.d.ts +133 -0
  35. package/dist/livepreview/index.d.ts +22 -0
  36. package/dist/livepreview/plugin.d.ts +3 -0
  37. package/dist/livepreview/ranges.d.ts +62 -0
  38. package/dist/markdown/katex-marked.d.ts +20 -0
  39. package/dist/markdown/parse.d.ts +27 -0
  40. package/dist/markdown/sanitize.d.ts +6 -0
  41. package/dist/options.d.ts +80 -0
  42. package/dist/supamde.css +2 -0
  43. package/dist/supamde.mjs +3556 -0
  44. package/dist/supamde.mjs.map +1 -0
  45. package/dist/ui/actions.d.ts +51 -0
  46. package/dist/ui/fullscreen.d.ts +16 -0
  47. package/dist/ui/icons.d.ts +4 -0
  48. package/dist/ui/inject-styles.d.ts +13 -0
  49. package/dist/ui/preview.d.ts +27 -0
  50. package/dist/ui/shortcut-label.d.ts +24 -0
  51. package/dist/ui/statusbar.d.ts +28 -0
  52. package/dist/ui/toolbar-config.d.ts +36 -0
  53. package/dist/ui/toolbar.d.ts +16 -0
  54. package/dist/ui/update-listener.d.ts +15 -0
  55. package/dist/util/text-format.d.ts +11 -0
  56. package/dist/utils/html.d.ts +2 -0
  57. package/dist/utils/text.d.ts +41 -0
  58. package/dist/version.d.ts +2 -0
  59. package/package.json +96 -4
@@ -0,0 +1,20 @@
1
+ import type { SupaCommand } from './types';
2
+ type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
3
+ /** Setzt ein absolutes Heading-Level; erneutes Setzen desselben Levels entfernt es. */
4
+ export declare function setHeading(level: HeadingLevel): SupaCommand;
5
+ /** Verkleinert die Überschrift (mehr #, Grenze 6); aus Klartext wird H1. */
6
+ export declare const headingSmaller: SupaCommand;
7
+ /** Vergrößert die Überschrift (weniger #); H1 → Klartext, bleibt bei Klartext. */
8
+ export declare const headingBigger: SupaCommand;
9
+ /** Blockzitat (`> `) je Zeile ein-/ausschalten. */
10
+ export declare const quote: SupaCommand;
11
+ /**
12
+ * Umschließt die Selektion mit ```-Fences (Codeblock) bzw. entfernt sie, wenn die
13
+ * Selektion bereits exakt von einem `` ```-Fence-Paar `` umschlossen ist (Toggle).
14
+ */
15
+ export declare const codeBlock: SupaCommand;
16
+ /** Fügt eine horizontale Trennlinie (`\n---\n`) an der Cursorzeile ein. */
17
+ export declare const horizontalRule: SupaCommand;
18
+ /** Entfernt erkannte Block-/Listen-Präfixe (#, >, -, 1., - [ ]) der selektierten Zeilen. */
19
+ export declare const cleanBlock: SupaCommand;
20
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Undo/Redo als dünne Re-Exports über `@codemirror/commands`. Der zugehörige
3
+ * History-State wird über die `history()`-Extension bereitgestellt (siehe
4
+ * `editor/extensions.ts`). Der CM6-`Command`-Typ ist zu `SupaCommand` kompatibel.
5
+ */
6
+ export { undo, redo } from '@codemirror/commands';
@@ -0,0 +1,18 @@
1
+ import type { SupaCommand } from './types';
2
+ /**
3
+ * Rückt alle von der Hauptselektion berührten Zeilen um ein `indentUnit` ein
4
+ * (Leerzeichen, Default 2). Die Einrückung wird IMMER am Zeilenanfang eingefügt,
5
+ * unabhängig davon, wo der Cursor in der Zeile steht — anders als der CM6-
6
+ * Standard `indentWithTab`, der mitten im Text ein Tab-Zeichen setzen würde.
7
+ *
8
+ * In Markdown ergibt sich die Listen-Verschachtelung genau aus dieser
9
+ * Leerzeichen-Einrückung, deshalb braucht es keinen Listen-Sonderfall.
10
+ */
11
+ export declare const indentLines: SupaCommand;
12
+ /**
13
+ * Rückt alle von der Hauptselektion berührten Zeilen um bis zu ein `indentUnit`
14
+ * aus. Zeilen ohne führenden Whitespace bleiben unverändert — der Command gibt
15
+ * dennoch `true` zurück, damit `Shift-Tab` die Taste konsumiert und den Fokus
16
+ * nicht aus dem Editor bewegt.
17
+ */
18
+ export declare const dedentLines: SupaCommand;
@@ -0,0 +1,9 @@
1
+ import type { SupaCommand } from './types';
2
+ /** Fettdruck (`**…**`) ein-/ausschalten. */
3
+ export declare const bold: SupaCommand;
4
+ /** Kursiv (`*…*`) ein-/ausschalten. */
5
+ export declare const italic: SupaCommand;
6
+ /** Durchstreichen (`~~…~~`) ein-/ausschalten. */
7
+ export declare const strikethrough: SupaCommand;
8
+ /** Inline-Code (`` `…` ``) ein-/ausschalten. */
9
+ export declare const inlineCode: SupaCommand;
@@ -0,0 +1,8 @@
1
+ import type { KeyBinding } from '@codemirror/view';
2
+ /**
3
+ * Default-Tastenkürzel aus easyMDE (Cmd → CM6 `Mod`, plattformgerecht). Der Großteil
4
+ * wird aus `BUILTIN_ACTIONS` (ui/actions.ts) abgeleitet — das ist die Single Source of
5
+ * Truth für Kürzel, die zu einem Toolbar-Button gehören. Nur Bindungen ohne Button
6
+ * (quote-Zweitkürzel, Sternchen-Liste, Listen-Fortsetzung) bleiben hier explizit.
7
+ */
8
+ export declare const supaKeymap: KeyBinding[];
@@ -0,0 +1,13 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ import type { SupaCommand } from './types';
3
+ /** Fügt `[text](url)` ein; ohne `text` wird die Selektion verwendet. */
4
+ export declare function insertLink(url: string, text?: string): SupaCommand;
5
+ /** Fügt `![alt](url)` ein; ohne `altText` wird die Selektion verwendet. */
6
+ export declare function insertImage(url: string, altText?: string): SupaCommand;
7
+ /**
8
+ * Toolbar-/Shortcut-Wrapper: fragt die URL per `window.prompt` ab und ruft den
9
+ * reinen `insertLink`. Der Seiteneffekt (Prompt) ist bewusst hier isoliert.
10
+ */
11
+ export declare function drawLink(view: EditorView): boolean;
12
+ /** Wie `drawLink`, aber für ein Bild (`insertImage`). */
13
+ export declare function drawImage(view: EditorView): boolean;
@@ -0,0 +1,20 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ import type { SupaCommand } from './types';
3
+ /** Ungeordnete Liste mit Spiegelstrich (`- `) ein-/ausschalten (Default, Cmd+L). */
4
+ export declare const unorderedList: SupaCommand;
5
+ /** Ungeordnete Liste mit Sternchen (`* `) ein-/ausschalten (Shift+Alt+Cmd+L). */
6
+ export declare const unorderedListStar: SupaCommand;
7
+ /** Checkliste (`- [ ] `) je Zeile ein-/ausschalten. */
8
+ export declare const checkList: SupaCommand;
9
+ /** Geordnete Liste (`1. `, `2. `, …) fortlaufend setzen bzw. entfernen. */
10
+ export declare const orderedList: SupaCommand;
11
+ /**
12
+ * Enter-Handler für Listen: setzt Einrückung und Präfix in der neuen Zeile fort;
13
+ * ist die aktuelle Listenzeile leer (nur Einrückung + Präfix), wird die Liste
14
+ * beendet — die Zeile wird geleert, der Cursor bleibt an ihrem Anfang stehen.
15
+ * `false`, wenn die Cursorzeile keine Liste ist (Standard-Enter greift dann).
16
+ *
17
+ * Die Präfix-Erkennung teilt sich mit `cleanBlock` die zentrale `stripLinePrefix`
18
+ * (Task 1); nur die Fortsetzungs-Logik (Inkrement) ist listenspezifisch.
19
+ */
20
+ export declare function continueList(view: EditorView): boolean;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Zentrale Definition aller markdown-Zeilen-Präfixe, die von mehreren Command-
3
+ * Modulen erkannt/entfernt werden. Eine einzige Quelle verhindert, dass sich
4
+ * `block.ts`, `list.ts` und `cleanBlock` in ihren Regexes auseinanderentwickeln.
5
+ * Reihenfolge ist bedeutsam: spezifischere Muster (Checkliste) VOR allgemeineren
6
+ * (Aufzählungsstrich) prüfen.
7
+ */
8
+ export declare const LINE_PREFIXES: readonly RegExp[];
9
+ /**
10
+ * Trennt ein erkanntes Zeilen-Präfix vom Rest der Zeile. Liefert `null`, wenn die
11
+ * Zeile mit keinem bekannten Präfix beginnt.
12
+ *
13
+ * Führender Whitespace (verschachtelte Listen) wird übersprungen und separat als
14
+ * `indent` zurückgegeben — `prefix` bleibt dadurch der reine Marker. Nur so kann
15
+ * `continueList` die Einrückung einer Unterliste fortführen, während `cleanBlock`
16
+ * unverändert ab `line.from + indent.length` genau den Marker entfernt.
17
+ */
18
+ export declare function stripLinePrefix(text: string): {
19
+ indent: string;
20
+ prefix: string;
21
+ rest: string;
22
+ } | null;
23
+ /**
24
+ * Zählt den zu entfernenden führenden Whitespace einer Zeile: bis zu `unit`
25
+ * Leerzeichen, weniger wenn weniger vorhanden sind. Ein führendes Tab-Zeichen
26
+ * zählt als eine vollständige Einrückstufe und wird als Ganzes entfernt.
27
+ * `0`, wenn die Zeile nicht mit Whitespace beginnt.
28
+ */
29
+ export declare function dedentWidth(text: string, unit: number): number;
@@ -0,0 +1,25 @@
1
+ import type { EditorState } from '@codemirror/state';
2
+ import type { SyntaxNode } from '@lezer/common';
3
+ /**
4
+ * Die gemeinsame Syntaxbaum-Traversierung: liefert den Knoten namens `nodeName`,
5
+ * der die Hauptselektion umschließt (oder `null`). EINZIGE Quelle dieser
6
+ * Traversierung im Projekt — `inline.ts` setzt hierauf auf (es braucht die Node
7
+ * zum Entfernen der Marker), die Bool-Queries sind `!== null` darüber.
8
+ */
9
+ export declare function resolveEnclosingNode(state: EditorState, nodeName: string): SyntaxNode | null;
10
+ export declare function isBold(state: EditorState): boolean;
11
+ export declare function isItalic(state: EditorState): boolean;
12
+ export declare function isStrikethrough(state: EditorState): boolean;
13
+ export declare function isInlineCode(state: EditorState): boolean;
14
+ /**
15
+ * Aktuelle Heading-Ebene der ersten Selektionszeile (0 = keine). EINZIGE
16
+ * Heading-Regex im Projekt — `block.ts` (`setHeading`, `headingSmaller`,
17
+ * `headingBigger`) ruft diese Funktion statt einer eigenen `currentLevel`.
18
+ * `match[0].length - 1` = #-Zeichen ohne das trennende Leerzeichen (identisch
19
+ * zur bisherigen `currentLevel`-Semantik).
20
+ */
21
+ export declare function activeHeadingLevel(state: EditorState): number;
22
+ export declare function isQuote(state: EditorState): boolean;
23
+ export declare function isInUnorderedList(state: EditorState): boolean;
24
+ export declare function isInOrderedList(state: EditorState): boolean;
25
+ export declare function isInCheckList(state: EditorState): boolean;
@@ -0,0 +1,7 @@
1
+ import type { SupaCommand } from './types';
2
+ /**
3
+ * Fügt ein GFM-Tabellengerüst (Header, Trennzeile, eine Datenzeile) am Cursor ein.
4
+ * Die Spaltenköpfe kommen aus dem Translator im State — ohne Provider englisch,
5
+ * damit der Command auch ohne SupaMDE-Instanz läuft.
6
+ */
7
+ export declare const table: SupaCommand;
@@ -0,0 +1,17 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ /**
3
+ * Ein SupaMDE-Command ist eine reine CM6-Command-Funktion: liest `view.state`,
4
+ * dispatcht bei Bedarf eine Transaktion und liefert `true`, wenn das Doc
5
+ * verändert wurde — sonst `false` (No-op). Testbar ohne DOM/Toolbar.
6
+ */
7
+ export type SupaCommand = (view: EditorView) => boolean;
8
+ /**
9
+ * Eine einzelne Doc-Änderung im CM6-`changes`-Format. Zentral definiert, damit
10
+ * die Command-Module (`text.ts`, `block.ts`, `list.ts`, `inline.ts`) nicht
11
+ * jeweils denselben Inline-Typ wiederholen.
12
+ */
13
+ export interface DocChange {
14
+ from: number;
15
+ to: number;
16
+ insert: string;
17
+ }
@@ -0,0 +1,14 @@
1
+ import { type Extension } from '@codemirror/state';
2
+ import type { ResolvedOptions } from '../options';
3
+ import { type UpdateSink } from '../ui/update-listener';
4
+ /**
5
+ * Übersetzt normalisierte Optionen in die CM6-Extension-Liste. Jede easyMDE-
6
+ * Option wird hier zur echten Extension (kein Flag-Layer). `autofocus` ist
7
+ * bewusst nicht enthalten — es ist eine View-Konstruktor-Option, keine Extension.
8
+ * GFM-Extensions sind aktiviert, um GitHub Flavored Markdown (Strikethrough, Tabellen, etc.) zu parsen.
9
+ * `Math` erkennt `$…$`/`$$…$$` als eigene Knoten, damit LaTeX-Inhalt nicht als
10
+ * Markdown (Fett, Links, …) interpretiert wird.
11
+ * `livePreviewCompartment` hält die Live-Modus-Extension — im Source-Modus leer,
12
+ * per `reconfigure` zur Laufzeit umschaltbar.
13
+ */
14
+ export declare function buildExtensions(resolved: ResolvedOptions, sink?: UpdateSink, extraExtensions?: Extension[]): Extension[];
@@ -0,0 +1,11 @@
1
+ import { HighlightStyle } from '@codemirror/language';
2
+ import type { Extension } from '@codemirror/state';
3
+ /**
4
+ * Kern-Formatierungen für die easyMDE-„Quasi-WYSIWYG"-Parität: der Markdown-
5
+ * Quelltext bleibt sichtbar, wird aber live formatiert. Farben stammen aus
6
+ * `tokens.ts`; exakte px/Farben sind bewusst NICHT Teil von M1 (Feinschliff
7
+ * folgt in M3/M6, dann nur an der Token-Quelle).
8
+ */
9
+ export declare const supaHighlightStyle: HighlightStyle;
10
+ /** Fertige Extension: Syntax-Highlighting mit dem SupaMDE-Stil. */
11
+ export declare const highlightExtension: Extension;
@@ -0,0 +1,53 @@
1
+ import type { EditorState, Extension } from '@codemirror/state';
2
+ import { EditorView } from '@codemirror/view';
3
+ /**
4
+ * Normalisiert den Text eines nackten `URL`-Knotens (GFM-Autoerkennung ohne
5
+ * Link/Autolink-Elternknoten) zu einer vollständigen URL mit Schema.
6
+ *
7
+ * - Steht bereits ein Schema im Text (`http://`, `https://`, `mailto:`),
8
+ * bleibt der Text unverändert (keine Verdopplung).
9
+ * - `www.example.com` bekommt das Schema `https://` vorangestellt.
10
+ * - Eine nackte E-Mail-Adresse (`@` enthalten, kein Schema) bekommt das
11
+ * Präfix `mailto:` vorangestellt — ABER NUR, wenn `precedingChar` (das
12
+ * Dokumentzeichen unmittelbar vor dem Knoten) keine Fortsetzung einer
13
+ * größeren URL anzeigt (siehe `URL_CONTINUATION_CHARS`). Die Schema-Prüfung
14
+ * läuft dabei ZUERST, damit z.B. `https://user@host/pfad` (URL mit
15
+ * Benutzerteil, hier bereits vollständig im Knoten enthalten) nicht
16
+ * fälschlich als E-Mail behandelt wird.
17
+ * - Erkennt die Kontextprüfung eine Fortsetzung, liefert die Funktion `null`
18
+ * (konservativ: lieber kein Link öffnen als versehentlich das Mailprogramm).
19
+ * - Alles andere (z.B. bereits vollständige http/https-URLs) bleibt unverändert.
20
+ *
21
+ * Reine Funktion — ohne DOM/Editor-State testbar. `precedingChar` ist
22
+ * `undefined`, wenn der Knoten am Dokumentanfang steht.
23
+ */
24
+ export declare function normalizeBareUrl(text: string, precedingChar: string | undefined): string | null;
25
+ /**
26
+ * Liefert die Ziel-URL des Links an `pos`, oder `null`.
27
+ *
28
+ * Deckt drei Fälle ab: Markdown-Links (`[Text](url)`), Autolinks (`<url>`)
29
+ * und nackte URLs (GFM-Autoerkennung, `URL`-Knoten direkt unter `Paragraph`).
30
+ * Bei nackten URLs wird der Text normalisiert (`normalizeBareUrl`), bevor die
31
+ * Schema-Prüfung läuft.
32
+ *
33
+ * `null` bei: keinem Link an der Position, fehlendem URL-Knoten, oder einem
34
+ * normalisierten Schema außerhalb der Allowlist. Reine Funktion — ohne DOM
35
+ * testbar.
36
+ */
37
+ export declare function linkUrlAt(state: EditorState, pos: number): string | null;
38
+ /**
39
+ * Der mousedown-Handler als benannte Funktion: liefert `true`, wenn er den Klick
40
+ * behandelt hat (Link geöffnet), sonst `false`. Separat exportiert, damit sich
41
+ * die Entscheidungslogik ohne DOM-Layout testen lässt — `event.defaultPrevented`
42
+ * taugt dafür nicht, weil CodeMirror bei simuliertem mousedown selbst
43
+ * `preventDefault()` aufruft (auch mit leerer Extension-Liste).
44
+ */
45
+ export declare function handleLinkMousedown(event: MouseEvent, view: EditorView): boolean;
46
+ /**
47
+ * Cmd/Ctrl+Klick öffnet den Link unter dem Zeiger in einem neuen Tab.
48
+ *
49
+ * Bewusst MODUSUNABHÄNGIG (fest in der Extension-Liste, außerhalb des
50
+ * Live-Preview-Compartments) — es gibt keinen Grund, nützliche Navigation an den
51
+ * Darstellungsmodus zu koppeln.
52
+ */
53
+ export declare const linkClickExtension: Extension;
@@ -0,0 +1,44 @@
1
+ import { EditorView, ViewPlugin, type PluginValue, type ViewUpdate } from '@codemirror/view';
2
+ /** CSS-Klasse auf `view.contentDOM`, die den Mauszeiger zur Klickhand macht. */
3
+ export declare const LINK_HOVER_CLASS = "supamde-link-hover";
4
+ /**
5
+ * Hält den Zustand "Zeiger steht auf einem klickbaren Link UND Modifier ist
6
+ * gedrückt" und spiegelt ihn als CSS-Klasse auf `view.contentDOM` — wie in
7
+ * VS Code soll der Mauszeiger dann zur Klickhand werden.
8
+ *
9
+ * Die Klasse muss laut Vorgabe SOFORT bei Tastendruck erscheinen (nicht erst
10
+ * bei der nächsten Mausbewegung) und sofort beim Loslassen verschwinden.
11
+ * Deshalb wird zusätzlich zu `mousemove` die zuletzt bekannte Mausposition
12
+ * vorgehalten und bei `keydown`/`keyup` sofort neu bewertet.
13
+ *
14
+ * `keydown`/`keyup` hängen bewusst am `window` und nicht am `view.contentDOM`:
15
+ * Modifier-Tasten sollen auch dann erkannt werden, wenn der Editor gerade
16
+ * nicht fokussiert ist (z.B. Fokus in einem Toolbar-Button), weil der Nutzer
17
+ * die Taste schon VOR dem Zurückbewegen der Maus in den Editor drücken kann.
18
+ * Ein Editor-lokaler Listener würde dieses Timing verpassen.
19
+ */
20
+ declare class LinkHoverCursorPlugin implements PluginValue {
21
+ private view;
22
+ private lastX;
23
+ private lastY;
24
+ private hasMousePosition;
25
+ private modifierDown;
26
+ private readonly onMouseMove;
27
+ private readonly onKeyDown;
28
+ private readonly onKeyUp;
29
+ private readonly onBlur;
30
+ constructor(view: EditorView);
31
+ update(_update: ViewUpdate): void;
32
+ destroy(): void;
33
+ private updateState;
34
+ private pointerIsOverLink;
35
+ }
36
+ /**
37
+ * Zeigt beim Hover mit gedrücktem Cmd/Ctrl-Modifier über einem klickbaren
38
+ * Link den Mauszeiger als Klickhand (`cursor: pointer`) — wie in VS Code.
39
+ * Die CSS-Regel dazu steht in `theme.ts` (`.${LINK_HOVER_CLASS}`).
40
+ *
41
+ * Modusunabhängig einzuhängen, analog zu `linkClickExtension`.
42
+ */
43
+ export declare const linkHoverCursorExtension: ViewPlugin<LinkHoverCursorPlugin, undefined>;
44
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { MarkdownConfig } from '@lezer/markdown';
2
+ /**
3
+ * Lezer-Markdown-Extension, die LaTeX-Bereiche (`$…$`, `$$…$$`) als eigene,
4
+ * in sich geschlossene Knoten erkennt. Ohne dies parst der Standard-Parser
5
+ * den Inhalt als normalen Markdown-Fließtext, wodurch z. B. `**…**` oder
6
+ * `[…]()` innerhalb einer Formel fälschlich als Fett/Link interpretiert wird.
7
+ */
8
+ export declare const Math: MarkdownConfig;
@@ -0,0 +1,26 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ import type { Extension } from '@codemirror/state';
3
+ import type { SupaMDEOptions } from '../options';
4
+ import { type ResolvedOptions } from '../options';
5
+ import type { UpdateSink } from '../ui/update-listener';
6
+ /** Steuerungs-Handle über den erzeugten Editor inkl. Rückbau. */
7
+ export interface EditorHandle {
8
+ /** Die zugrunde liegende CM6-EditorView. */
9
+ view: EditorView;
10
+ /** Baut den Editor zurück und liefert die wiederhergestellte Textarea. */
11
+ toTextArea(): HTMLTextAreaElement;
12
+ /** Schreibt den aktuellen Doc-Inhalt sofort in die Textarea. */
13
+ forceSync(): void;
14
+ /**
15
+ * Die normalisierten Optionen dieser Instanz. Herausgegeben, damit die Fassade
16
+ * (`src/index.ts`) sie NICHT erneut auflösen muss — `resolveOptions` läuft pro
17
+ * Instanz genau einmal, Warnungen erscheinen daher genau einmal.
18
+ */
19
+ resolved: ResolvedOptions;
20
+ }
21
+ /**
22
+ * CM6-Nachbau von `fromTextArea`: erzeugt eine EditorView aus dem Textarea-
23
+ * Inhalt, fügt sie davor ein, versteckt die Textarea und hält den Form-Submit-
24
+ * Wert synchron. Der Textarea-Wert ist die Quelle NUR bei Konstruktion.
25
+ */
26
+ export declare function editorFromTextArea(options: SupaMDEOptions, sink?: UpdateSink, extraExtensions?: Extension[]): EditorHandle;
@@ -0,0 +1,7 @@
1
+ import type { Extension } from '@codemirror/state';
2
+ /**
3
+ * Basis-Erscheinungsbild des SupaMDE-Editors (Container, Font, Padding).
4
+ * Ersetzt das CM5-'easymde'-Theme. Farben/Font stammen aus `tokens.ts`;
5
+ * Feinschliff (Farben/Abstände) folgt in M3/M6, dann nur an der Token-Quelle.
6
+ */
7
+ export declare const supaTheme: Extension;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Geteilte Style-Tokens für Highlight (`highlight.ts`) und Theme (`theme.ts`).
3
+ * EINE Quelle für Farben und Font-Stack, damit der Feinschliff in M3/M6 nur hier
4
+ * ansetzt. Exakte Werte sind bewusst vorläufig (easyMDE-Look, keine px-Parität).
5
+ */
6
+ /** Kern-Farbwerte des SupaMDE-Basis-Looks. */
7
+ export declare const colors: {
8
+ /** Blockquote-Text (gedämpftes Grau). */
9
+ readonly quote: "#6a737d";
10
+ /** Links (GitHub-Blau). */
11
+ readonly link: "#0366d6";
12
+ /** Container-Rahmen. */
13
+ readonly border: "#ddd";
14
+ };
15
+ /** System-Font-Stack (kein Web-Font-Load in M1). */
16
+ export declare const fontStack = "system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif";
@@ -0,0 +1,10 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ /**
3
+ * Headless value-Logik über einer `EditorView`. Bewusst aus der `SupaMDE`-
4
+ * Fassade herausgezogen: so ist sie ohne Textarea/Konstruktor testbar (Spec
5
+ * Abschnitt 3) und in M2 (Commands) wiederverwendbar.
6
+ */
7
+ /** Liefert den aktuellen Editor-Inhalt als String. */
8
+ export declare function readValue(view: EditorView): string;
9
+ /** Ersetzt den gesamten Editor-Inhalt durch `val`. */
10
+ export declare function writeValue(view: EditorView, val: string): void;
@@ -0,0 +1,31 @@
1
+ import { type SupaStorage } from './storage';
2
+ /** Konfiguration des Autosave. */
3
+ export interface AutosaveOptions {
4
+ /** Aktiviert Autosave. Default: false. */
5
+ enabled?: boolean;
6
+ /** Pflicht. Identifiziert das Dokument im Speicher. */
7
+ key: string;
8
+ /** Debounce nach der letzten Änderung, in ms. Default: 1000. */
9
+ delay?: number;
10
+ /** Eigener Speicher. Default: localStorage unter `supamde:<key>`. */
11
+ storage?: SupaStorage;
12
+ /** Wird gerufen, wenn beim Start ein Entwurf geladen wurde. */
13
+ onRestore?: (saved: string) => void;
14
+ }
15
+ /** Die Hooks, über die Autosave das Dokument erreicht — ohne CM6-Abhängigkeit. */
16
+ export interface AutosaveHooks {
17
+ getValue(): string;
18
+ setValue(value: string): void;
19
+ onSaved(time: Date): void;
20
+ }
21
+ /** Das Steuerungs-Handle über eine Autosave-Instanz. */
22
+ export interface Autosave {
23
+ start(): Promise<void>;
24
+ schedule(): void;
25
+ clear(): Promise<void>;
26
+ stop(): void;
27
+ isActive(): boolean;
28
+ }
29
+ /** Default-Debounce in Millisekunden. */
30
+ export declare const DEFAULT_AUTOSAVE_DELAY = 1000;
31
+ export declare function createAutosave(options: AutosaveOptions, hooks: AutosaveHooks): Autosave;
@@ -0,0 +1,73 @@
1
+ import type { EditorView } from '@codemirror/view';
2
+ import { type Translator } from '../i18n/translator';
3
+ /**
4
+ * Ein Upload-Fehler — strukturiert statt vorformatiert, damit der Host selbst
5
+ * darstellen und übersetzen kann.
6
+ */
7
+ export interface UploadError {
8
+ kind: 'too-large' | 'type-not-allowed' | 'upload-failed';
9
+ file: File;
10
+ /** Der ursprüngliche Fehler aus `upload()`, bei `kind === 'upload-failed'`. */
11
+ cause?: unknown;
12
+ }
13
+ /** Konfiguration des Bild-Uploads. */
14
+ export interface UploadImageOptions {
15
+ /** Aktiviert den Bild-Upload. Default: false. */
16
+ enabled?: boolean;
17
+ /** Pflicht. Lädt die Datei hoch und liefert die URL; wirft bei Fehler. */
18
+ upload: (file: File) => Promise<string>;
19
+ /** Maximale Dateigröße in Bytes. Default: 2 MB. */
20
+ maxSize?: number;
21
+ /** Erlaubte MIME-Typen. */
22
+ accept?: string[];
23
+ /** Wird bei jedem Fehler gerufen. Default: keiner (nur Statusbar). */
24
+ onError?: (error: UploadError) => void;
25
+ }
26
+ /** Das Steuerungs-Handle über den Uploader. */
27
+ export interface ImageUploader {
28
+ uploadFiles(files: FileList | File[]): void;
29
+ /** Die erlaubten MIME-Typen — für das `accept`-Attribut des File-Inputs. */
30
+ accept(): string[];
31
+ isActive(): boolean;
32
+ /**
33
+ * Räumt den Rückfall-Timer ab. Beim Rückbau des Editors zu rufen — sonst
34
+ * feuerte er nach `toTextArea()` gegen eine bereits zerstörte Statusbar.
35
+ * Laufende Uploads werden NICHT abgebrochen: Ihre `upload()`-Promise gehört
36
+ * dem Host, und ihr Ergebnis findet über den dann leeren Platzhalter-Bereich
37
+ * ohnehin kein Ziel mehr.
38
+ */
39
+ destroy(): void;
40
+ }
41
+ /** Default-Obergrenze: 2 MB. */
42
+ export declare const DEFAULT_UPLOAD_MAX_SIZE: number;
43
+ /** Default-Liste erlaubter MIME-Typen. */
44
+ export declare const DEFAULT_UPLOAD_ACCEPT: string[];
45
+ /** Anzeigedauer der Erfolgsmeldung, bevor auf `statusInit` zurückgefallen wird. */
46
+ export declare const STATUS_DONE_MS = 2000;
47
+ /** Anzeigedauer der Fehlermeldung. Länger, weil sie gelesen werden muss. */
48
+ export declare const STATUS_ERROR_MS = 6000;
49
+ /**
50
+ * Die Markdown-Textform eines fertigen Bildes.
51
+ *
52
+ * Bewusst NICHT über `insertImage()` aus `commands/link-image.ts`: Jenes fügt an
53
+ * der aktuellen SELEKTION ein — genau der Weg, den die Positionsregel verbietet.
54
+ * Der Upload muss an der gemappten Platzhalter-Position ersetzen, nicht an der
55
+ * Cursorposition. Geteilt wird deshalb nur die Textform, hier als eine Zeile.
56
+ */
57
+ export declare function imageMarkdown(alt: string, url: string): string;
58
+ /**
59
+ * Prüft eine Datei. `null` heißt: in Ordnung. Größe zuerst — eine zu große
60
+ * Datei ist auch dann zu groß, wenn ihr Typ zusätzlich nicht passt, und die
61
+ * Größe ist die häufigere Ursache.
62
+ */
63
+ export declare function validateFile(file: File, opts: {
64
+ maxSize: number;
65
+ accept: string[];
66
+ }): UploadError['kind'] | null;
67
+ /**
68
+ * Erzeugt den Uploader. `translator` liefert Platzhalter und Statusmeldungen
69
+ * (`upload.*`-Schlüssel); ohne Angabe englisch.
70
+ */
71
+ export declare function createImageUploader(view: EditorView, options: UploadImageOptions, hooks: {
72
+ setStatus(text: string): void;
73
+ }, translator?: Translator): ImageUploader;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Der Speicher-Vertrag des Autosave. Async-fähig, damit ein Server-Backend oder
3
+ * IndexedDB ohne Zusatzschicht passt — die eingebaute localStorage-Variante ist
4
+ * synchron, SupaMDE behandelt beide Fälle über `await` gleich.
5
+ */
6
+ export interface SupaStorage {
7
+ load(key: string): string | null | Promise<string | null>;
8
+ save(key: string, value: string): void | Promise<void>;
9
+ clear(key: string): void | Promise<void>;
10
+ }
11
+ /** Default-Prefix im localStorage: Einträge liegen unter `supamde:<key>`. */
12
+ export declare const STORAGE_PREFIX = "supamde:";
13
+ /** localStorage-Implementierung. Synchron; wirft bei Quota/Private Mode durch. */
14
+ export declare function createLocalStorage(prefix?: string): SupaStorage;
15
+ /**
16
+ * In-Memory-Speicher. Primär als Testdouble gedacht, aber auch als bewusste
17
+ * Host-Wahl brauchbar ("nur für diese Sitzung merken").
18
+ */
19
+ export declare function createMemoryStorage(): SupaStorage;
20
+ /**
21
+ * Probe-Schreibvorgang: schreibt einen Wegwerf-Eintrag, liest ihn zurück und
22
+ * räumt ihn wieder ab. Fängt JEDEN Wurf — im Private Mode wirft schon der
23
+ * Zugriff auf `localStorage`, nicht erst `setItem`.
24
+ *
25
+ * Wichtig: Die Verfügbarkeit ist nur die halbe Miete. Der spätere `save()` mit
26
+ * echtem Inhalt läuft trotzdem in `try/catch` (siehe `autosave.ts`) — easyMDE
27
+ * prüfte nur mit einem Ein-Byte-Probeschreiben und ließ die echte
28
+ * Quota-Exception beim Speichern großer Inhalte ungefangen durchschlagen.
29
+ */
30
+ export declare function isStorageAvailable(storage: SupaStorage): Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ import type { Extension } from '@codemirror/state';
2
+ /**
3
+ * Die Bilddateien aus einem DataTransfer. Nicht-Bilder werden hier bereits
4
+ * ausgesiebt: Ein Drop mit einem PDF darf NICHT als Bild-Upload gelten und soll
5
+ * ungestört den Standardweg gehen.
6
+ */
7
+ export declare function imageFilesFrom(data: DataTransfer | null): File[];
8
+ /**
9
+ * Drop- und Paste-Handler. Greift NUR ein, wenn Bilddateien im Spiel sind —
10
+ * reiner Text-Drop und Text-Paste laufen unverändert durch den CM6-Standardweg.
11
+ */
12
+ export declare function uploadDropPasteExtension(onFiles: (files: File[]) => void): Extension;
13
+ /**
14
+ * Öffnet die Dateiauswahl über einen bei Bedarf erzeugten, versteckten Input.
15
+ *
16
+ * Bewusst NICHT in der Toolbar geparkt: So funktioniert `openBrowseFileWindow()`
17
+ * auch bei `toolbar: false` — bei easyMDE warf derselbe Aufruf ohne Toolbar
18
+ * einen Fehler, weil der Input am Toolbar-DOM hing.
19
+ */
20
+ export declare function openFilePicker(accept: string[], onFiles: (files: File[]) => void): void;
@@ -0,0 +1,35 @@
1
+ import { StateField, type EditorState } from '@codemirror/state';
2
+ /** Der aktuelle Bereich eines offenen Platzhalters im Dokument. */
3
+ export interface PlaceholderRange {
4
+ from: number;
5
+ to: number;
6
+ }
7
+ /** Nimmt einen neuen Platzhalter unter `id` in die Verfolgung auf. */
8
+ export declare const addPlaceholder: import("@codemirror/state").StateEffectType<{
9
+ id: number;
10
+ from: number;
11
+ to: number;
12
+ }>;
13
+ /** Nimmt den Platzhalter mit dieser `id` wieder heraus. */
14
+ export declare const removePlaceholder: import("@codemirror/state").StateEffectType<number>;
15
+ /**
16
+ * Hält die offenen Platzhalter und mappt sie bei JEDER Transaktion durch
17
+ * `tr.changes`. Bewusst ein StateField und kein Instanzfeld mit gemerkten
18
+ * Zahlen: Genau daran scheiterte easyMDE — dort wurde das fertige Bild
19
+ * nachträglich am dann-aktuellen Cursor eingefügt, sodass es bei Weitertippen
20
+ * an falscher Stelle landete.
21
+ */
22
+ export declare const uploadPlaceholderField: StateField<Map<number, PlaceholderRange>>;
23
+ /**
24
+ * Der AKTUELLE Bereich eines Platzhalters, oder `null`, wenn er nicht mehr
25
+ * existiert. Die einzige erlaubte Quelle für die Ersetzungsposition.
26
+ */
27
+ export declare function placeholderRange(state: EditorState, id: number): PlaceholderRange | null;
28
+ /**
29
+ * Erzeugt eine unabhängige ID-Quelle. Bewusst eine Factory statt eines
30
+ * modul-globalen Zählers: Der wäre gemeinsamer Zustand über alle Instanzen und
31
+ * alle Testdateien hinweg und machte jeden Test auf eine konkrete ID von der
32
+ * Ausführungsreihenfolge abhängig. Eindeutigkeit ist nur PRO View nötig — die
33
+ * IDs sind Schlüssel in der Map genau eines `uploadPlaceholderField`.
34
+ */
35
+ export declare function createIdSource(): () => number;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Zählt Wörter in `text`: durch Whitespace getrennte, nicht-leere Tokens.
3
+ * Reine Funktion — das neue Sicherheitsnetz, das easyMDE fehlt.
4
+ */
5
+ export declare function wordCount(text: string): number;
@@ -0,0 +1,3 @@
1
+ import type { Locale } from './types';
2
+ /** Deutsch — die bis zur Internationalisierung fest verdrahteten Texte. Reine Daten. */
3
+ export declare const de: Locale;
@@ -0,0 +1,3 @@
1
+ import type { Locale } from './types';
2
+ /** Englisch — Default-Sprache und Rückfall für fehlende Schlüssel. Reine Daten. */
3
+ export declare const en: Locale;
@@ -0,0 +1,9 @@
1
+ import { Facet } from '@codemirror/state';
2
+ import { type Translator } from './translator';
3
+ /**
4
+ * Der Translator im Editor-State. Commands sind reine `(view) => boolean`-
5
+ * Funktionen ohne Zugriff auf die SupaMDE-Instanz; die Facet ist der
6
+ * idiomatische CM6-Weg, ihnen Konfiguration mitzugeben. Ohne Provider (z. B. in
7
+ * Unit-Tests ohne Instanz) gilt Englisch.
8
+ */
9
+ export declare const translatorFacet: Facet<Translator, Translator>;
@@ -0,0 +1,17 @@
1
+ import type { Locale, LocaleTexts, PluralTextKey, SimpleTextKey } from './types';
2
+ /** Liefert die UI-Texte einer Instanz. Einmal pro Instanz erzeugt, danach unveränderlich. */
3
+ export interface Translator {
4
+ /** Effektiver Locale-Code (nach Gültigkeitsprüfung). */
5
+ readonly code: string;
6
+ /** Einfacher Text mit Platzhaltern. */
7
+ t(key: SimpleTextKey, params?: Record<string, string>): string;
8
+ /** Pluralform zu `count`; setzt `{count}` und weitere Platzhalter ein. */
9
+ plural(key: PluralTextKey, count: number, params?: Record<string, string>): string;
10
+ /** Uhrzeit HH:MM im Format des Locale-Codes. */
11
+ formatTime(date: Date): string;
12
+ }
13
+ /**
14
+ * Baut den Translator einer Instanz. Rangfolge pro Schlüssel:
15
+ * `texts` → `locale.texts` → `en.texts`.
16
+ */
17
+ export declare function createTranslator(locale?: Locale, texts?: Partial<LocaleTexts>): Translator;