Skip to content

Commit

Permalink
Handle italicized links
Browse files Browse the repository at this point in the history
  • Loading branch information
iafisher committed Mar 2, 2024
1 parent caae15d commit 09b5758
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## v0.2.2 (2 March 2024)
- Italicized links are now handled correctly. (https://github.com/iafisher/obsidian-quick-links/issues/3)

## v0.2.0 (3 September 2023)
- Support for live preview mode

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ To set up the plugin for development, clone this repository and create a symlink
After each rebuild, you will need to disable and reenable the plugin to load the latest code.

Since Obsidian only re-renders the changed parts of a file, you may need to close and reopen the file to see the rendered result of the latest code.

The plugin produces a lot of debugging output, so make sure that Debug/Verbose output isn't filtered out in the console logs.
65 changes: 49 additions & 16 deletions src/live-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class LivePreviewQuickLinksPluginValue implements PluginValue {
from,
to,
enter: (node: SyntaxNodeRef) => {
console.debug("Found node:", node.node.type.name);
nodes.push(node.node);
},
});
Expand All @@ -74,36 +75,40 @@ class LivePreviewQuickLinksPluginValue implements PluginValue {
if (settings.useWikiLinkSyntax) {
// e.g., "[[w:New York City]]"
const plainInternalLinkPattern = [
"formatting-link_formatting-link-start",
"hmd-internal-link",
"formatting-link_formatting-link-end",
["formatting-link_formatting-link-start", "em_formatting-link_formatting-link-start"],
["em_hmd-internal-link", "hmd-internal-link"],
["formatting-link_formatting-link-end", "em_formatting-link_formatting-link-end"],
];
console.debug("Searching for plain internal links");
for (const chunk of findChunks(nodes, plainInternalLinkPattern)) {
console.assert(chunk.length === 3);
const from = chunk[0].from;
const to = chunk[chunk.length - 1].to;
const target = view.state.sliceDoc(chunk[1].from, chunk[1].to);

const link = { text: "", target };

const link = { text: "", target, em: chunk[0].name.startsWith("em") };
console.debug("Found link (plain internal)", link);
this.handleLink(link, false, { from, to }, slices, quickLinksMap);
}

// e.g., "[[w:Los Angeles|L.A.]]"
const pipedInternalLinkPattern = [
"formatting-link_formatting-link-start",
["formatting-link_formatting-link-start", "em_formatting-link_formatting-link-start"],
"hmd-internal-link_link-has-alias",
"hmd-internal-link_link-alias-pipe",
"hmd-internal-link_link-alias",
"formatting-link_formatting-link-end",
["formatting-link_formatting-link-end", "em_formatting-link_formatting-link-end"],
];
console.debug("Searching for piped internal links");
for (const chunk of findChunks(nodes, pipedInternalLinkPattern)) {
console.assert(chunk.length === 5);
const from = chunk[0].from;
const to = chunk[chunk.length - 1].to;
const target = view.state.sliceDoc(chunk[1].from, chunk[1].to);
const text = view.state.sliceDoc(chunk[3].from, chunk[3].to);

const link = { text, target };

const link = { text, target, em: chunk[0].name.startsWith("em") };
console.debug("Found link (piped internal)", link);
this.handleLink(link, false, { from, to }, slices, quickLinksMap);
}
}
Expand All @@ -117,14 +122,16 @@ class LivePreviewQuickLinksPluginValue implements PluginValue {
"string_url",
"formatting_formatting-link-string_string_url",
];
console.debug("Searching for external links");
for (const chunk of findChunks(nodes, externalLinkPattern1)) {
console.assert(chunk.length === 6);
const from = chunk[0].from;
const to = chunk[chunk.length - 1].to;
const target = view.state.sliceDoc(chunk[4].from, chunk[4].to);
const text = view.state.sliceDoc(chunk[1].from, chunk[1].to);

const link = { text, target };
const link = { text, target, em: false };
console.debug("Found link (external)", link);
this.handleLink(link, true, { from, to }, slices, quickLinksMap);
}

Expand All @@ -135,14 +142,16 @@ class LivePreviewQuickLinksPluginValue implements PluginValue {
"string_url",
"formatting_formatting-link-string_string_url",
];
console.debug("Searching for external links (pattern2)");
for (const chunk of findChunks(nodes, externalLinkPattern2)) {
console.assert(chunk.length === 4);
const from = chunk[0].from;
const to = chunk[chunk.length - 1].to;
const target = view.state.sliceDoc(chunk[2].from, chunk[2].to);
const text = "";

const link = { text, target };
const link = { text, target, em: false };
console.debug("Found link (external)", link);
this.handleLink(link, true, { from, to }, slices, quickLinksMap);
}
}
Expand Down Expand Up @@ -184,7 +193,9 @@ class LivePreviewQuickLinksPluginValue implements PluginValue {
}
}

function findChunks(nodes: SyntaxNode[], pattern: string[]): SyntaxNode[][] {
type ChunkPattern = (string | string[])[];

function findChunks(nodes: SyntaxNode[], pattern: ChunkPattern): SyntaxNode[][] {
const chunks: SyntaxNode[][] = [];

for (let i = 0; i <= nodes.length - pattern.length; i++) {
Expand All @@ -197,16 +208,31 @@ function findChunks(nodes: SyntaxNode[], pattern: string[]): SyntaxNode[][] {
return chunks;
}

function doesChunkMatch(chunk: SyntaxNodeRef[], pattern: string[]) {
function doesChunkMatch(chunk: SyntaxNodeRef[], pattern: ChunkPattern): boolean {
for (let i = 0; i < chunk.length; i++) {
if (!chunk[i].name.startsWith(pattern[i])) {
return false;
if (typeof pattern[i] === "string") {
if (!chunk[i].name.startsWith(pattern[i] as string)) {
return false;
}
} else {
let matched = false;
for (const subpattern of pattern[i]) {
if (chunk[i].name.startsWith(subpattern)) {
matched = true;
break;
}
}

if (!matched) {
return false;
}
}
}

return true;
}


class QuickLinksWidget extends WidgetType {
private slice: QuickLinkSlice;

Expand All @@ -226,6 +252,13 @@ class QuickLinksWidget extends WidgetType {
el.setAttribute("href", this.slice.linkToInsert.target);
el.setAttribute("rel", "noopener");
el.setAttribute("target", "_blank");

if (this.slice.linkToInsert.em) {
const outer = document.createElement("em");
outer.appendChild(el);
return outer;
}

return el;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/quick-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const DEFAULT_QUICK_LINKS: QuickLinkMacro[] = [
export interface RawLink {
text: string;
target: string;
em: boolean,
}

export function transformLink(
Expand Down Expand Up @@ -50,7 +51,7 @@ export function transformLink(
: link.text;

const linkTarget = quickLink.target.replace("%s", linkHrefNoPrefix);
return { target: linkTarget, text: displayText };
return { target: linkTarget, text: displayText, em: link.em };
}

export function getLinkPrefix(linkHref: string): string {
Expand Down
2 changes: 1 addition & 1 deletion src/reading-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function markdownPostProcessor(

const linkHref = linkElement.getAttribute("href") ?? "";
const linkText = linkElement.innerText ?? "";
const rawLink = { text: linkText, target: linkHref };
const rawLink = { text: linkText, target: linkHref, em: false };
const maybeLink = transformLink(rawLink, quickLinksMap);
if (maybeLink !== null) {
context.addChild(new QuickLinkRenderChild(linkElement, maybeLink));
Expand Down

0 comments on commit 09b5758

Please sign in to comment.