Skip to content

Commit f2ce504

Browse files
committed
Copy overtype src.
1 parent 65e9028 commit f2ce504

File tree

10 files changed

+3819
-38
lines changed

10 files changed

+3819
-38
lines changed

browser-extension/src/overtype/icons.js

Lines changed: 77 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/**
2+
* Link Tooltip - CSS Anchor Positioning with index-based anchors
3+
* Shows a clickable tooltip when cursor is within a link
4+
* Uses CSS anchor positioning with dynamically selected anchor
5+
*/
6+
7+
export class LinkTooltip {
8+
constructor(editor) {
9+
this.editor = editor;
10+
this.tooltip = null;
11+
this.currentLink = null;
12+
this.hideTimeout = null;
13+
14+
this.init();
15+
}
16+
17+
init() {
18+
// Check for CSS anchor positioning support
19+
const supportsAnchor =
20+
CSS.supports("position-anchor: --x") &&
21+
CSS.supports("position-area: center");
22+
23+
if (!supportsAnchor) {
24+
// Don't show anything if not supported
25+
return;
26+
}
27+
28+
// Create tooltip element
29+
this.createTooltip();
30+
31+
// Listen for cursor position changes
32+
this.editor.textarea.addEventListener("selectionchange", () =>
33+
this.checkCursorPosition()
34+
);
35+
this.editor.textarea.addEventListener("keyup", (e) => {
36+
if (e.key.includes("Arrow") || e.key === "Home" || e.key === "End") {
37+
this.checkCursorPosition();
38+
}
39+
});
40+
41+
// Hide tooltip when typing or scrolling
42+
this.editor.textarea.addEventListener("input", () => this.hide());
43+
this.editor.textarea.addEventListener("scroll", () => this.hide());
44+
45+
// Keep tooltip visible on hover
46+
this.tooltip.addEventListener("mouseenter", () => this.cancelHide());
47+
this.tooltip.addEventListener("mouseleave", () => this.scheduleHide());
48+
}
49+
50+
createTooltip() {
51+
// Create tooltip element
52+
this.tooltip = document.createElement("div");
53+
this.tooltip.className = "overtype-link-tooltip";
54+
55+
// Add CSS anchor positioning styles
56+
const tooltipStyles = document.createElement("style");
57+
tooltipStyles.textContent = `
58+
@supports (position-anchor: --x) and (position-area: center) {
59+
.overtype-link-tooltip {
60+
position: absolute;
61+
position-anchor: var(--target-anchor, --link-0);
62+
position-area: block-end center;
63+
margin-top: 8px;
64+
65+
background: #333;
66+
color: white;
67+
padding: 6px 10px;
68+
border-radius: 16px;
69+
font-size: 12px;
70+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
71+
display: none;
72+
z-index: 10000;
73+
cursor: pointer;
74+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
75+
max-width: 300px;
76+
white-space: nowrap;
77+
overflow: hidden;
78+
text-overflow: ellipsis;
79+
80+
position-try: most-width block-end inline-end, flip-inline, block-start center;
81+
position-visibility: anchors-visible;
82+
}
83+
84+
.overtype-link-tooltip.visible {
85+
display: flex;
86+
}
87+
}
88+
`;
89+
document.head.appendChild(tooltipStyles);
90+
91+
// Add link icon and text container
92+
this.tooltip.innerHTML = `
93+
<span style="display: flex; align-items: center; gap: 6px;">
94+
<svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" style="flex-shrink: 0;">
95+
<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>
96+
<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>
97+
</svg>
98+
<span class="overtype-link-tooltip-url"></span>
99+
</span>
100+
`;
101+
102+
// Click handler to open link
103+
this.tooltip.addEventListener("click", (e) => {
104+
e.preventDefault();
105+
e.stopPropagation();
106+
if (this.currentLink) {
107+
window.open(this.currentLink.url, "_blank");
108+
this.hide();
109+
}
110+
});
111+
112+
// Append tooltip to editor container
113+
this.editor.container.appendChild(this.tooltip);
114+
}
115+
116+
checkCursorPosition() {
117+
const cursorPos = this.editor.textarea.selectionStart;
118+
const text = this.editor.textarea.value;
119+
120+
// Find if cursor is within a markdown link
121+
const linkInfo = this.findLinkAtPosition(text, cursorPos);
122+
123+
if (linkInfo) {
124+
if (
125+
!this.currentLink ||
126+
this.currentLink.url !== linkInfo.url ||
127+
this.currentLink.index !== linkInfo.index
128+
) {
129+
this.show(linkInfo);
130+
}
131+
} else {
132+
this.scheduleHide();
133+
}
134+
}
135+
136+
findLinkAtPosition(text, position) {
137+
// Regex to find markdown links: [text](url)
138+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
139+
let match;
140+
let linkIndex = 0;
141+
142+
while ((match = linkRegex.exec(text)) !== null) {
143+
const start = match.index;
144+
const end = match.index + match[0].length;
145+
146+
if (position >= start && position <= end) {
147+
return {
148+
text: match[1],
149+
url: match[2],
150+
index: linkIndex,
151+
start: start,
152+
end: end,
153+
};
154+
}
155+
linkIndex++;
156+
}
157+
158+
return null;
159+
}
160+
161+
show(linkInfo) {
162+
this.currentLink = linkInfo;
163+
this.cancelHide();
164+
165+
// Update tooltip content
166+
const urlSpan = this.tooltip.querySelector(".overtype-link-tooltip-url");
167+
urlSpan.textContent = linkInfo.url;
168+
169+
// Set the CSS variable to point to the correct anchor
170+
this.tooltip.style.setProperty(
171+
"--target-anchor",
172+
`--link-${linkInfo.index}`
173+
);
174+
175+
// Show tooltip (CSS anchor positioning handles the rest)
176+
this.tooltip.classList.add("visible");
177+
}
178+
179+
hide() {
180+
this.tooltip.classList.remove("visible");
181+
this.currentLink = null;
182+
}
183+
184+
scheduleHide() {
185+
this.cancelHide();
186+
this.hideTimeout = setTimeout(() => this.hide(), 300);
187+
}
188+
189+
cancelHide() {
190+
if (this.hideTimeout) {
191+
clearTimeout(this.hideTimeout);
192+
this.hideTimeout = null;
193+
}
194+
}
195+
196+
destroy() {
197+
this.cancelHide();
198+
if (this.tooltip && this.tooltip.parentNode) {
199+
this.tooltip.parentNode.removeChild(this.tooltip);
200+
}
201+
this.tooltip = null;
202+
this.currentLink = null;
203+
}
204+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
interface OverTypeOptions {
2+
fontSize?: number;
3+
lineHeight?: number;
4+
fontFamily?: string;
5+
theme?: string | ThemeObject;
6+
autofocus?: boolean;
7+
placeholder?: string;
8+
value?: string;
9+
autoResize?: boolean;
10+
minHeight?: number;
11+
maxHeight?: number;
12+
toolbar?: boolean;
13+
onChange?: (value: string) => void;
14+
onKeydown?: (event: KeyboardEvent) => void;
15+
}
16+
17+
interface ThemeObject {
18+
[key: string]: string;
19+
}
20+
21+
class OverType {
22+
constructor(
23+
target: string | Element | NodeList | Element[],
24+
options?: OverTypeOptions
25+
);
26+
27+
getValue(): string;
28+
setValue(value: string): void;
29+
getRenderedHTML(processContent?: boolean): string;
30+
getPreviewHTML(): string;
31+
setTheme(theme: string | ThemeObject): void;
32+
showStats(show: boolean): void;
33+
showPlainTextarea(show: boolean): void;
34+
showPreviewMode(show: boolean): void;
35+
36+
static init(selector: string, options?: OverTypeOptions): OverType[];
37+
}
38+
39+
export = OverType;

0 commit comments

Comments
 (0)