Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1a85c21
Harder than I thought lol...
Aug 31, 2025
b882b2c
Format.
WebsByTodd Aug 31, 2025
65e9028
Add overtype types.
WebsByTodd Aug 31, 2025
f2ce504
Copy overtype src.
WebsByTodd Sep 2, 2025
0a245f2
Modify the GH DOM a bit to match what OverType expects so that it use…
WebsByTodd Sep 2, 2025
f1a8084
Organize the code a bit.
WebsByTodd Sep 3, 2025
e17fc2b
Only run on github for now.
WebsByTodd Sep 3, 2025
753f9d2
Set minHeight and autoResize to match GitHub.
WebsByTodd Sep 3, 2025
a107bd5
Inject GitHub styles with !important to match OEM.
WebsByTodd Sep 3, 2025
6275239
Keep typedefs from overtype repo.
WebsByTodd Sep 3, 2025
9e3fab5
First customization of repo.
WebsByTodd Sep 3, 2025
de2a9fd
Revert "First customization of repo."
WebsByTodd Sep 3, 2025
dddd0a9
Freely modify overtype styles.
WebsByTodd Sep 3, 2025
9b96a31
Merge codeblock syntax highlighting. https://github.com/panphora/over…
WebsByTodd Sep 4, 2025
8209ec5
Merge branch 'main' into feat/overtype-hard
Sep 4, 2025
f92f6cb
Exclude `src/overtype` from biome
nedtwigg Sep 4, 2025
8fa7f19
Add the POC script as `github-playground`
nedtwigg Sep 4, 2025
e735186
fixup package-lock.json
nedtwigg Sep 4, 2025
854d10c
Exclude the playgrounds from biome
nedtwigg Sep 4, 2025
4184941
Wire up the playground.
nedtwigg Sep 4, 2025
81794d8
Add typedefs.
WebsByTodd Sep 4, 2025
5aa99e3
Install and configure highlight.js.
WebsByTodd Sep 4, 2025
697194b
Don't let `pre` tags switch to useragent monospace font.
WebsByTodd Sep 4, 2025
a124a16
Merge branch 'feat/overtype-hard' into feat/friendly-playground
nedtwigg Sep 4, 2025
a68d742
Pull latest overtype-hard changes into the github-playground.
nedtwigg Sep 4, 2025
50536e2
Adapt the overtype POC branch to the new playground layout (#7)
nedtwigg Sep 4, 2025
941b6b7
Remove overtype because we inlined it.
nedtwigg Sep 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion browser-extension/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"files": {
"ignoreUnknown": false,
"includes": [".*", "src/**", "tests/**"]
"includes": [".*", "src/**", "tests/**", "!src/overtype", "!src/playgrounds"]
},
"formatter": {
"enabled": true,
Expand Down
22 changes: 16 additions & 6 deletions browser-extension/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions browser-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"author": "DiffPlug",
"dependencies": {
"@wxt-dev/webextension-polyfill": "^1.0.0",
"highlight.js": "^11.11.1",
"webextension-polyfill": "^0.12.0"
},
"description": "Syntax highlighting and autosave for comments on GitHub (and other other markdown-friendly websites).",
Expand Down
5 changes: 5 additions & 0 deletions browser-extension/src/entrypoints/content.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { CONFIG } from '../lib/config'
import { logger } from '../lib/logger'
import { EnhancerRegistry, TextareaRegistry } from '../lib/registries'
import { githubPrNewCommentContentScript } from '../playgrounds/github-playground'

const enhancers = new EnhancerRegistry()
const enhancedTextareas = new TextareaRegistry()

export default defineContentScript({
main() {
if (CONFIG.MODE === 'PLAYGROUNDS_PR') {
githubPrNewCommentContentScript()
return
}
const textAreasOnPageLoad = document.querySelectorAll<HTMLTextAreaElement>(`textarea`)
for (const textarea of textAreasOnPageLoad) {
enhanceMaybe(textarea)
Expand Down
13 changes: 7 additions & 6 deletions browser-extension/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Configuration constants for the extension
const MODES = ['PROD', 'PLAYGROUNDS_PR'] as const

export type ModeType = (typeof MODES)[number]

export const CONFIG = {
ADDED_OVERTYPE_CLASS: 'gitcasso-overtype',
// Debug settings
DEBUG: true, // Set to true to enable debug logging
EXTENSION_NAME: 'gitcasso',
INITIAL_SCAN_DELAY_MS: 100,
MUTATION_OBSERVER_DELAY_MS: 100,
DEBUG: true, // enabled debug logging
EXTENSION_NAME: 'gitcasso', // decorates logs
MODE: 'PLAYGROUNDS_PR' satisfies ModeType,
} as const
77 changes: 77 additions & 0 deletions browser-extension/src/overtype/icons.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

204 changes: 204 additions & 0 deletions browser-extension/src/overtype/link-tooltip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/**
* Link Tooltip - CSS Anchor Positioning with index-based anchors
* Shows a clickable tooltip when cursor is within a link
* Uses CSS anchor positioning with dynamically selected anchor
*/

export class LinkTooltip {
constructor(editor) {
this.editor = editor;
this.tooltip = null;
this.currentLink = null;
this.hideTimeout = null;

this.init();
}

init() {
// Check for CSS anchor positioning support
const supportsAnchor =
CSS.supports("position-anchor: --x") &&
CSS.supports("position-area: center");

if (!supportsAnchor) {
// Don't show anything if not supported
return;
}

// Create tooltip element
this.createTooltip();

// Listen for cursor position changes
this.editor.textarea.addEventListener("selectionchange", () =>
this.checkCursorPosition()
);
this.editor.textarea.addEventListener("keyup", (e) => {
if (e.key.includes("Arrow") || e.key === "Home" || e.key === "End") {
this.checkCursorPosition();
}
});

// Hide tooltip when typing or scrolling
this.editor.textarea.addEventListener("input", () => this.hide());
this.editor.textarea.addEventListener("scroll", () => this.hide());

// Keep tooltip visible on hover
this.tooltip.addEventListener("mouseenter", () => this.cancelHide());
this.tooltip.addEventListener("mouseleave", () => this.scheduleHide());
}

createTooltip() {
// Create tooltip element
this.tooltip = document.createElement("div");
this.tooltip.className = "overtype-link-tooltip";

// Add CSS anchor positioning styles
const tooltipStyles = document.createElement("style");
tooltipStyles.textContent = `
@supports (position-anchor: --x) and (position-area: center) {
.overtype-link-tooltip {
position: absolute;
position-anchor: var(--target-anchor, --link-0);
position-area: block-end center;
margin-top: 8px;

background: #333;
color: white;
padding: 6px 10px;
border-radius: 16px;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: none;
z-index: 10000;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

position-try: most-width block-end inline-end, flip-inline, block-start center;
position-visibility: anchors-visible;
}

.overtype-link-tooltip.visible {
display: flex;
}
}
`;
document.head.appendChild(tooltipStyles);

// Add link icon and text container
this.tooltip.innerHTML = `
<span style="display: flex; align-items: center; gap: 6px;">
<svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor" style="flex-shrink: 0;">
<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>
<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>
</svg>
<span class="overtype-link-tooltip-url"></span>
</span>
`;

// Click handler to open link
this.tooltip.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
if (this.currentLink) {
window.open(this.currentLink.url, "_blank");
this.hide();
}
});

// Append tooltip to editor container
this.editor.container.appendChild(this.tooltip);
}

checkCursorPosition() {
const cursorPos = this.editor.textarea.selectionStart;
const text = this.editor.textarea.value;

// Find if cursor is within a markdown link
const linkInfo = this.findLinkAtPosition(text, cursorPos);

if (linkInfo) {
if (
!this.currentLink ||
this.currentLink.url !== linkInfo.url ||
this.currentLink.index !== linkInfo.index
) {
this.show(linkInfo);
}
} else {
this.scheduleHide();
}
}

findLinkAtPosition(text, position) {
// Regex to find markdown links: [text](url)
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
let match;
let linkIndex = 0;

while ((match = linkRegex.exec(text)) !== null) {
const start = match.index;
const end = match.index + match[0].length;

if (position >= start && position <= end) {
return {
text: match[1],
url: match[2],
index: linkIndex,
start: start,
end: end,
};
}
linkIndex++;
}

return null;
}

show(linkInfo) {
this.currentLink = linkInfo;
this.cancelHide();

// Update tooltip content
const urlSpan = this.tooltip.querySelector(".overtype-link-tooltip-url");
urlSpan.textContent = linkInfo.url;

// Set the CSS variable to point to the correct anchor
this.tooltip.style.setProperty(
"--target-anchor",
`--link-${linkInfo.index}`
);

// Show tooltip (CSS anchor positioning handles the rest)
this.tooltip.classList.add("visible");
}

hide() {
this.tooltip.classList.remove("visible");
this.currentLink = null;
}

scheduleHide() {
this.cancelHide();
this.hideTimeout = setTimeout(() => this.hide(), 300);
}

cancelHide() {
if (this.hideTimeout) {
clearTimeout(this.hideTimeout);
this.hideTimeout = null;
}
}

destroy() {
this.cancelHide();
if (this.tooltip && this.tooltip.parentNode) {
this.tooltip.parentNode.removeChild(this.tooltip);
}
this.tooltip = null;
this.currentLink = null;
}
}
Loading