Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions src/panel/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ export class ReviewPanelView extends ItemView {
}
if (target.closest(".tc-card-actions, .tc-reply, button, textarea, input"))
return;
if (hasSelectionIn(card)) return;
// With an anchor, jump to the commented span rather than the chip.
const from = anchor ? anchor.from : thread.from;
this.host.revealOffset(file, from, thread.to - from, true);
Expand Down Expand Up @@ -508,6 +509,7 @@ export class ReviewPanelView extends ItemView {
card.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
if (target.closest(".tc-reply, button, textarea, input")) return;
if (hasSelectionIn(card)) return;
if (thread) this.host.revealOffset(file, n.from, thread.to - n.from, true);
else this.host.revealOffset(file, n.from, n.to - n.from);
});
Expand Down Expand Up @@ -621,6 +623,7 @@ export class ReviewPanelView extends ItemView {
return;
}
if (target.closest("button")) return;
if (hasSelectionIn(card)) return;
this.host.revealOffset(file, n.from, n.to - n.from);
});

Expand Down Expand Up @@ -850,3 +853,9 @@ class ConfirmActionModal extends Modal {
this.close();
}
}

/** True when the user just selected text inside `card` (so a click shouldn't jump away). */
function hasSelectionIn(card: HTMLElement): boolean {
const sel = card.win.getSelection();
return !!sel && !sel.isCollapsed && card.contains(sel.anchorNode);
}
62 changes: 42 additions & 20 deletions src/reading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ function locateAll(
}
}
if (op.closeIn) {
const found = locateClose(el, op.node, cursor);
const found = locateClose(el, op.node, cursor, loc.wrapEl);
if (found) {
loc.closeRange = found.range;
cursor = found.range.end;
Expand All @@ -252,10 +252,10 @@ function locateOpen(
cursor: TextPos,
): { range: DomRange; wrapEl?: Element } | null {
if (node.kind === "highlight") {
return locateBracedWrapper(el, cursor, ["MARK"]);
return locateBracedWrapper(el, cursor, "==", ["MARK"]);
}
if (node.kind === "substitution") {
return locateBracedWrapper(el, cursor, ["DEL", "S"]);
return locateBracedWrapper(el, cursor, "~~", ["DEL", "S"]);
}
// addition / deletion / comment: the open token is `{<prefix><sigil>` where
// the (optional) metadata prefix survives in the DOM as literal text. Match
Expand Down Expand Up @@ -300,12 +300,13 @@ function locateClose(
el: HTMLElement,
node: CriticNode,
cursor: TextPos,
wrapEl?: Element,
): { range: DomRange } | null {
if (node.kind === "highlight") {
return locateBracedCloser(el, cursor, "==}");
return locateBracedCloser(el, cursor, "==}", wrapEl);
}
if (node.kind === "substitution") {
return locateBracedCloser(el, cursor, "~~}");
return locateBracedCloser(el, cursor, "~~}", wrapEl);
}
const tok = closeLiteral(node.kind);
return locateLiteral(el, cursor, tok);
Expand Down Expand Up @@ -348,30 +349,37 @@ function locateLiteral(

/**
* Locate the open token of a markup whose internal delimiters are also
* native markdown (`==` → <mark>, `~~` → <del>/<s>). Try the literal token
* first (in case Obsidian didn't eat them — e.g., leading/trailing space
* inside disqualifies the syntax); otherwise look for `{` immediately
* followed by an element with one of `tags`.
* native markdown (`==` → <mark>, `~~` → <del>/<s>). At each `{`, accept
* either the literal `{<prefix><sigil>` (Obsidian didn't eat the sigil — e.g.
* leading/trailing space inside disqualifies the syntax) or `{<prefix>`
* immediately followed by an element with one of `tags`. The first `{` that
* matches wins — preferring a literal match further ahead would grab the open
* of a later mark (issue #49).
*/
function locateBracedWrapper(
el: HTMLElement,
cursor: TextPos,
sigil: string,
tags: string[],
): { range: DomRange; wrapEl?: Element } | null {
// Obsidian didn't eat the sigil (e.g. leading/trailing space disqualified
// `==`/`~~`): the whole `{<prefix><sigil>` is literal text in one node.
const lit = locateOpenSigil(el, cursor);
if (lit) return { range: lit.range };

// Sigil eaten into the following <mark>/<del>/<s>; only `{<prefix>` remains
// as literal text. Match `{` + optional prefix at the (whitespace) tail and
// delete the whole `{<prefix>` so the prefix is never echoed.
let node: Text | null = cursor.node;
let offset = cursor.offset;
while (node) {
const text = node.nodeValue ?? "";
let idx = text.indexOf("{", offset);
while (idx >= 0) {
const lit = OPEN_WITH_PREFIX.exec(text.slice(idx));
if (lit && lit[0].endsWith(sigil)) {
return {
range: {
start: { node, offset: idx },
end: { node, offset: idx + lit[0].length },
},
};
}
// Sigil eaten into the following <mark>/<del>/<s>; only `{<prefix>`
// remains as literal text. Delete the whole `{<prefix>` so the prefix
// is never echoed.
const m = OPEN_PREFIX_THEN_WS.exec(text.slice(idx));
if (m) {
// m[0] = `{<prefix>` + trailing whitespace; strip the trailing ws from
Expand Down Expand Up @@ -428,10 +436,24 @@ function locateBracedCloser(
el: HTMLElement,
cursor: TextPos,
literalToken: string,
wrapEl?: Element,
): { range: DomRange } | null {
const lit = locateLiteral(el, cursor, literalToken);
if (lit) return lit;
return locateLiteral(el, cursor, "}");
if (!wrapEl) return locateLiteral(el, cursor, literalToken);
// Sigil eaten into the wrapper: the closer is the `}` right after it.
// Searching for the literal token instead could match a later mark's close
// (issue #49).
const after = firstTextAfter(el, wrapEl);
return after ? locateLiteral(el, { node: after, offset: 0 }, "}") : null;
}

function firstTextAfter(root: HTMLElement, wrapEl: Element): Text | null {
const w = makeWalker(root);
let n = w.nextNode();
while (n && (wrapEl.contains(n) ||
!(wrapEl.compareDocumentPosition(n) & Node.DOCUMENT_POSITION_FOLLOWING))) {
n = w.nextNode();
}
return n as Text | null;
}

// ---------- Mutation ----------
Expand Down
9 changes: 9 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,15 @@ body {
color: var(--text-faint);
margin-left: 8px;
}
/* Obsidian disables selection in side panes; let users copy card text (#50). */
.tc-message-body,
.tc-thread-anchor,
.tc-diff-added,
.tc-diff-removed,
.tc-diff-highlight {
user-select: text;
-webkit-user-select: text;
}
.tc-message-body {
font-size: 0.92em;
white-space: pre-wrap;
Expand Down
Loading