Skip to content

Commit

Permalink
feat(icon-packs): support inline svg render
Browse files Browse the repository at this point in the history
- add span warpper for all icon from `PackManager.getIcon`, including emoji
- add `isc-emoji(img/svg)-icon` class for warpper
- add new async method `PackManager.getSVGIcon`to get inline svg element or svg content

close #23
  • Loading branch information
aidenlx committed Mar 9, 2022
1 parent c197d21 commit e9cd3c1
Show file tree
Hide file tree
Showing 15 changed files with 320 additions and 156 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@lezer/common": "^0.15.11",
"@release-it/bumper": "^3.0.1",
"@release-it/conventional-changelog": "^3.3.0",
"@types/dompurify": "^2.3.3",
"@types/json-schema": "^7.0.9",
"@types/jszip": "^3.4.1",
"@types/mime-types": "^2.1.1",
Expand Down
20 changes: 10 additions & 10 deletions src/icon-in-editor/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ export default class IconWidget extends WidgetType {
}

toDOM(view: EditorView) {
const icon = this.plugin.packManager.getIcon(this.id);
let wrap = createSpan({
cls: cls("cm-isc", {
"cm-isc-emoji": typeof icon === "string",
"cm-isc-img": icon instanceof HTMLImageElement,
}),
cls: "cm-isc-icon",
attr: { "aria-label": this.id.replace(/_/g, " ") },
});
if (icon) {
wrap.append(icon);
} else {
wrap.append(`:${this.id}:`);
}

this.plugin.packManager.getSVGIcon(this.id).then((span) => {
if (!span) {
wrap.append(`:${this.id}:`);
} else {
span.classList.forEach((cls) => wrap.addClass(cls));
wrap.replaceChildren(...span.childNodes);
}
});
return wrap;
}

Expand Down
28 changes: 23 additions & 5 deletions src/icon-packs/built-ins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { setIcon } from "obsidian";

import { LucideIcon, ObsidianIcon } from "../icons";
import { BultiInIconData as BultiInIconDataType, IconInfo } from "./types";
import { getClsForIcon } from "./utils";

const kabobToSnake = (name: string) => name.replace(/-/g, "_");

Expand All @@ -12,6 +13,10 @@ const LucidePackName = "luc",

export type SVGPacknames = typeof LucidePackName | typeof ObsidianPackName;

const removeBultiInIconAttrs = (el: HTMLElement) =>
["class", "height", "width"].forEach((k) =>
el.firstElementChild?.removeAttribute(k),
);
class BultiInIconData implements BultiInIconDataType {
public type = "bulti-in" as const;
public name: string;
Expand All @@ -27,24 +32,37 @@ class BultiInIconData implements BultiInIconDataType {
el,
(this.pack === LucidePackName ? "lucide-" : "") + this.obsidianId,
);
["class", "height", "width"].forEach((k) =>
el.firstElementChild?.removeAttribute(k),
);
removeBultiInIconAttrs(el);
el.firstElementChild?.setAttribute("xmlns", "http://www.w3.org/2000/svg");
return el.innerHTML;
}
public get dataUri() {
return svg2uri(this.data);
}
public getDOM(svg: boolean): HTMLSpanElement {
const el = createSpan({ cls: getClsForIcon(this) });
if (svg) {
el.addClass("isc-svg-icon");
setIcon(
el,
(this.pack === LucidePackName ? "lucide-" : "") + this.obsidianId,
);
removeBultiInIconAttrs(el);
} else {
el.addClass("isc-img-icon");
el.createEl("img", { attr: { src: this.dataUri } });
}
return el;
}
}

const EMOJI_PACK_NAME = "emoji";
const getBuiltIns = (): {
packs: ReadonlyMap<string, BultiInIconDataType>;
packs: ReadonlyMap<string, BultiInIconData>;
ids: ReadonlyArray<IconInfo>;
packnames: ReadonlyArray<string>;
} => {
let packs = new Map<string, BultiInIconDataType>(),
let packs = new Map<string, BultiInIconData>(),
ids = [] as IconInfo[],
packnames = [] as string[];

Expand Down
76 changes: 76 additions & 0 deletions src/icon-packs/file-icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { FileSystemAdapter } from "obsidian";
import IconSC from "../isc-main";
import { FileIconData as FileIconDataType, FileIconInfo } from "./types";
import { extname } from "path";

import { getClsForIcon, getPacknNameFromId } from "./utils";

export class FileIconData implements FileIconDataType {
static getData(
id: string,
path: string,
plugin: IconSC,
): FileIconData | null {
const result = getPacknNameFromId(id);
if (!result) return null;
return new FileIconData(id, result.name, result.pack, path, plugin);
}

public type = "file" as const;
public path: string;
constructor(
private _id: string,
private _name: string,
private _pack: string,
path: string,
private plugin: IconSC,
) {
this.path = path.trim();
}
private get vault() {
return this.plugin.app.vault;
}

public get id() {
return this._id;
}
public get pack() {
return this._pack;
}
public get name() {
return this._name;
}
public get ext() {
return extname(this.path);
}
public get fsPath() {
if (this.vault.adapter instanceof FileSystemAdapter) {
return this.vault.adapter.getFullPath(this.path);
} else return null;
}
public get resourcePath() {
return this.vault.adapter.getResourcePath(this.path);
}
public getDOM(svg: true): Promise<HTMLSpanElement>;
public getDOM(svg: false): HTMLSpanElement;
public getDOM(svg: boolean): Promise<HTMLSpanElement> | HTMLSpanElement {
const el = createSpan({ cls: getClsForIcon(this) });
if (svg && this.ext === ".svg") {
el.addClass("isc-svg-icon");
return (async () => {
const svgEl = await this.plugin.fileIconCache.getIcon(this.path);
if (svgEl) {
el.append(svgEl);
svgEl.addClass("isc-svg-icon");
} else {
console.error("failed to get icon data for", this.path);
}
return el;
})();
} else {
el.addClass("isc-img-icon");
el.createEl("img", { attr: { src: this.resourcePath } });
return el;
}
}
}
64 changes: 64 additions & 0 deletions src/icon-packs/icon-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Stat } from "obsidian";
import type IconSC from "../isc-main";

declare global {
const DOMPurify: typeof import("dompurify");
}

interface IconCache {
ctime: number;
mtime: number;
size: number;
svg: SVGElement;
}

export default class FileIconCache {
constructor(public plugin: IconSC) {}
private get vault() {
return this.plugin.app.vault;
}
private cache = new Map<string, IconCache>();
async getIcon(normalizedPath: string): Promise<SVGElement | null> {
const stat = await this.vault.adapter.stat(normalizedPath);
if (!stat || stat.type !== "file") return null;
if (this.cache.has(normalizedPath)) {
const cache = this.cache.get(normalizedPath)!;
if (
cache.ctime === stat.ctime &&
cache.mtime === stat.mtime &&
cache.size === stat.size
) {
return cache.svg.cloneNode(true) as SVGElement;
}
}
const svg = await this.readIntoCache(normalizedPath, stat);
return svg.cloneNode(true) as SVGElement;
}
private async readIntoCache(
normalizedPath: string,
stat: Stat,
): Promise<SVGElement> {
const data = DOMPurify.sanitize(
await this.vault.adapter.read(normalizedPath),
),
svg = new DOMParser().parseFromString(data, "image/svg+xml")
.documentElement as unknown as SVGElement;
this.cache.set(normalizedPath, { ...stat, svg });
return svg;
}
refresh() {
const refresh = async (path: string) => {
const stat = await this.vault.adapter.stat(path);
if (!stat || stat.type !== "file") {
this.cache.delete(path);
} else {
await this.readIntoCache(path, stat);
}
return path;
};
return Promise.allSettled([...this.cache.keys()].map(refresh));
}
clear() {
this.cache.clear();
}
}
35 changes: 14 additions & 21 deletions src/icon-packs/icon.less
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,20 @@
cursor: pointer;
}

img.isc-icon {
display: inline;
height: 1em;
width: 1em;
vertical-align: text-top;
cursor: default;
.view-content .mod-cm6 .cm-isc > & {
cursor: pointer;
}
.markdown-preview-view &,
.markdown-source-view & {
height: var(--editor-font-size);
width: var(--editor-font-size);
}
.theme-dark & {
&.isc-fab,
&.isc-far,
&.isc-fas,
&.isc-rif,
&.isc-ril {
filter: invert(1);
span.isc-icon {
& > img,
& > svg {
height: 1em;
width: 1em;
vertical-align: text-top;
cursor: default !important;
.view-content .mod-cm6 .cm-isc > & {
cursor: pointer;
}
.markdown-preview-view &,
.markdown-source-view & {
height: var(--editor-font-size);
width: var(--editor-font-size);
}
}
}
Loading

0 comments on commit e9cd3c1

Please sign in to comment.