дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
feat(plugins): let a plugin set its own icon (#1761)
A plugin can set `icon` (and `icon-dark`) in plugin.toml. The SVGs go through the existing sanitiser (16 KiB each), ship with the package, and show in Settings › Plugins (list rows and detail header) and the marketplace, whose index entries carry them; the IDE sanitises index icons again and drops unsafe ones. IDEs before plugin API 1.2 refuse a manifest with `icon`, so Manifest::needs_api refuses one whose engines.openidea still admits them (check and install), keeping the index from offering such a package to older IDEs. Closes #1732 Co-authored-by: arnaud-ritti <[email protected]>
Цей коміт міститься в:
1 джерело
2dc59ef2e2
коміт
e65825e661
18 files changed
+428
-33
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import type { PluginIcon } from "@/bindings";
|
||||
import { registerExternalIcons } from "@/ui/icons";
|
||||
|
||||
// ponytail: never unregistered; bounded by the distinct icons seen this session.
|
||||
const registered = new Set<string>();
|
||||
|
||||
/** FNV-1a: a short key for an icon's markup. */
|
||||
function hash(text: string): string {
|
||||
let h = 0x811c9dc5;
|
||||
for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 0x01000193);
|
||||
return (h >>> 0).toString(16);
|
||||
}
|
||||
|
||||
const ROOT_ONLY = new Set(["viewBox", "width", "height", "id", "version"]);
|
||||
|
||||
/**
|
||||
* `Icon` keeps only the root's viewBox and draws on `fill="none"`: move the
|
||||
* root's other attributes onto a group, with SVG's default black fill.
|
||||
*/
|
||||
function standalone(svg: string): string {
|
||||
const open = svg.indexOf("<svg");
|
||||
const start = svg.indexOf(">", open) + 1;
|
||||
const end = svg.lastIndexOf("</svg>");
|
||||
if (open < 0 || start <= open || end < start) return svg;
|
||||
const tag = svg.slice(open, start);
|
||||
const inherited = [...tag.matchAll(/\s([\w:-]+)="[^"]*"/g)]
|
||||
.filter((m) => !ROOT_ONLY.has(m[1] ?? "") && !m[1]?.startsWith("xmlns"))
|
||||
.map((m) => m[0])
|
||||
.join("");
|
||||
const fill = /\sfill="/.test(inherited) ? "" : ' fill="#000"';
|
||||
return `${tag}<g${fill}${inherited}>${svg.slice(start, end)}</g></svg>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The icon name of a plugin's own icon, else the generic one. Names follow the
|
||||
* markup, so copies of a plugin with different icons (marketplace, local) keep
|
||||
* their own; plugin ids have no `/`, so no id yields another's name or `_dark`.
|
||||
*/
|
||||
export function pluginIconName(pluginId: string, icon: PluginIcon | null | undefined): string {
|
||||
if (!icon) return "plugin";
|
||||
const name = `ext:pluginIcon/${pluginId}/${hash(`${icon.light}\n${icon.dark ?? ""}`)}`;
|
||||
if (registered.has(name)) return name;
|
||||
registered.add(name);
|
||||
registerExternalIcons(
|
||||
icon.dark
|
||||
? [
|
||||
[name, standalone(icon.light)],
|
||||
[`${name}_dark`, standalone(icon.dark)],
|
||||
]
|
||||
: [[name, standalone(icon.light)]],
|
||||
);
|
||||
return name;
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача