Skip to content

Commit 0358c6e

Browse files
authored
Getting overtype to work... (#1)
2 parents 348af96 + 941b6b7 commit 0358c6e

File tree

15 files changed

+4102
-13
lines changed

15 files changed

+4102
-13
lines changed

browser-extension/biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"files": {
1212
"ignoreUnknown": false,
13-
"includes": [".*", "src/**", "tests/**"]
13+
"includes": [".*", "src/**", "tests/**", "!src/overtype", "!src/playgrounds"]
1414
},
1515
"formatter": {
1616
"enabled": true,

browser-extension/package-lock.json

Lines changed: 16 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser-extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"author": "DiffPlug",
33
"dependencies": {
44
"@wxt-dev/webextension-polyfill": "^1.0.0",
5+
"highlight.js": "^11.11.1",
56
"webextension-polyfill": "^0.12.0"
67
},
78
"description": "Syntax highlighting and autosave for comments on GitHub (and other other markdown-friendly websites).",

browser-extension/src/entrypoints/content.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { CONFIG } from '../lib/config'
22
import { logger } from '../lib/logger'
33
import { EnhancerRegistry, TextareaRegistry } from '../lib/registries'
4+
import { githubPrNewCommentContentScript } from '../playgrounds/github-playground'
45

56
const enhancers = new EnhancerRegistry()
67
const enhancedTextareas = new TextareaRegistry()
78

89
export default defineContentScript({
910
main() {
11+
if (CONFIG.MODE === 'PLAYGROUNDS_PR') {
12+
githubPrNewCommentContentScript()
13+
return
14+
}
1015
const textAreasOnPageLoad = document.querySelectorAll<HTMLTextAreaElement>(`textarea`)
1116
for (const textarea of textAreasOnPageLoad) {
1217
enhanceMaybe(textarea)
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Configuration constants for the extension
1+
const MODES = ['PROD', 'PLAYGROUNDS_PR'] as const
2+
3+
export type ModeType = (typeof MODES)[number]
4+
25
export const CONFIG = {
36
ADDED_OVERTYPE_CLASS: 'gitcasso-overtype',
4-
// Debug settings
5-
DEBUG: true, // Set to true to enable debug logging
6-
EXTENSION_NAME: 'gitcasso',
7-
INITIAL_SCAN_DELAY_MS: 100,
8-
MUTATION_OBSERVER_DELAY_MS: 100,
7+
DEBUG: true, // enabled debug logging
8+
EXTENSION_NAME: 'gitcasso', // decorates logs
9+
MODE: 'PLAYGROUNDS_PR' satisfies ModeType,
910
} as const

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+
}

0 commit comments

Comments
 (0)