diff --git a/.changeset/lovely-laws-return.md b/.changeset/lovely-laws-return.md
new file mode 100644
index 00000000..374a1105
--- /dev/null
+++ b/.changeset/lovely-laws-return.md
@@ -0,0 +1,5 @@
+---
+'@portaljs/remark-wiki-link': minor
+---
+
+Add image resize feature
diff --git a/package-lock.json b/package-lock.json
index 8690c7e5..2c0a090b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -49897,7 +49897,7 @@
},
"packages/components": {
"name": "@portaljs/components",
- "version": "0.5.10",
+ "version": "0.6.0",
"dependencies": {
"@githubocto/flat-ui": "^0.14.1",
"@heroicons/react": "^2.0.17",
@@ -50383,7 +50383,7 @@
},
"packages/remark-wiki-link": {
"name": "@portaljs/remark-wiki-link",
- "version": "1.1.2",
+ "version": "1.1.3",
"license": "MIT",
"dependencies": {
"mdast-util-to-markdown": "^1.5.0",
diff --git a/packages/remark-wiki-link/package.json b/packages/remark-wiki-link/package.json
index 2d6ec07c..ce91ee51 100644
--- a/packages/remark-wiki-link/package.json
+++ b/packages/remark-wiki-link/package.json
@@ -1,6 +1,6 @@
{
"name": "@portaljs/remark-wiki-link",
- "version": "1.1.2",
+ "version": "1.1.3",
"description": "Parse and render wiki-style links in markdown especially Obsidian style links.",
"repository": {
"type": "git",
diff --git a/packages/remark-wiki-link/src/lib/fromMarkdown.ts b/packages/remark-wiki-link/src/lib/fromMarkdown.ts
index 5b5f7b9c..08814224 100644
--- a/packages/remark-wiki-link/src/lib/fromMarkdown.ts
+++ b/packages/remark-wiki-link/src/lib/fromMarkdown.ts
@@ -1,23 +1,23 @@
-import { isSupportedFileFormat } from "./isSupportedFileFormat";
+import { isSupportedFileFormat } from './isSupportedFileFormat';
const defaultWikiLinkResolver = (target: string) => {
// for [[#heading]] links
if (!target) {
return [];
}
- let permalink = target.replace(/\/index$/, "");
+ let permalink = target.replace(/\/index$/, '');
// TODO what to do with [[index]] link?
if (permalink.length === 0) {
- permalink = "/";
+ permalink = '/';
}
return [permalink];
};
export interface FromMarkdownOptions {
pathFormat?:
- | "raw" // default; use for regular relative or absolute paths
- | "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
- | "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
+ | 'raw' // default; use for regular relative or absolute paths
+ | 'obsidian-absolute' // use for Obsidian-style absolute paths (with no leading slash)
+ | 'obsidian-short'; // use for Obsidian-style shortened paths (shortest path possible)
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
newClassName?: string; // class name to add to links that don't have a matching permalink
@@ -25,14 +25,23 @@ export interface FromMarkdownOptions {
hrefTemplate?: (permalink: string) => string; // function to generate the href attribute of a link
}
+export function getImageSize(size: string) {
+ // eslint-disable-next-line prefer-const
+ let [width, height] = size.split('x');
+
+ if (!height) height = width;
+
+ return { width, height };
+}
+
// mdas-util-from-markdown extension
// https://github.com/syntax-tree/mdast-util-from-markdown#extension
function fromMarkdown(opts: FromMarkdownOptions = {}) {
- const pathFormat = opts.pathFormat || "raw";
+ const pathFormat = opts.pathFormat || 'raw';
const permalinks = opts.permalinks || [];
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
- const newClassName = opts.newClassName || "new";
- const wikiLinkClassName = opts.wikiLinkClassName || "internal";
+ const newClassName = opts.newClassName || 'new';
+ const wikiLinkClassName = opts.wikiLinkClassName || 'internal';
const defaultHrefTemplate = (permalink: string) => permalink;
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
@@ -44,9 +53,9 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
function enterWikiLink(token) {
this.enter(
{
- type: "wikiLink",
+ type: 'wikiLink',
data: {
- isEmbed: token.isType === "embed",
+ isEmbed: token.isType === 'embed',
target: null, // the target of the link, e.g. "Foo Bar#Heading" in "[[Foo Bar#Heading]]"
alias: null, // the alias of the link, e.g. "Foo" in "[[Foo Bar|Foo]]"
permalink: null, // TODO shouldn't this be named just "link"?
@@ -80,18 +89,18 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
} = wikiLink;
// eslint-disable-next-line no-useless-escape
const wikiLinkWithHeadingPattern = /^(.*?)(#.*)?$/u;
- const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
+ const [, path, heading = ''] = target.match(wikiLinkWithHeadingPattern);
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
const matchingPermalink = permalinks.find((e) => {
return possibleWikiLinkPermalinks.find((p) => {
- if (pathFormat === "obsidian-short") {
+ if (pathFormat === 'obsidian-short') {
if (e === p || e.endsWith(p)) {
return true;
}
- } else if (pathFormat === "obsidian-absolute") {
- if (e === "/" + p) {
+ } else if (pathFormat === 'obsidian-absolute') {
+ if (e === '/' + p) {
return true;
}
} else {
@@ -106,20 +115,19 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
// TODO this is ugly
const link =
matchingPermalink ||
- (pathFormat === "obsidian-absolute"
- ? "/" + possibleWikiLinkPermalinks[0]
+ (pathFormat === 'obsidian-absolute'
+ ? '/' + possibleWikiLinkPermalinks[0]
: possibleWikiLinkPermalinks[0]) ||
- "";
+ '';
wikiLink.data.exists = !!matchingPermalink;
wikiLink.data.permalink = link;
-
// remove leading # if the target is a heading on the same page
- const displayName = alias || target.replace(/^#/, "");
- const headingId = heading.replace(/\s+/g, "-").toLowerCase();
+ const displayName = alias || target.replace(/^#/, '');
+ const headingId = heading.replace(/\s+/g, '-').toLowerCase();
let classNames = wikiLinkClassName;
if (!matchingPermalink) {
- classNames += " " + newClassName;
+ classNames += ' ' + newClassName;
}
if (isEmbed) {
@@ -127,44 +135,55 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
if (!isSupportedFormat) {
// Temporarily render note transclusion as a regular wiki link
if (!format) {
- wikiLink.data.hName = "a";
+ wikiLink.data.hName = 'a';
wikiLink.data.hProperties = {
- className: classNames + " " + "transclusion",
+ className: classNames + ' ' + 'transclusion',
href: hrefTemplate(link) + headingId,
};
- wikiLink.data.hChildren = [{ type: "text", value: displayName }];
-
+ wikiLink.data.hChildren = [{ type: 'text', value: displayName }];
} else {
- wikiLink.data.hName = "p";
+ wikiLink.data.hName = 'p';
wikiLink.data.hChildren = [
{
- type: "text",
+ type: 'text',
value: `![[${target}]]`,
},
];
}
- } else if (format === "pdf") {
- wikiLink.data.hName = "iframe";
+ } else if (format === 'pdf') {
+ wikiLink.data.hName = 'iframe';
wikiLink.data.hProperties = {
className: classNames,
- width: "100%",
+ width: '100%',
src: `${hrefTemplate(link)}#toolbar=0`,
};
} else {
- wikiLink.data.hName = "img";
- wikiLink.data.hProperties = {
- className: classNames,
- src: hrefTemplate(link),
- alt: displayName,
- };
+ const hasDimensions = alias && /^\d+(x\d+)?$/.test(alias);
+ // Take the target as alt text except if alt name was provided [[target|alt text]]
+ const altText = hasDimensions || !alias ? target : alias;
+
+ wikiLink.data.hName = 'img';
+ wikiLink.data.hProperties = {
+ className: classNames,
+ src: hrefTemplate(link),
+ alt: altText
+ };
+
+ if (hasDimensions) {
+ const { width, height } = getImageSize(alias as string);
+ Object.assign(wikiLink.data.hProperties, {
+ width,
+ height,
+ });
+ }
}
} else {
- wikiLink.data.hName = "a";
+ wikiLink.data.hName = 'a';
wikiLink.data.hProperties = {
className: classNames,
href: hrefTemplate(link) + headingId,
};
- wikiLink.data.hChildren = [{ type: "text", value: displayName }];
+ wikiLink.data.hChildren = [{ type: 'text', value: displayName }];
}
}
diff --git a/packages/remark-wiki-link/src/lib/html.ts b/packages/remark-wiki-link/src/lib/html.ts
index 1058652f..a95c0c6a 100644
--- a/packages/remark-wiki-link/src/lib/html.ts
+++ b/packages/remark-wiki-link/src/lib/html.ts
@@ -1,23 +1,24 @@
-import { isSupportedFileFormat } from "./isSupportedFileFormat";
+import { getImageSize } from './fromMarkdown';
+import { isSupportedFileFormat } from './isSupportedFileFormat';
const defaultWikiLinkResolver = (target: string) => {
// for [[#heading]] links
if (!target) {
return [];
}
- let permalink = target.replace(/\/index$/, "");
+ let permalink = target.replace(/\/index$/, '');
// TODO what to do with [[index]] link?
if (permalink.length === 0) {
- permalink = "/";
+ permalink = '/';
}
return [permalink];
};
export interface HtmlOptions {
pathFormat?:
- | "raw" // default; use for regular relative or absolute paths
- | "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
- | "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
+ | 'raw' // default; use for regular relative or absolute paths
+ | 'obsidian-absolute' // use for Obsidian-style absolute paths (with no leading slash)
+ | 'obsidian-short'; // use for Obsidian-style shortened paths (shortest path possible)
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
newClassName?: string; // class name to add to links that don't have a matching permalink
@@ -28,11 +29,11 @@ export interface HtmlOptions {
// Micromark HtmlExtension
// https://github.com/micromark/micromark#htmlextension
function html(opts: HtmlOptions = {}) {
- const pathFormat = opts.pathFormat || "raw";
+ const pathFormat = opts.pathFormat || 'raw';
const permalinks = opts.permalinks || [];
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
- const newClassName = opts.newClassName || "new";
- const wikiLinkClassName = opts.wikiLinkClassName || "internal";
+ const newClassName = opts.newClassName || 'new';
+ const wikiLinkClassName = opts.wikiLinkClassName || 'internal';
const defaultHrefTemplate = (permalink: string) => permalink;
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
@@ -41,21 +42,21 @@ function html(opts: HtmlOptions = {}) {
}
function enterWikiLink() {
- let stack = this.getData("wikiLinkStack");
- if (!stack) this.setData("wikiLinkStack", (stack = []));
+ let stack = this.getData('wikiLinkStack');
+ if (!stack) this.setData('wikiLinkStack', (stack = []));
stack.push({});
}
function exitWikiLinkTarget(token) {
const target = this.sliceSerialize(token);
- const current = top(this.getData("wikiLinkStack"));
+ const current = top(this.getData('wikiLinkStack'));
current.target = target;
}
function exitWikiLinkAlias(token) {
const alias = this.sliceSerialize(token);
- const current = top(this.getData("wikiLinkStack"));
+ const current = top(this.getData('wikiLinkStack'));
current.alias = alias;
}
@@ -111,7 +112,9 @@ function html(opts: HtmlOptions = {}) {
// Temporarily render note transclusion as a regular wiki link
if (!format) {
this.tag(
- ``
+ ``
);
this.raw(displayName);
this.tag("");
@@ -125,11 +128,18 @@ function html(opts: HtmlOptions = {}) {
)}#toolbar=0" class="${classNames}" />`
);
} else {
- this.tag(
- ``
- );
+ const hasDimensions = alias && /^\d+(x\d+)?$/.test(alias);
+ // Take the target as alt text except if alt name was provided [[target|alt text]]
+ const altText = hasDimensions || !alias ? target : alias;
+ let imgAttributes = `src="${hrefTemplate(
+ link
+ )}" alt="${altText}" class="${classNames}"`;
+
+ if (hasDimensions) {
+ const { width, height } = getImageSize(alias as string);
+ imgAttributes += ` width="${width}" height="${height}"`;
+ }
+ this.tag(`
`);
}
} else {
this.tag(
diff --git a/packages/remark-wiki-link/test/micromarkExtensionWikiLink.spec.ts b/packages/remark-wiki-link/test/micromarkExtensionWikiLink.spec.ts
index 663b885c..5f2f9e8e 100644
--- a/packages/remark-wiki-link/test/micromarkExtensionWikiLink.spec.ts
+++ b/packages/remark-wiki-link/test/micromarkExtensionWikiLink.spec.ts
@@ -48,7 +48,7 @@ describe("micromark-extension-wiki-link", () => {
html({
permalinks: ["/some/folder/Wiki Link"],
pathFormat: "obsidian-short",
- }) as any // TODO type fix
+ }) as any, // TODO type fix
],
});
expect(serialized).toBe(
@@ -75,7 +75,7 @@ describe("micromark-extension-wiki-link", () => {
html({
permalinks: ["/some/folder/Wiki Link"],
pathFormat: "obsidian-absolute",
- }) as any // TODO type fix
+ }) as any, // TODO type fix
],
});
expect(serialized).toBe(
@@ -97,10 +97,14 @@ describe("micromark-extension-wiki-link", () => {
});
test("parses a wiki link with heading and alias", () => {
- const serialized = micromark("[[Wiki Link#Some Heading|Alias]]", "ascii", {
- extensions: [syntax()],
- htmlExtensions: [html() as any], // TODO type fix
- });
+ const serialized = micromark(
+ "[[Wiki Link#Some Heading|Alias]]",
+ "ascii",
+ {
+ extensions: [syntax()],
+ htmlExtensions: [html() as any], // TODO type fix
+ }
+ );
// note: lowercased and hyphenated heading
expect(serialized).toBe(
'
![[My Image.xyz]]
"); + expect(serialized).toBe('![[My Image.xyz]]
'); }); test("parses and image ambed with a matching permalink", () => { @@ -147,6 +151,28 @@ describe("micromark-extension-wiki-link", () => { ); }); + // TODO: Fix alt attribute + test("Can identify the dimensions of the image if exists", () => { + const serialized = micromark("![[My Image.jpg|200]]", "ascii", { + extensions: [syntax()], + htmlExtensions: [html({ permalinks: ["My Image.jpg"] }) as any], // TODO type fix + }); + expect(serialized).toBe( + '

[[Wiki Link
"); + expect(serialized).toBe('[[Wiki Link
'); }); test("doesn't parse a wiki link with one missing closing bracket", () => { @@ -197,7 +223,7 @@ describe("micromark-extension-wiki-link", () => { extensions: [syntax()], htmlExtensions: [html() as any], // TODO type fix }); - expect(serialized).toBe("[[Wiki Link]
"); + expect(serialized).toBe('[[Wiki Link]
'); }); test("doesn't parse a wiki link with a missing opening bracket", () => { @@ -205,7 +231,7 @@ describe("micromark-extension-wiki-link", () => { extensions: [syntax()], htmlExtensions: [html() as any], // TODO type fix }); - expect(serialized).toBe("[Wiki Link]]
"); + expect(serialized).toBe('[Wiki Link]]
'); }); test("doesn't parse a wiki link in single brackets", () => { @@ -213,7 +239,7 @@ describe("micromark-extension-wiki-link", () => { extensions: [syntax()], htmlExtensions: [html() as any], // TODO type fix }); - expect(serialized).toBe("[Wiki Link]
"); + expect(serialized).toBe('[Wiki Link]
'); }); }); @@ -225,7 +251,7 @@ describe("micromark-extension-wiki-link", () => { html({ newClassName: "test-new", wikiLinkClassName: "test-wiki-link", - }) as any // TODO type fix + }) as any, // TODO type fix ], }); expect(serialized).toBe( @@ -251,7 +277,7 @@ describe("micromark-extension-wiki-link", () => { wikiLinkResolver: (page) => [ page.replace(/\s+/, "-").toLowerCase(), ], - }) as any // TODO type fix + }) as any, // TODO type fix ], }); expect(serialized).toBe( @@ -330,5 +356,5 @@ describe("micromark-extension-wiki-link", () => { }); expect(serialized).toBe(``); }); - }) + }); }); diff --git a/packages/remark-wiki-link/test/remarkWikiLink.spec.ts b/packages/remark-wiki-link/test/remarkWikiLink.spec.ts index 276038fc..051cc42c 100644 --- a/packages/remark-wiki-link/test/remarkWikiLink.spec.ts +++ b/packages/remark-wiki-link/test/remarkWikiLink.spec.ts @@ -246,6 +246,28 @@ describe("remark-wiki-link", () => { expect(node.data?.hName).toEqual("img"); expect((node.data?.hProperties as any).src).toEqual("My Image.png"); expect((node.data?.hProperties as any).alt).toEqual("My Image.png"); + expect((node.data?.hProperties as any).width).toBeUndefined(); + expect((node.data?.hProperties as any).height).toBeUndefined(); + }); + }); + + test("Can identify the dimensions of the image if exists", () => { + const processor = unified().use(markdown).use(wikiLinkPlugin); + + let ast = processor.parse("![[My Image.png|132x612]]"); + ast = processor.runSync(ast); + + expect(select("wikiLink", ast)).not.toEqual(null); + + visit(ast, "wikiLink", (node: Node) => { + expect(node.data?.isEmbed).toEqual(true); + expect(node.data?.target).toEqual("My Image.png"); + expect(node.data?.permalink).toEqual("My Image.png"); + expect(node.data?.hName).toEqual("img"); + expect((node.data?.hProperties as any).src).toEqual("My Image.png"); + expect((node.data?.hProperties as any).alt).toEqual("My Image.png"); + expect((node.data?.hProperties as any).width).toBe("132"); + expect((node.data?.hProperties as any).height).toBe("612"); }); }); @@ -365,13 +387,17 @@ describe("remark-wiki-link", () => { test("parses a link with special characters and symbols", () => { const processor = unified().use(markdown).use(wikiLinkPlugin); - let ast = processor.parse("[[li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\#li-nk-w(i)th-àcèô íã_a(n)D_UNDERLINE!:ª%@'*º$ °~./\\]]"); + let ast = processor.parse( + "[[li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\#li-nk-w(i)th-àcèô íã_a(n)D_UNDERLINE!:ª%@'*º$ °~./\\]]" + ); ast = processor.runSync(ast); expect(select("wikiLink", ast)).not.toEqual(null); visit(ast, "wikiLink", (node: Node) => { expect(node.data?.exists).toEqual(false); - expect(node.data?.permalink).toEqual("li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\"); + expect(node.data?.permalink).toEqual( + "li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\" + ); expect(node.data?.alias).toEqual(null); expect(node.data?.hName).toEqual("a"); expect((node.data?.hProperties as any).className).toEqual( @@ -383,9 +409,9 @@ describe("remark-wiki-link", () => { expect((node.data?.hChildren as any)[0].value).toEqual( "li nk-w(i)th-àcèô íã_a(n)d_underline!:ª%@'*º$ °~./\\#li-nk-w(i)th-àcèô íã_a(n)D_UNDERLINE!:ª%@'*º$ °~./\\" ); - }) + }); }); - }) + }); describe("invalid wiki links", () => { test("doesn't parse a wiki link with two missing closing brackets", () => { @@ -586,4 +612,3 @@ describe("remark-wiki-link", () => { }); }); }); -