Skip to content

Commit 57952e0

Browse files
[remark-wiki-link][m]: Add image size adjustment in remark-wiki-link (#1084)
* [remark-wiki-link][m]: Add image size adjustment in `remark-wiki-link` * [remark-wiki-links][m]: Add image size feature to images
1 parent 852cf60 commit 57952e0

File tree

7 files changed

+166
-81
lines changed

7 files changed

+166
-81
lines changed

.changeset/lovely-laws-return.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@portaljs/remark-wiki-link': minor
3+
---
4+
5+
Add image resize feature

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/remark-wiki-link/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@portaljs/remark-wiki-link",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"description": "Parse and render wiki-style links in markdown especially Obsidian style links.",
55
"repository": {
66
"type": "git",

packages/remark-wiki-link/src/lib/fromMarkdown.ts

+58-39
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,47 @@
1-
import { isSupportedFileFormat } from "./isSupportedFileFormat";
1+
import { isSupportedFileFormat } from './isSupportedFileFormat';
22

33
const defaultWikiLinkResolver = (target: string) => {
44
// for [[#heading]] links
55
if (!target) {
66
return [];
77
}
8-
let permalink = target.replace(/\/index$/, "");
8+
let permalink = target.replace(/\/index$/, '');
99
// TODO what to do with [[index]] link?
1010
if (permalink.length === 0) {
11-
permalink = "/";
11+
permalink = '/';
1212
}
1313
return [permalink];
1414
};
1515

1616
export interface FromMarkdownOptions {
1717
pathFormat?:
18-
| "raw" // default; use for regular relative or absolute paths
19-
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
20-
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
18+
| 'raw' // default; use for regular relative or absolute paths
19+
| 'obsidian-absolute' // use for Obsidian-style absolute paths (with no leading slash)
20+
| 'obsidian-short'; // use for Obsidian-style shortened paths (shortest path possible)
2121
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
2222
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
2323
newClassName?: string; // class name to add to links that don't have a matching permalink
2424
wikiLinkClassName?: string; // class name to add to all wiki links
2525
hrefTemplate?: (permalink: string) => string; // function to generate the href attribute of a link
2626
}
2727

28+
export function getImageSize(size: string) {
29+
// eslint-disable-next-line prefer-const
30+
let [width, height] = size.split('x');
31+
32+
if (!height) height = width;
33+
34+
return { width, height };
35+
}
36+
2837
// mdas-util-from-markdown extension
2938
// https://github.com/syntax-tree/mdast-util-from-markdown#extension
3039
function fromMarkdown(opts: FromMarkdownOptions = {}) {
31-
const pathFormat = opts.pathFormat || "raw";
40+
const pathFormat = opts.pathFormat || 'raw';
3241
const permalinks = opts.permalinks || [];
3342
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
34-
const newClassName = opts.newClassName || "new";
35-
const wikiLinkClassName = opts.wikiLinkClassName || "internal";
43+
const newClassName = opts.newClassName || 'new';
44+
const wikiLinkClassName = opts.wikiLinkClassName || 'internal';
3645
const defaultHrefTemplate = (permalink: string) => permalink;
3746

3847
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
@@ -44,9 +53,9 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
4453
function enterWikiLink(token) {
4554
this.enter(
4655
{
47-
type: "wikiLink",
56+
type: 'wikiLink',
4857
data: {
49-
isEmbed: token.isType === "embed",
58+
isEmbed: token.isType === 'embed',
5059
target: null, // the target of the link, e.g. "Foo Bar#Heading" in "[[Foo Bar#Heading]]"
5160
alias: null, // the alias of the link, e.g. "Foo" in "[[Foo Bar|Foo]]"
5261
permalink: null, // TODO shouldn't this be named just "link"?
@@ -80,18 +89,18 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
8089
} = wikiLink;
8190
// eslint-disable-next-line no-useless-escape
8291
const wikiLinkWithHeadingPattern = /^(.*?)(#.*)?$/u;
83-
const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
92+
const [, path, heading = ''] = target.match(wikiLinkWithHeadingPattern);
8493

8594
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
8695

8796
const matchingPermalink = permalinks.find((e) => {
8897
return possibleWikiLinkPermalinks.find((p) => {
89-
if (pathFormat === "obsidian-short") {
98+
if (pathFormat === 'obsidian-short') {
9099
if (e === p || e.endsWith(p)) {
91100
return true;
92101
}
93-
} else if (pathFormat === "obsidian-absolute") {
94-
if (e === "/" + p) {
102+
} else if (pathFormat === 'obsidian-absolute') {
103+
if (e === '/' + p) {
95104
return true;
96105
}
97106
} else {
@@ -106,65 +115,75 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
106115
// TODO this is ugly
107116
const link =
108117
matchingPermalink ||
109-
(pathFormat === "obsidian-absolute"
110-
? "/" + possibleWikiLinkPermalinks[0]
118+
(pathFormat === 'obsidian-absolute'
119+
? '/' + possibleWikiLinkPermalinks[0]
111120
: possibleWikiLinkPermalinks[0]) ||
112-
"";
121+
'';
113122

114123
wikiLink.data.exists = !!matchingPermalink;
115124
wikiLink.data.permalink = link;
116-
117125
// remove leading # if the target is a heading on the same page
118-
const displayName = alias || target.replace(/^#/, "");
119-
const headingId = heading.replace(/\s+/g, "-").toLowerCase();
126+
const displayName = alias || target.replace(/^#/, '');
127+
const headingId = heading.replace(/\s+/g, '-').toLowerCase();
120128
let classNames = wikiLinkClassName;
121129
if (!matchingPermalink) {
122-
classNames += " " + newClassName;
130+
classNames += ' ' + newClassName;
123131
}
124132

125133
if (isEmbed) {
126134
const [isSupportedFormat, format] = isSupportedFileFormat(target);
127135
if (!isSupportedFormat) {
128136
// Temporarily render note transclusion as a regular wiki link
129137
if (!format) {
130-
wikiLink.data.hName = "a";
138+
wikiLink.data.hName = 'a';
131139
wikiLink.data.hProperties = {
132-
className: classNames + " " + "transclusion",
140+
className: classNames + ' ' + 'transclusion',
133141
href: hrefTemplate(link) + headingId,
134142
};
135-
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
136-
143+
wikiLink.data.hChildren = [{ type: 'text', value: displayName }];
137144
} else {
138-
wikiLink.data.hName = "p";
145+
wikiLink.data.hName = 'p';
139146
wikiLink.data.hChildren = [
140147
{
141-
type: "text",
148+
type: 'text',
142149
value: `![[${target}]]`,
143150
},
144151
];
145152
}
146-
} else if (format === "pdf") {
147-
wikiLink.data.hName = "iframe";
153+
} else if (format === 'pdf') {
154+
wikiLink.data.hName = 'iframe';
148155
wikiLink.data.hProperties = {
149156
className: classNames,
150-
width: "100%",
157+
width: '100%',
151158
src: `${hrefTemplate(link)}#toolbar=0`,
152159
};
153160
} else {
154-
wikiLink.data.hName = "img";
155-
wikiLink.data.hProperties = {
156-
className: classNames,
157-
src: hrefTemplate(link),
158-
alt: displayName,
159-
};
161+
const hasDimensions = alias && /^\d+(x\d+)?$/.test(alias);
162+
// Take the target as alt text except if alt name was provided [[target|alt text]]
163+
const altText = hasDimensions || !alias ? target : alias;
164+
165+
wikiLink.data.hName = 'img';
166+
wikiLink.data.hProperties = {
167+
className: classNames,
168+
src: hrefTemplate(link),
169+
alt: altText
170+
};
171+
172+
if (hasDimensions) {
173+
const { width, height } = getImageSize(alias as string);
174+
Object.assign(wikiLink.data.hProperties, {
175+
width,
176+
height,
177+
});
178+
}
160179
}
161180
} else {
162-
wikiLink.data.hName = "a";
181+
wikiLink.data.hName = 'a';
163182
wikiLink.data.hProperties = {
164183
className: classNames,
165184
href: hrefTemplate(link) + headingId,
166185
};
167-
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
186+
wikiLink.data.hChildren = [{ type: 'text', value: displayName }];
168187
}
169188
}
170189

packages/remark-wiki-link/src/lib/html.ts

+29-19
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1-
import { isSupportedFileFormat } from "./isSupportedFileFormat";
1+
import { getImageSize } from './fromMarkdown';
2+
import { isSupportedFileFormat } from './isSupportedFileFormat';
23

34
const defaultWikiLinkResolver = (target: string) => {
45
// for [[#heading]] links
56
if (!target) {
67
return [];
78
}
8-
let permalink = target.replace(/\/index$/, "");
9+
let permalink = target.replace(/\/index$/, '');
910
// TODO what to do with [[index]] link?
1011
if (permalink.length === 0) {
11-
permalink = "/";
12+
permalink = '/';
1213
}
1314
return [permalink];
1415
};
1516

1617
export interface HtmlOptions {
1718
pathFormat?:
18-
| "raw" // default; use for regular relative or absolute paths
19-
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
20-
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
19+
| 'raw' // default; use for regular relative or absolute paths
20+
| 'obsidian-absolute' // use for Obsidian-style absolute paths (with no leading slash)
21+
| 'obsidian-short'; // use for Obsidian-style shortened paths (shortest path possible)
2122
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
2223
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
2324
newClassName?: string; // class name to add to links that don't have a matching permalink
@@ -28,11 +29,11 @@ export interface HtmlOptions {
2829
// Micromark HtmlExtension
2930
// https://github.com/micromark/micromark#htmlextension
3031
function html(opts: HtmlOptions = {}) {
31-
const pathFormat = opts.pathFormat || "raw";
32+
const pathFormat = opts.pathFormat || 'raw';
3233
const permalinks = opts.permalinks || [];
3334
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
34-
const newClassName = opts.newClassName || "new";
35-
const wikiLinkClassName = opts.wikiLinkClassName || "internal";
35+
const newClassName = opts.newClassName || 'new';
36+
const wikiLinkClassName = opts.wikiLinkClassName || 'internal';
3637
const defaultHrefTemplate = (permalink: string) => permalink;
3738
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
3839

@@ -41,21 +42,21 @@ function html(opts: HtmlOptions = {}) {
4142
}
4243

4344
function enterWikiLink() {
44-
let stack = this.getData("wikiLinkStack");
45-
if (!stack) this.setData("wikiLinkStack", (stack = []));
45+
let stack = this.getData('wikiLinkStack');
46+
if (!stack) this.setData('wikiLinkStack', (stack = []));
4647

4748
stack.push({});
4849
}
4950

5051
function exitWikiLinkTarget(token) {
5152
const target = this.sliceSerialize(token);
52-
const current = top(this.getData("wikiLinkStack"));
53+
const current = top(this.getData('wikiLinkStack'));
5354
current.target = target;
5455
}
5556

5657
function exitWikiLinkAlias(token) {
5758
const alias = this.sliceSerialize(token);
58-
const current = top(this.getData("wikiLinkStack"));
59+
const current = top(this.getData('wikiLinkStack'));
5960
current.alias = alias;
6061
}
6162

@@ -111,7 +112,9 @@ function html(opts: HtmlOptions = {}) {
111112
// Temporarily render note transclusion as a regular wiki link
112113
if (!format) {
113114
this.tag(
114-
`<a href="${hrefTemplate(link + headingId)}" class="${classNames} transclusion">`
115+
`<a href="${hrefTemplate(
116+
link + headingId
117+
)}" class="${classNames} transclusion">`
115118
);
116119
this.raw(displayName);
117120
this.tag("</a>");
@@ -125,11 +128,18 @@ function html(opts: HtmlOptions = {}) {
125128
)}#toolbar=0" class="${classNames}" />`
126129
);
127130
} else {
128-
this.tag(
129-
`<img src="${hrefTemplate(
130-
link
131-
)}" alt="${displayName}" class="${classNames}" />`
132-
);
131+
const hasDimensions = alias && /^\d+(x\d+)?$/.test(alias);
132+
// Take the target as alt text except if alt name was provided [[target|alt text]]
133+
const altText = hasDimensions || !alias ? target : alias;
134+
let imgAttributes = `src="${hrefTemplate(
135+
link
136+
)}" alt="${altText}" class="${classNames}"`;
137+
138+
if (hasDimensions) {
139+
const { width, height } = getImageSize(alias as string);
140+
imgAttributes += ` width="${width}" height="${height}"`;
141+
}
142+
this.tag(`<img ${imgAttributes} />`);
133143
}
134144
} else {
135145
this.tag(

0 commit comments

Comments
 (0)