starmark 1.0.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.
@@ -0,0 +1,19 @@
1
+ export function attachToolbarButton(button, handler) {
2
+ button.addEventListener("mousedown", (event) => {
3
+ event.preventDefault();
4
+ });
5
+ button.addEventListener("click", handler);
6
+ }
7
+
8
+ export function createToolbarButton({ label, title, icon, disabled = false }) {
9
+ const button = document.createElement("button");
10
+ button.type = "button";
11
+ button.className = "toolbar-btn";
12
+ button.setAttribute("aria-label", label);
13
+ if (title) {
14
+ button.title = title;
15
+ }
16
+ button.innerHTML = icon;
17
+ button.disabled = disabled;
18
+ return button;
19
+ }
@@ -0,0 +1,26 @@
1
+ import { attachToolbarButton, createToolbarButton } from "../toolkit.js";
2
+ import { icons } from "../icons.js";
3
+
4
+ export default {
5
+ group: "history",
6
+
7
+ mount(container, api) {
8
+ const isMac = navigator.platform.toUpperCase().includes("MAC");
9
+ const button = createToolbarButton({
10
+ label: "Undo",
11
+ title: isMac ? "Undo (⌘Z)" : "Undo (Ctrl+Z)",
12
+ icon: icons.undo,
13
+ disabled: true,
14
+ });
15
+
16
+ api.onHistoryChange(({ canUndo }) => {
17
+ button.disabled = !canUndo;
18
+ });
19
+
20
+ attachToolbarButton(button, () => {
21
+ api.undo();
22
+ });
23
+
24
+ container.append(button);
25
+ },
26
+ };
@@ -0,0 +1,26 @@
1
+ import { attachToolbarButton, createToolbarButton } from "../toolkit.js";
2
+ import { icons } from "../icons.js";
3
+
4
+ export default {
5
+ group: "history",
6
+
7
+ mount(container, api) {
8
+ const isMac = navigator.platform.toUpperCase().includes("MAC");
9
+ const button = createToolbarButton({
10
+ label: "Redo",
11
+ title: isMac ? "Redo (⌘⇧Z)" : "Redo (Ctrl+Shift+Z)",
12
+ icon: icons.redo,
13
+ disabled: true,
14
+ });
15
+
16
+ api.onHistoryChange(({ canRedo }) => {
17
+ button.disabled = !canRedo;
18
+ });
19
+
20
+ attachToolbarButton(button, () => {
21
+ api.redo();
22
+ });
23
+
24
+ container.append(button);
25
+ },
26
+ };
@@ -0,0 +1,21 @@
1
+ import { attachToolbarButton, createToolbarButton } from "../toolkit.js";
2
+ import { icons } from "../icons.js";
3
+
4
+ export default {
5
+ group: "format",
6
+
7
+ mount(container, api) {
8
+ const button = createToolbarButton({
9
+ label: "Bold",
10
+ icon: icons.bold,
11
+ });
12
+
13
+ attachToolbarButton(button, () => {
14
+ api.runHistoryAction(() => {
15
+ api.wrapSelection("**");
16
+ });
17
+ });
18
+
19
+ container.append(button);
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import { attachToolbarButton, createToolbarButton } from "../toolkit.js";
2
+ import { icons } from "../icons.js";
3
+
4
+ export default {
5
+ group: "format",
6
+
7
+ mount(container, api) {
8
+ const button = createToolbarButton({
9
+ label: "Italic",
10
+ icon: icons.italic,
11
+ });
12
+
13
+ attachToolbarButton(button, () => {
14
+ api.runHistoryAction(() => {
15
+ api.wrapSelection("*");
16
+ });
17
+ });
18
+
19
+ container.append(button);
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import { attachToolbarButton, createToolbarButton } from "../toolkit.js";
2
+ import { icons } from "../icons.js";
3
+
4
+ export default {
5
+ group: "format",
6
+
7
+ mount(container, api) {
8
+ const button = createToolbarButton({
9
+ label: "Strikethrough",
10
+ icon: icons.strikethrough,
11
+ });
12
+
13
+ attachToolbarButton(button, () => {
14
+ api.runHistoryAction(() => {
15
+ api.wrapSelection("~~");
16
+ });
17
+ });
18
+
19
+ container.append(button);
20
+ },
21
+ };
@@ -0,0 +1,136 @@
1
+ import { attachToolbarButton, createToolbarButton } from "../toolkit.js";
2
+ import { icons } from "../icons.js";
3
+
4
+ let savedLinkRange = null;
5
+
6
+ function createLinkDialog() {
7
+ const dialog = document.createElement("dialog");
8
+ dialog.id = "link-dialog";
9
+ dialog.className = "link-dialog";
10
+ dialog.innerHTML = `
11
+ <div class="dialog-header">
12
+ <h2>Insert link</h2>
13
+ <button type="button" class="dialog-close link-dialog-close" aria-label="Close">&times;</button>
14
+ </div>
15
+ <form class="link-form">
16
+ <div class="link-field">
17
+ <label for="link-text">Link text</label>
18
+ <input id="link-text" type="text" autocomplete="off" spellcheck="false" />
19
+ </div>
20
+ <div class="link-field">
21
+ <label for="link-url">URL</label>
22
+ <input id="link-url" type="text" autocomplete="off" spellcheck="false" />
23
+ </div>
24
+ <div class="link-form-actions">
25
+ <button type="submit" class="primary">Done</button>
26
+ </div>
27
+ </form>
28
+ `;
29
+
30
+ const closeBtn = dialog.querySelector(".link-dialog-close");
31
+ const form = dialog.querySelector(".link-form");
32
+ const textInput = dialog.querySelector("#link-text");
33
+ const urlInput = dialog.querySelector("#link-url");
34
+
35
+ closeBtn.addEventListener("click", () => {
36
+ dialog.close();
37
+ });
38
+
39
+ dialog.addEventListener("click", (event) => {
40
+ if (event.target === dialog) {
41
+ dialog.close();
42
+ }
43
+ });
44
+
45
+ dialog.addEventListener("close", () => {
46
+ savedLinkRange = null;
47
+ });
48
+
49
+ textInput.addEventListener("keydown", (event) => {
50
+ if (event.key === "Enter") {
51
+ event.preventDefault();
52
+ urlInput.focus();
53
+ }
54
+ });
55
+
56
+ return { dialog, form, textInput, urlInput };
57
+ }
58
+
59
+ function saveEditorSelection(api) {
60
+ const selection = window.getSelection();
61
+ if (!selection || selection.rangeCount === 0) {
62
+ savedLinkRange = null;
63
+ return "";
64
+ }
65
+
66
+ const range = selection.getRangeAt(0);
67
+ if (!api.editor.contains(range.commonAncestorContainer)) {
68
+ savedLinkRange = null;
69
+ return "";
70
+ }
71
+
72
+ savedLinkRange = range.cloneRange();
73
+ return range.toString();
74
+ }
75
+
76
+ function insertMarkdownLinkAtSelection(api, linkText, url) {
77
+ const trimmedUrl = url.trim();
78
+ if (!savedLinkRange || !trimmedUrl) {
79
+ return false;
80
+ }
81
+
82
+ const markdown = `[${linkText}](${trimmedUrl})`;
83
+ savedLinkRange.deleteContents();
84
+ const textNode = document.createTextNode(markdown);
85
+ savedLinkRange.insertNode(textNode);
86
+
87
+ const selection = window.getSelection();
88
+ const range = document.createRange();
89
+ range.setStartAfter(textNode);
90
+ range.collapse(true);
91
+ selection.removeAllRanges();
92
+ selection.addRange(range);
93
+
94
+ savedLinkRange = null;
95
+ api.reevaluateLines();
96
+ api.flushHistory();
97
+ api.focus();
98
+ return true;
99
+ }
100
+
101
+ export default {
102
+ group: "insert",
103
+
104
+ mount(container, api) {
105
+ const { dialog, form, textInput, urlInput } = createLinkDialog();
106
+ document.body.append(dialog);
107
+
108
+ form.addEventListener("submit", (event) => {
109
+ event.preventDefault();
110
+
111
+ api.flushHistory();
112
+
113
+ if (!insertMarkdownLinkAtSelection(api, textInput.value, urlInput.value)) {
114
+ urlInput.focus();
115
+ return;
116
+ }
117
+
118
+ dialog.close();
119
+ });
120
+
121
+ const button = createToolbarButton({
122
+ label: "Insert link",
123
+ icon: icons.link,
124
+ });
125
+
126
+ attachToolbarButton(button, () => {
127
+ const selectedText = saveEditorSelection(api);
128
+ textInput.value = selectedText;
129
+ urlInput.value = "";
130
+ dialog.showModal();
131
+ urlInput.focus();
132
+ });
133
+
134
+ container.append(button);
135
+ },
136
+ };