@riebeckite/plugin-icons 0.0.34 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +120 -110
- package/dist/src/icon_packs.d.ts +15 -0
- package/dist/src/settings.d.ts +3 -15
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -417,30 +417,30 @@ function readSource(source, target, manual) {
|
|
|
417
417
|
exists: found !== void 0
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
|
-
const
|
|
420
|
+
const path4 = target.path;
|
|
421
421
|
switch (source) {
|
|
422
422
|
case "icon":
|
|
423
423
|
return { kind: "string", value: manual?.icon ?? "" };
|
|
424
424
|
case "color":
|
|
425
425
|
return { kind: "string", value: manual?.color ?? "" };
|
|
426
426
|
case "name": {
|
|
427
|
-
const name =
|
|
427
|
+
const name = path4.split("/").pop() ?? path4;
|
|
428
428
|
return {
|
|
429
429
|
kind: "string",
|
|
430
430
|
value: name.replace(/\.[^.]+$/, "")
|
|
431
431
|
};
|
|
432
432
|
}
|
|
433
433
|
case "filename":
|
|
434
|
-
return { kind: "string", value:
|
|
434
|
+
return { kind: "string", value: path4.split("/").pop() ?? path4 };
|
|
435
435
|
case "extension": {
|
|
436
|
-
const name =
|
|
436
|
+
const name = path4.split("/").pop() ?? path4;
|
|
437
437
|
const dot = name.lastIndexOf(".");
|
|
438
438
|
return { kind: "string", value: dot > 0 ? name.slice(dot + 1) : "" };
|
|
439
439
|
}
|
|
440
440
|
case "tree":
|
|
441
|
-
return { kind: "string", value:
|
|
441
|
+
return { kind: "string", value: path4.split("/").slice(0, -1).join("/") };
|
|
442
442
|
case "path":
|
|
443
|
-
return { kind: "string", value:
|
|
443
|
+
return { kind: "string", value: path4 };
|
|
444
444
|
case "tags":
|
|
445
445
|
return { kind: "array", value: target.tags ?? [] };
|
|
446
446
|
case "links":
|
|
@@ -601,15 +601,99 @@ function toBoolean(value) {
|
|
|
601
601
|
}
|
|
602
602
|
|
|
603
603
|
// src/settings.ts
|
|
604
|
-
import { promises as
|
|
605
|
-
import
|
|
604
|
+
import { promises as fs2 } from "node:fs";
|
|
605
|
+
import path2 from "node:path";
|
|
606
606
|
import {
|
|
607
607
|
fingerprintExternalValue
|
|
608
608
|
} from "@riebeckite/core";
|
|
609
|
+
|
|
610
|
+
// src/icon_packs.ts
|
|
611
|
+
import { promises as fs } from "node:fs";
|
|
612
|
+
import path from "node:path";
|
|
613
|
+
var MAX_SVG_BYTES = 256 * 1024;
|
|
614
|
+
function normalizeIconName(name) {
|
|
615
|
+
return name.split(/[\s_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
616
|
+
}
|
|
617
|
+
async function loadIconPacks(root, diagnostics) {
|
|
618
|
+
const packs = /* @__PURE__ */ new Map();
|
|
619
|
+
let entries;
|
|
620
|
+
try {
|
|
621
|
+
entries = await fs.readdir(root, { withFileTypes: true });
|
|
622
|
+
} catch {
|
|
623
|
+
return packs;
|
|
624
|
+
}
|
|
625
|
+
let zipCount = 0;
|
|
626
|
+
for (const entry of entries) {
|
|
627
|
+
if (entry.isSymbolicLink()) continue;
|
|
628
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith(".zip")) {
|
|
629
|
+
zipCount += 1;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (!entry.isDirectory()) continue;
|
|
633
|
+
const packPath = path.join(root, entry.name);
|
|
634
|
+
const realPack = await safeRealPath(packPath);
|
|
635
|
+
if (!realPack || !isWithin(root, realPack)) continue;
|
|
636
|
+
let files;
|
|
637
|
+
try {
|
|
638
|
+
files = await fs.readdir(realPack, { withFileTypes: true });
|
|
639
|
+
} catch {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const pack = /* @__PURE__ */ new Map();
|
|
643
|
+
for (const file of files) {
|
|
644
|
+
if (!file.isFile() || !file.name.toLowerCase().endsWith(".svg")) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
const filePath = path.join(realPack, file.name);
|
|
648
|
+
const realFile = await safeRealPath(filePath);
|
|
649
|
+
if (!realFile || !isWithin(root, realFile)) continue;
|
|
650
|
+
try {
|
|
651
|
+
const stat = await fs.stat(realFile);
|
|
652
|
+
if (stat.size > MAX_SVG_BYTES) continue;
|
|
653
|
+
const iconName = normalizeIconName(
|
|
654
|
+
file.name.slice(0, -".svg".length)
|
|
655
|
+
).toLowerCase();
|
|
656
|
+
if (!iconName) continue;
|
|
657
|
+
pack.set(iconName, {
|
|
658
|
+
name: file.name,
|
|
659
|
+
filePath: realFile,
|
|
660
|
+
size: stat.size,
|
|
661
|
+
modified: stat.mtimeMs
|
|
662
|
+
});
|
|
663
|
+
} catch {
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (pack.size > 0) {
|
|
667
|
+
const packName = normalizeIconName(entry.name).toLowerCase();
|
|
668
|
+
if (packName) packs.set(packName, pack);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (zipCount > 0) {
|
|
672
|
+
diagnostics.push({
|
|
673
|
+
code: "icons-unsupported-zip-pack",
|
|
674
|
+
severity: "info",
|
|
675
|
+
message: "Zip-based Iconize icon packs are not read; extract them into folders under the icon packs directory.",
|
|
676
|
+
meta: { packCount: zipCount }
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
return packs;
|
|
680
|
+
}
|
|
681
|
+
function isWithin(root, target) {
|
|
682
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
683
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
684
|
+
}
|
|
685
|
+
async function safeRealPath(value) {
|
|
686
|
+
try {
|
|
687
|
+
return await fs.realpath(value);
|
|
688
|
+
} catch {
|
|
689
|
+
return void 0;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/settings.ts
|
|
609
694
|
var ICONIZE_ID = "obsidian-icon-folder";
|
|
610
695
|
var ICONIC_ID = "iconic";
|
|
611
696
|
var MAX_SETTINGS_BYTES = 2 * 1024 * 1024;
|
|
612
|
-
var MAX_SVG_BYTES = 256 * 1024;
|
|
613
697
|
var DEFAULT_ICONIZE_SETTINGS = {
|
|
614
698
|
iconPacksPath: ".obsidian/icons",
|
|
615
699
|
iconColor: null,
|
|
@@ -686,9 +770,6 @@ function findIconPackFile(packs, packName, iconName) {
|
|
|
686
770
|
if (!pack) return void 0;
|
|
687
771
|
return pack.get(normalizeIconName(iconName).toLowerCase());
|
|
688
772
|
}
|
|
689
|
-
function normalizeIconName(name) {
|
|
690
|
-
return name.split(/[\s_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
691
|
-
}
|
|
692
773
|
function normalizeVaultPath(value) {
|
|
693
774
|
const normalized = value.normalize("NFC").replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").replace(/\/{2,}/g, "/");
|
|
694
775
|
if (!normalized || normalized.includes("\0")) return void 0;
|
|
@@ -700,18 +781,18 @@ function normalizeVaultPath(value) {
|
|
|
700
781
|
}
|
|
701
782
|
async function resolveVaultRoot(options, contentDirectory) {
|
|
702
783
|
if (options.vaultRoot) {
|
|
703
|
-
const explicit =
|
|
704
|
-
const root =
|
|
784
|
+
const explicit = path2.resolve(options.vaultRoot);
|
|
785
|
+
const root = path2.basename(explicit) === ".obsidian" ? path2.dirname(explicit) : explicit;
|
|
705
786
|
return await isDirectory(root) ? root : void 0;
|
|
706
787
|
}
|
|
707
788
|
const starts = [contentDirectory, process.cwd()].filter(
|
|
708
789
|
(value) => typeof value === "string"
|
|
709
790
|
);
|
|
710
791
|
for (const start of starts) {
|
|
711
|
-
let current =
|
|
792
|
+
let current = path2.resolve(start);
|
|
712
793
|
for (; ; ) {
|
|
713
|
-
if (await isDirectory(
|
|
714
|
-
const parent =
|
|
794
|
+
if (await isDirectory(path2.join(current, ".obsidian"))) return current;
|
|
795
|
+
const parent = path2.dirname(current);
|
|
715
796
|
if (parent === current) break;
|
|
716
797
|
current = parent;
|
|
717
798
|
}
|
|
@@ -723,8 +804,8 @@ function resolveVaultPrefix(options, vaultRoot, contentDirectory) {
|
|
|
723
804
|
return normalizeVaultPath(options.vaultPrefix) ?? "";
|
|
724
805
|
}
|
|
725
806
|
if (!vaultRoot || !contentDirectory) return "";
|
|
726
|
-
const relative =
|
|
727
|
-
if (!relative || relative.startsWith("..") ||
|
|
807
|
+
const relative = path2.relative(vaultRoot, path2.resolve(contentDirectory));
|
|
808
|
+
if (!relative || relative.startsWith("..") || path2.isAbsolute(relative)) {
|
|
728
809
|
return "";
|
|
729
810
|
}
|
|
730
811
|
return `${normalizeVaultPath(relative) ?? ""}/`.replace(/^\/+/, "");
|
|
@@ -732,8 +813,8 @@ function resolveVaultPrefix(options, vaultRoot, contentDirectory) {
|
|
|
732
813
|
async function resolveDataFile(input) {
|
|
733
814
|
const root = input.vaultRoot ?? input.root;
|
|
734
815
|
if (input.explicit) {
|
|
735
|
-
const resolved =
|
|
736
|
-
if (!
|
|
816
|
+
const resolved = path2.resolve(input.root, input.explicit);
|
|
817
|
+
if (!isWithin2(root, resolved)) {
|
|
737
818
|
input.diagnostics.push({
|
|
738
819
|
code: "icons-untrusted-settings-path",
|
|
739
820
|
severity: "warning",
|
|
@@ -744,7 +825,7 @@ async function resolveDataFile(input) {
|
|
|
744
825
|
}
|
|
745
826
|
return await isFile(resolved) ? resolved : void 0;
|
|
746
827
|
}
|
|
747
|
-
const candidate =
|
|
828
|
+
const candidate = path2.join(
|
|
748
829
|
root,
|
|
749
830
|
".obsidian",
|
|
750
831
|
"plugins",
|
|
@@ -778,10 +859,10 @@ async function readJsonFile(filePath, source, diagnostics) {
|
|
|
778
859
|
}
|
|
779
860
|
async function readTextFile(filePath, maxBytes) {
|
|
780
861
|
try {
|
|
781
|
-
const stat = await
|
|
862
|
+
const stat = await fs2.lstat(filePath);
|
|
782
863
|
if (!stat.isFile() || stat.isSymbolicLink()) return void 0;
|
|
783
864
|
if (stat.size > maxBytes) return void 0;
|
|
784
|
-
return await
|
|
865
|
+
return await fs2.readFile(filePath, "utf8");
|
|
785
866
|
} catch {
|
|
786
867
|
return void 0;
|
|
787
868
|
}
|
|
@@ -928,73 +1009,9 @@ function parseIconicRules(value) {
|
|
|
928
1009
|
}
|
|
929
1010
|
return rules;
|
|
930
1011
|
}
|
|
931
|
-
async function loadIconPacks(root, diagnostics) {
|
|
932
|
-
const packs = /* @__PURE__ */ new Map();
|
|
933
|
-
let entries;
|
|
934
|
-
try {
|
|
935
|
-
entries = await fs.readdir(root, { withFileTypes: true });
|
|
936
|
-
} catch {
|
|
937
|
-
return packs;
|
|
938
|
-
}
|
|
939
|
-
let zipCount = 0;
|
|
940
|
-
for (const entry of entries) {
|
|
941
|
-
if (entry.isSymbolicLink()) continue;
|
|
942
|
-
if (entry.isFile() && entry.name.toLowerCase().endsWith(".zip")) {
|
|
943
|
-
zipCount += 1;
|
|
944
|
-
continue;
|
|
945
|
-
}
|
|
946
|
-
if (!entry.isDirectory()) continue;
|
|
947
|
-
const packPath = path.join(root, entry.name);
|
|
948
|
-
const realPack = await safeRealPath(packPath);
|
|
949
|
-
if (!realPack || !isWithin(root, realPack)) continue;
|
|
950
|
-
let files;
|
|
951
|
-
try {
|
|
952
|
-
files = await fs.readdir(realPack, { withFileTypes: true });
|
|
953
|
-
} catch {
|
|
954
|
-
continue;
|
|
955
|
-
}
|
|
956
|
-
const pack = /* @__PURE__ */ new Map();
|
|
957
|
-
for (const file of files) {
|
|
958
|
-
if (!file.isFile() || !file.name.toLowerCase().endsWith(".svg")) {
|
|
959
|
-
continue;
|
|
960
|
-
}
|
|
961
|
-
const filePath = path.join(realPack, file.name);
|
|
962
|
-
const realFile = await safeRealPath(filePath);
|
|
963
|
-
if (!realFile || !isWithin(root, realFile)) continue;
|
|
964
|
-
try {
|
|
965
|
-
const stat = await fs.stat(realFile);
|
|
966
|
-
if (stat.size > MAX_SVG_BYTES) continue;
|
|
967
|
-
const iconName = normalizeIconName(
|
|
968
|
-
file.name.slice(0, -".svg".length)
|
|
969
|
-
).toLowerCase();
|
|
970
|
-
if (!iconName) continue;
|
|
971
|
-
pack.set(iconName, {
|
|
972
|
-
name: file.name,
|
|
973
|
-
filePath: realFile,
|
|
974
|
-
size: stat.size,
|
|
975
|
-
modified: stat.mtimeMs
|
|
976
|
-
});
|
|
977
|
-
} catch {
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
if (pack.size > 0) {
|
|
981
|
-
const packName = normalizeIconName(entry.name).toLowerCase();
|
|
982
|
-
if (packName) packs.set(packName, pack);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
if (zipCount > 0) {
|
|
986
|
-
diagnostics.push({
|
|
987
|
-
code: "icons-unsupported-zip-pack",
|
|
988
|
-
severity: "info",
|
|
989
|
-
message: "Zip-based Iconize icon packs are not read; extract them into folders under the icon packs directory.",
|
|
990
|
-
meta: { packCount: zipCount }
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
return packs;
|
|
994
|
-
}
|
|
995
1012
|
function resolvePackRoot(vaultRoot, value) {
|
|
996
|
-
const resolved =
|
|
997
|
-
return
|
|
1013
|
+
const resolved = path2.resolve(vaultRoot, value);
|
|
1014
|
+
return isWithin2(vaultRoot, resolved) ? resolved : void 0;
|
|
998
1015
|
}
|
|
999
1016
|
function isLucidePackType(value) {
|
|
1000
1017
|
return value === "none" || value === "custom" || value === "native";
|
|
@@ -1005,13 +1022,13 @@ function nonEmptyString(value) {
|
|
|
1005
1022
|
function isRecord(value) {
|
|
1006
1023
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1007
1024
|
}
|
|
1008
|
-
function
|
|
1009
|
-
const relative =
|
|
1010
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
1025
|
+
function isWithin2(root, target) {
|
|
1026
|
+
const relative = path2.relative(path2.resolve(root), path2.resolve(target));
|
|
1027
|
+
return relative === "" || !relative.startsWith("..") && !path2.isAbsolute(relative);
|
|
1011
1028
|
}
|
|
1012
1029
|
async function isDirectory(value) {
|
|
1013
1030
|
try {
|
|
1014
|
-
const stat = await
|
|
1031
|
+
const stat = await fs2.lstat(value);
|
|
1015
1032
|
return stat.isDirectory() && !stat.isSymbolicLink();
|
|
1016
1033
|
} catch {
|
|
1017
1034
|
return false;
|
|
@@ -1019,19 +1036,12 @@ async function isDirectory(value) {
|
|
|
1019
1036
|
}
|
|
1020
1037
|
async function isFile(value) {
|
|
1021
1038
|
try {
|
|
1022
|
-
const stat = await
|
|
1039
|
+
const stat = await fs2.lstat(value);
|
|
1023
1040
|
return stat.isFile() && !stat.isSymbolicLink();
|
|
1024
1041
|
} catch {
|
|
1025
1042
|
return false;
|
|
1026
1043
|
}
|
|
1027
1044
|
}
|
|
1028
|
-
async function safeRealPath(value) {
|
|
1029
|
-
try {
|
|
1030
|
-
return await fs.realpath(value);
|
|
1031
|
-
} catch {
|
|
1032
|
-
return void 0;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
1045
|
|
|
1036
1046
|
// src/iconize.ts
|
|
1037
1047
|
function resolveIconize(data, target, options) {
|
|
@@ -1101,7 +1111,7 @@ function matchesRule2(pattern, value) {
|
|
|
1101
1111
|
// src/render.ts
|
|
1102
1112
|
import { readFileSync } from "node:fs";
|
|
1103
1113
|
import { createRequire } from "node:module";
|
|
1104
|
-
import
|
|
1114
|
+
import path3 from "node:path";
|
|
1105
1115
|
import {
|
|
1106
1116
|
escapeHtml,
|
|
1107
1117
|
escapeHtmlAttribute
|
|
@@ -1220,7 +1230,7 @@ var IconRenderer = class {
|
|
|
1220
1230
|
const name = reference.slice("lucide:".length);
|
|
1221
1231
|
if (/^[a-z0-9-]+$/.test(name) && this.#lucideDirectory) {
|
|
1222
1232
|
html = renderSvg(
|
|
1223
|
-
readSvg(
|
|
1233
|
+
readSvg(path3.join(this.#lucideDirectory, `${name}.svg`))
|
|
1224
1234
|
);
|
|
1225
1235
|
}
|
|
1226
1236
|
} else if (reference.startsWith("text:")) {
|
|
@@ -1254,17 +1264,17 @@ function resolveLucideDirectory() {
|
|
|
1254
1264
|
try {
|
|
1255
1265
|
const require2 = createRequire(import.meta.url);
|
|
1256
1266
|
try {
|
|
1257
|
-
return
|
|
1258
|
-
|
|
1267
|
+
return path3.join(
|
|
1268
|
+
path3.dirname(require2.resolve("lucide-static/package.json")),
|
|
1259
1269
|
"icons"
|
|
1260
1270
|
);
|
|
1261
1271
|
} catch {
|
|
1262
|
-
let current =
|
|
1272
|
+
let current = path3.dirname(require2.resolve("lucide-static"));
|
|
1263
1273
|
for (; ; ) {
|
|
1264
|
-
if (
|
|
1265
|
-
return
|
|
1274
|
+
if (path3.basename(current) === "lucide-static") {
|
|
1275
|
+
return path3.join(current, "icons");
|
|
1266
1276
|
}
|
|
1267
|
-
const parent =
|
|
1277
|
+
const parent = path3.dirname(current);
|
|
1268
1278
|
if (parent === current) return void 0;
|
|
1269
1279
|
current = parent;
|
|
1270
1280
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type IconDiagnostic = {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly severity: "info" | "warning";
|
|
4
|
+
readonly message: string;
|
|
5
|
+
readonly meta?: Readonly<Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
export type IconPackFile = {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly filePath: string;
|
|
10
|
+
readonly size: number;
|
|
11
|
+
readonly modified: number;
|
|
12
|
+
};
|
|
13
|
+
export type IconPacks = ReadonlyMap<string, ReadonlyMap<string, IconPackFile>>;
|
|
14
|
+
export declare function normalizeIconName(name: string): string;
|
|
15
|
+
export declare function loadIconPacks(root: string, diagnostics: IconDiagnostic[]): Promise<IconPacks>;
|
package/dist/src/settings.d.ts
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { type ResolvedRiebeckiteConfig } from "@riebeckite/core";
|
|
2
|
+
import { type IconDiagnostic, type IconPackFile, type IconPacks } from "./icon_packs.js";
|
|
2
3
|
import type { IconicData, IconizeData, ResolvedIconsOptions } from "./types.js";
|
|
3
|
-
type
|
|
4
|
-
|
|
5
|
-
readonly severity: "info" | "warning";
|
|
6
|
-
readonly message: string;
|
|
7
|
-
readonly meta?: Readonly<Record<string, unknown>>;
|
|
8
|
-
};
|
|
9
|
-
type IconPackFile = {
|
|
10
|
-
readonly name: string;
|
|
11
|
-
readonly filePath: string;
|
|
12
|
-
readonly size: number;
|
|
13
|
-
readonly modified: number;
|
|
14
|
-
};
|
|
15
|
-
export type IconPacks = ReadonlyMap<string, ReadonlyMap<string, IconPackFile>>;
|
|
4
|
+
export type { IconPacks } from "./icon_packs.js";
|
|
5
|
+
export { normalizeIconName } from "./icon_packs.js";
|
|
16
6
|
export type LoadedIconsSettings = {
|
|
17
7
|
readonly iconize?: IconizeData;
|
|
18
8
|
readonly iconic?: IconicData;
|
|
@@ -24,6 +14,4 @@ export type LoadedIconsSettings = {
|
|
|
24
14
|
};
|
|
25
15
|
export declare function loadIconsSettings(options: ResolvedIconsOptions, config: ResolvedRiebeckiteConfig | undefined): Promise<LoadedIconsSettings>;
|
|
26
16
|
export declare function findIconPackFile(packs: IconPacks, packName: string, iconName: string): IconPackFile | undefined;
|
|
27
|
-
export declare function normalizeIconName(name: string): string;
|
|
28
17
|
export declare function normalizeVaultPath(value: string): string | undefined;
|
|
29
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-icons",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@riebeckite/core": "0.0.
|
|
8
|
+
"@riebeckite/core": "0.0.36",
|
|
9
9
|
"@types/mdast": "^4.0.4",
|
|
10
10
|
"lucide-static": "^1.61.0",
|
|
11
11
|
"sanitize-html": "^2.18.1"
|