overtype 1.0.6 → 1.1.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/LICENSE +1 -1
- package/README.md +26 -0
- package/dist/overtype.esm.js +202 -2
- package/dist/overtype.esm.js.map +3 -3
- package/dist/overtype.js +202 -2
- package/dist/overtype.js.map +3 -3
- package/dist/overtype.min.js +89 -44
- package/package.json +1 -1
- package/src/link-tooltip.js +195 -0
- package/src/overtype.js +50 -1
- package/src/parser.js +36 -1
- package/src/styles.js +2 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -383,6 +383,27 @@ Check the `examples` folder for complete examples:
|
|
|
383
383
|
- `custom-theme.html` - Theme customization
|
|
384
384
|
- `dynamic.html` - Dynamic creation/destruction
|
|
385
385
|
|
|
386
|
+
## Limitations
|
|
387
|
+
|
|
388
|
+
Due to the transparent textarea overlay approach, OverType has some intentional design limitations:
|
|
389
|
+
|
|
390
|
+
### Images Not Supported
|
|
391
|
+
Images (``) are not rendered. Variable-height images would break the character alignment between textarea and preview.
|
|
392
|
+
|
|
393
|
+
### Monospace Font Required
|
|
394
|
+
All text must use a monospace font to maintain alignment. Variable-width fonts would cause the textarea cursor position to drift from the visual text position.
|
|
395
|
+
|
|
396
|
+
### Fixed Font Size
|
|
397
|
+
All content must use the same font size. Different sizes for headers or other elements would break vertical alignment.
|
|
398
|
+
|
|
399
|
+
### Visible Markdown Syntax
|
|
400
|
+
All markdown formatting characters remain visible (e.g., `**bold**` shows the asterisks). This is intentional - hiding them would break the 1:1 character mapping.
|
|
401
|
+
|
|
402
|
+
### Links Require Modifier Key
|
|
403
|
+
Links are clickable with Cmd/Ctrl+Click only. Direct clicking would interfere with text editing since clicks need to position the cursor in the textarea.
|
|
404
|
+
|
|
405
|
+
These limitations are what enable OverType's core benefits: perfect native textarea behavior, tiny size, and zero complexity.
|
|
406
|
+
|
|
386
407
|
## Development
|
|
387
408
|
|
|
388
409
|
```bash
|
|
@@ -431,6 +452,11 @@ OverType uses a unique invisible textarea overlay approach:
|
|
|
431
452
|
- Textarea content drives everything
|
|
432
453
|
- One-way data flow: textarea → parser → preview
|
|
433
454
|
|
|
455
|
+
## Contributors
|
|
456
|
+
|
|
457
|
+
Special thanks to:
|
|
458
|
+
- [Josh Doman](https://github.com/joshdoman) - Fixed inline code formatting preservation ([#6](https://github.com/panphora/overtype/pull/6))
|
|
459
|
+
|
|
434
460
|
## License
|
|
435
461
|
|
|
436
462
|
MIT
|
package/dist/overtype.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OverType v1.0
|
|
2
|
+
* OverType v1.1.0
|
|
3
3
|
* A lightweight markdown editor library with perfect WYSIWYG alignment
|
|
4
4
|
* @license MIT
|
|
5
5
|
* @author Demo User
|
|
@@ -14,6 +14,12 @@ var __publicField = (obj, key, value) => {
|
|
|
14
14
|
|
|
15
15
|
// src/parser.js
|
|
16
16
|
var MarkdownParser = class {
|
|
17
|
+
/**
|
|
18
|
+
* Reset link index (call before parsing a new document)
|
|
19
|
+
*/
|
|
20
|
+
static resetLinkIndex() {
|
|
21
|
+
this.linkIndex = 0;
|
|
22
|
+
}
|
|
17
23
|
/**
|
|
18
24
|
* Escape HTML special characters
|
|
19
25
|
* @param {string} text - Raw text to escape
|
|
@@ -139,7 +145,10 @@ var MarkdownParser = class {
|
|
|
139
145
|
* @returns {string} HTML with link styling
|
|
140
146
|
*/
|
|
141
147
|
static parseLinks(html) {
|
|
142
|
-
return html.replace(/\[(.+?)\]\((.+?)\)/g,
|
|
148
|
+
return html.replace(/\[(.+?)\]\((.+?)\)/g, (match, text, url) => {
|
|
149
|
+
const anchorName = `--link-${this.linkIndex++}`;
|
|
150
|
+
return `<a href="${url}" style="anchor-name: ${anchorName}"><span class="syntax-marker">[</span>${text}<span class="syntax-marker">](</span><span class="syntax-marker">${url}</span><span class="syntax-marker">)</span></a>`;
|
|
151
|
+
});
|
|
143
152
|
}
|
|
144
153
|
/**
|
|
145
154
|
* Parse all inline elements in correct order
|
|
@@ -149,9 +158,18 @@ var MarkdownParser = class {
|
|
|
149
158
|
static parseInlineElements(text) {
|
|
150
159
|
let html = text;
|
|
151
160
|
html = this.parseInlineCode(html);
|
|
161
|
+
const codeBlocks = /* @__PURE__ */ new Map();
|
|
162
|
+
html = html.replace(/(<code>.*?<\/code>)/g, (match) => {
|
|
163
|
+
const placeholder = `\uE000${codeBlocks.size}\uE001`;
|
|
164
|
+
codeBlocks.set(placeholder, match);
|
|
165
|
+
return placeholder;
|
|
166
|
+
});
|
|
152
167
|
html = this.parseLinks(html);
|
|
153
168
|
html = this.parseBold(html);
|
|
154
169
|
html = this.parseItalic(html);
|
|
170
|
+
codeBlocks.forEach((codeBlock, placeholder) => {
|
|
171
|
+
html = html.replace(placeholder, codeBlock);
|
|
172
|
+
});
|
|
155
173
|
return html;
|
|
156
174
|
}
|
|
157
175
|
/**
|
|
@@ -186,6 +204,7 @@ var MarkdownParser = class {
|
|
|
186
204
|
* @returns {string} Parsed HTML
|
|
187
205
|
*/
|
|
188
206
|
static parse(text, activeLine = -1, showActiveLineRaw = false) {
|
|
207
|
+
this.resetLinkIndex();
|
|
189
208
|
const lines = text.split("\n");
|
|
190
209
|
const parsedLines = lines.map((line, index) => {
|
|
191
210
|
if (showActiveLineRaw && index === activeLine) {
|
|
@@ -197,6 +216,8 @@ var MarkdownParser = class {
|
|
|
197
216
|
return parsedLines.join("");
|
|
198
217
|
}
|
|
199
218
|
};
|
|
219
|
+
// Track link index for anchor naming
|
|
220
|
+
__publicField(MarkdownParser, "linkIndex", 0);
|
|
200
221
|
|
|
201
222
|
// node_modules/markdown-actions/dist/markdown-actions.esm.js
|
|
202
223
|
var __defProp2 = Object.defineProperty;
|
|
@@ -1487,6 +1508,8 @@ function generateStyles(options = {}) {
|
|
|
1487
1508
|
padding: 0 !important;
|
|
1488
1509
|
border-radius: 2px !important;
|
|
1489
1510
|
font-family: inherit !important;
|
|
1511
|
+
font-size: inherit !important;
|
|
1512
|
+
line-height: inherit !important;
|
|
1490
1513
|
font-weight: normal !important;
|
|
1491
1514
|
}
|
|
1492
1515
|
|
|
@@ -1962,6 +1985,152 @@ var Toolbar = class {
|
|
|
1962
1985
|
}
|
|
1963
1986
|
};
|
|
1964
1987
|
|
|
1988
|
+
// src/link-tooltip.js
|
|
1989
|
+
var LinkTooltip = class {
|
|
1990
|
+
constructor(editor) {
|
|
1991
|
+
this.editor = editor;
|
|
1992
|
+
this.tooltip = null;
|
|
1993
|
+
this.currentLink = null;
|
|
1994
|
+
this.hideTimeout = null;
|
|
1995
|
+
this.init();
|
|
1996
|
+
}
|
|
1997
|
+
init() {
|
|
1998
|
+
const supportsAnchor = CSS.supports("position-anchor: --x") && CSS.supports("position-area: center");
|
|
1999
|
+
if (!supportsAnchor) {
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
this.createTooltip();
|
|
2003
|
+
this.editor.textarea.addEventListener("selectionchange", () => this.checkCursorPosition());
|
|
2004
|
+
this.editor.textarea.addEventListener("keyup", (e) => {
|
|
2005
|
+
if (e.key.includes("Arrow") || e.key === "Home" || e.key === "End") {
|
|
2006
|
+
this.checkCursorPosition();
|
|
2007
|
+
}
|
|
2008
|
+
});
|
|
2009
|
+
this.editor.textarea.addEventListener("input", () => this.hide());
|
|
2010
|
+
this.editor.textarea.addEventListener("scroll", () => this.hide());
|
|
2011
|
+
this.tooltip.addEventListener("mouseenter", () => this.cancelHide());
|
|
2012
|
+
this.tooltip.addEventListener("mouseleave", () => this.scheduleHide());
|
|
2013
|
+
}
|
|
2014
|
+
createTooltip() {
|
|
2015
|
+
this.tooltip = document.createElement("div");
|
|
2016
|
+
this.tooltip.className = "overtype-link-tooltip";
|
|
2017
|
+
const tooltipStyles = document.createElement("style");
|
|
2018
|
+
tooltipStyles.textContent = `
|
|
2019
|
+
@supports (position-anchor: --x) and (position-area: center) {
|
|
2020
|
+
.overtype-link-tooltip {
|
|
2021
|
+
position: absolute;
|
|
2022
|
+
position-anchor: var(--target-anchor, --link-0);
|
|
2023
|
+
position-area: block-end center;
|
|
2024
|
+
margin-top: 8px;
|
|
2025
|
+
|
|
2026
|
+
background: #333;
|
|
2027
|
+
color: white;
|
|
2028
|
+
padding: 6px 10px;
|
|
2029
|
+
border-radius: 16px;
|
|
2030
|
+
font-size: 12px;
|
|
2031
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
2032
|
+
display: none;
|
|
2033
|
+
z-index: 10000;
|
|
2034
|
+
cursor: pointer;
|
|
2035
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
2036
|
+
max-width: 300px;
|
|
2037
|
+
white-space: nowrap;
|
|
2038
|
+
overflow: hidden;
|
|
2039
|
+
text-overflow: ellipsis;
|
|
2040
|
+
|
|
2041
|
+
position-try: most-width block-end inline-end, flip-inline, block-start center;
|
|
2042
|
+
position-visibility: anchors-visible;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
.overtype-link-tooltip.visible {
|
|
2046
|
+
display: flex;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
`;
|
|
2050
|
+
document.head.appendChild(tooltipStyles);
|
|
2051
|
+
this.tooltip.innerHTML = `
|
|
2052
|
+
<span style="display: flex; align-items: center; gap: 6px;">
|
|
2053
|
+
<svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" style="flex-shrink: 0;">
|
|
2054
|
+
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
|
|
2055
|
+
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path>
|
|
2056
|
+
</svg>
|
|
2057
|
+
<span class="overtype-link-tooltip-url"></span>
|
|
2058
|
+
</span>
|
|
2059
|
+
`;
|
|
2060
|
+
this.tooltip.addEventListener("click", (e) => {
|
|
2061
|
+
e.preventDefault();
|
|
2062
|
+
e.stopPropagation();
|
|
2063
|
+
if (this.currentLink) {
|
|
2064
|
+
window.open(this.currentLink.url, "_blank");
|
|
2065
|
+
this.hide();
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
this.editor.container.appendChild(this.tooltip);
|
|
2069
|
+
}
|
|
2070
|
+
checkCursorPosition() {
|
|
2071
|
+
const cursorPos = this.editor.textarea.selectionStart;
|
|
2072
|
+
const text = this.editor.textarea.value;
|
|
2073
|
+
const linkInfo = this.findLinkAtPosition(text, cursorPos);
|
|
2074
|
+
if (linkInfo) {
|
|
2075
|
+
if (!this.currentLink || this.currentLink.url !== linkInfo.url || this.currentLink.index !== linkInfo.index) {
|
|
2076
|
+
this.show(linkInfo);
|
|
2077
|
+
}
|
|
2078
|
+
} else {
|
|
2079
|
+
this.scheduleHide();
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
findLinkAtPosition(text, position) {
|
|
2083
|
+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
2084
|
+
let match;
|
|
2085
|
+
let linkIndex = 0;
|
|
2086
|
+
while ((match = linkRegex.exec(text)) !== null) {
|
|
2087
|
+
const start = match.index;
|
|
2088
|
+
const end = match.index + match[0].length;
|
|
2089
|
+
if (position >= start && position <= end) {
|
|
2090
|
+
return {
|
|
2091
|
+
text: match[1],
|
|
2092
|
+
url: match[2],
|
|
2093
|
+
index: linkIndex,
|
|
2094
|
+
start,
|
|
2095
|
+
end
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
linkIndex++;
|
|
2099
|
+
}
|
|
2100
|
+
return null;
|
|
2101
|
+
}
|
|
2102
|
+
show(linkInfo) {
|
|
2103
|
+
this.currentLink = linkInfo;
|
|
2104
|
+
this.cancelHide();
|
|
2105
|
+
const urlSpan = this.tooltip.querySelector(".overtype-link-tooltip-url");
|
|
2106
|
+
urlSpan.textContent = linkInfo.url;
|
|
2107
|
+
this.tooltip.style.setProperty("--target-anchor", `--link-${linkInfo.index}`);
|
|
2108
|
+
this.tooltip.classList.add("visible");
|
|
2109
|
+
}
|
|
2110
|
+
hide() {
|
|
2111
|
+
this.tooltip.classList.remove("visible");
|
|
2112
|
+
this.currentLink = null;
|
|
2113
|
+
}
|
|
2114
|
+
scheduleHide() {
|
|
2115
|
+
this.cancelHide();
|
|
2116
|
+
this.hideTimeout = setTimeout(() => this.hide(), 300);
|
|
2117
|
+
}
|
|
2118
|
+
cancelHide() {
|
|
2119
|
+
if (this.hideTimeout) {
|
|
2120
|
+
clearTimeout(this.hideTimeout);
|
|
2121
|
+
this.hideTimeout = null;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
destroy() {
|
|
2125
|
+
this.cancelHide();
|
|
2126
|
+
if (this.tooltip && this.tooltip.parentNode) {
|
|
2127
|
+
this.tooltip.parentNode.removeChild(this.tooltip);
|
|
2128
|
+
}
|
|
2129
|
+
this.tooltip = null;
|
|
2130
|
+
this.currentLink = null;
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
|
|
1965
2134
|
// src/overtype.js
|
|
1966
2135
|
var _OverType = class _OverType {
|
|
1967
2136
|
/**
|
|
@@ -2020,6 +2189,7 @@ var _OverType = class _OverType {
|
|
|
2020
2189
|
this._buildFromScratch();
|
|
2021
2190
|
}
|
|
2022
2191
|
this.shortcuts = new ShortcutsManager(this);
|
|
2192
|
+
this.linkTooltip = new LinkTooltip(this);
|
|
2023
2193
|
if (this.options.toolbar) {
|
|
2024
2194
|
this.toolbar = new Toolbar(this);
|
|
2025
2195
|
this.toolbar.create();
|
|
@@ -2290,6 +2460,36 @@ var _OverType = class _OverType {
|
|
|
2290
2460
|
* @private
|
|
2291
2461
|
*/
|
|
2292
2462
|
handleKeydown(event) {
|
|
2463
|
+
if (event.key === "Tab") {
|
|
2464
|
+
event.preventDefault();
|
|
2465
|
+
const start = this.textarea.selectionStart;
|
|
2466
|
+
const end = this.textarea.selectionEnd;
|
|
2467
|
+
const value = this.textarea.value;
|
|
2468
|
+
if (start !== end && event.shiftKey) {
|
|
2469
|
+
const before = value.substring(0, start);
|
|
2470
|
+
const selection = value.substring(start, end);
|
|
2471
|
+
const after = value.substring(end);
|
|
2472
|
+
const lines = selection.split("\n");
|
|
2473
|
+
const outdented = lines.map((line) => line.replace(/^ /, "")).join("\n");
|
|
2474
|
+
this.textarea.value = before + outdented + after;
|
|
2475
|
+
this.textarea.selectionStart = start;
|
|
2476
|
+
this.textarea.selectionEnd = start + outdented.length;
|
|
2477
|
+
} else if (start !== end) {
|
|
2478
|
+
const before = value.substring(0, start);
|
|
2479
|
+
const selection = value.substring(start, end);
|
|
2480
|
+
const after = value.substring(end);
|
|
2481
|
+
const lines = selection.split("\n");
|
|
2482
|
+
const indented = lines.map((line) => " " + line).join("\n");
|
|
2483
|
+
this.textarea.value = before + indented + after;
|
|
2484
|
+
this.textarea.selectionStart = start;
|
|
2485
|
+
this.textarea.selectionEnd = start + indented.length;
|
|
2486
|
+
} else {
|
|
2487
|
+
this.textarea.value = value.substring(0, start) + " " + value.substring(end);
|
|
2488
|
+
this.textarea.selectionStart = this.textarea.selectionEnd = start + 2;
|
|
2489
|
+
}
|
|
2490
|
+
this.textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2293
2493
|
const handled = this.shortcuts.handleKeydown(event);
|
|
2294
2494
|
if (!handled && this.options.onKeydown) {
|
|
2295
2495
|
this.options.onKeydown(event, this);
|