Add docking functionality to Model Linker dialog
This commit is contained in:
@@ -145,6 +145,14 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--dock {
|
||||
width: 46px;
|
||||
min-width: 46px;
|
||||
height: 100%;
|
||||
min-height: 40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--close {
|
||||
width: 46px;
|
||||
min-width: 46px;
|
||||
@@ -448,6 +456,63 @@
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.comfyui-model-linker-tab-button .mdi,
|
||||
.comfyui-model-linker-tab-button .mdi-link-variant {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ml-sidebar-dock-panel {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
color: var(--ml-text);
|
||||
background: var(--ml-bg);
|
||||
}
|
||||
|
||||
#model-linker-modal.ml-is-docked {
|
||||
position: relative;
|
||||
inset: auto;
|
||||
top: auto;
|
||||
left: auto;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--undock {
|
||||
display: none;
|
||||
width: 46px;
|
||||
min-width: 46px;
|
||||
height: 100%;
|
||||
min-height: 40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#model-linker-modal.ml-is-docked .ml-window-btn--undock {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#model-linker-modal.ml-is-docked .ml-window-btn--dock {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#model-linker-modal.ml-is-docked .ml-window-btn--fullscreen {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes ml-slide-down {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
+403
-7
@@ -33,6 +33,14 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
this.activeTabStorageKey = 'model_linker_active_tab';
|
||||
this.activeTab = this.restoreActiveTab(); // Default tab
|
||||
this.fullscreen = false;
|
||||
this.docked = false;
|
||||
this.dockContainer = null;
|
||||
this.lastDockContainer = null;
|
||||
this.pendingDockToSidebar = false;
|
||||
this.sidebarTabId = "comfyui-model-linker";
|
||||
this.dockButton = null;
|
||||
this.undockButton = null;
|
||||
this._floatingRectBeforeDock = null;
|
||||
this._dragging = false;
|
||||
this._dragStart = null;
|
||||
this._analysisProgressToken = null;
|
||||
@@ -2044,6 +2052,24 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
onclick: () => this.toggleFullScreen()
|
||||
});
|
||||
this.setTooltip(fullscreenButton, "Toggle full screen");
|
||||
|
||||
this.dockButton = $el("button", {
|
||||
id: "model-linker-dock-toggle",
|
||||
className: "ml-window-btn ml-window-btn--dock",
|
||||
innerHTML: getSvgIcon('internalLink', 'currentColor', 'ml-window-btn-icon'),
|
||||
ariaLabel: "Dock Model Linker to sidebar",
|
||||
onclick: () => this.dockToSidebar()
|
||||
});
|
||||
this.setTooltip(this.dockButton, "Dock to sidebar");
|
||||
|
||||
this.undockButton = $el("button", {
|
||||
id: "model-linker-undock-toggle",
|
||||
className: "ml-window-btn ml-window-btn--undock",
|
||||
innerHTML: getSvgIcon('externalLink', 'currentColor', 'ml-window-btn-icon'),
|
||||
ariaLabel: "Undock Model Linker",
|
||||
onclick: () => this.undockToFloating()
|
||||
});
|
||||
this.setTooltip(this.undockButton, "Undock to floating window");
|
||||
|
||||
return $el("div.ml-dialog-shell", {}, [
|
||||
$el("div.ml-dialog-topbar", {}, [
|
||||
@@ -2056,6 +2082,8 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
])
|
||||
]),
|
||||
$el("div.ml-dialog-controls", {}, [
|
||||
this.dockButton,
|
||||
this.undockButton,
|
||||
fullscreenButton,
|
||||
$el("button", {
|
||||
className: "ml-window-btn ml-window-btn--close",
|
||||
@@ -2067,6 +2095,294 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
captureFloatingRect() {
|
||||
if (!this.element || this.docked || getComputedStyle(this.element).display === 'none') return;
|
||||
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
if (!rect.width || !rect.height) return;
|
||||
|
||||
this._floatingRectBeforeDock = {
|
||||
top: Math.round(rect.top),
|
||||
left: Math.round(rect.left),
|
||||
width: Math.round(rect.width),
|
||||
height: Math.round(rect.height)
|
||||
};
|
||||
}
|
||||
|
||||
clearModalPlacementStyles() {
|
||||
if (!this.element) return;
|
||||
|
||||
[
|
||||
'position',
|
||||
'top',
|
||||
'left',
|
||||
'right',
|
||||
'bottom',
|
||||
'inset',
|
||||
'transform',
|
||||
'width',
|
||||
'height',
|
||||
'maxWidth',
|
||||
'maxHeight',
|
||||
'minWidth',
|
||||
'minHeight',
|
||||
'borderRadius',
|
||||
'resize'
|
||||
].forEach((property) => {
|
||||
this.element.style[property] = '';
|
||||
});
|
||||
}
|
||||
|
||||
restoreFloatingGeometry() {
|
||||
const el = this.element;
|
||||
if (!el) return;
|
||||
|
||||
el.style.width = '1100px';
|
||||
el.style.height = '700px';
|
||||
el.style.maxWidth = '100vw';
|
||||
el.style.maxHeight = '100vh';
|
||||
el.style.minWidth = '640px';
|
||||
el.style.minHeight = '420px';
|
||||
el.style.resize = 'both';
|
||||
el.style.borderRadius = '7px';
|
||||
|
||||
const rect = this._floatingRectBeforeDock;
|
||||
if (rect?.width && rect?.height) {
|
||||
el.style.width = `${rect.width}px`;
|
||||
el.style.height = `${rect.height}px`;
|
||||
el.style.top = `${rect.top}px`;
|
||||
el.style.left = `${rect.left}px`;
|
||||
el.style.transform = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const wh = JSON.parse(localStorage.getItem('model_linker_modal_size_before_fs') || 'null');
|
||||
if (wh?.w && wh?.h) {
|
||||
el.style.width = `${wh.w}px`;
|
||||
el.style.height = `${wh.h}px`;
|
||||
}
|
||||
|
||||
const pos = JSON.parse(localStorage.getItem('model_linker_modal_pos') || 'null');
|
||||
if (pos && Number.isFinite(pos.top) && Number.isFinite(pos.left)) {
|
||||
el.style.top = `${pos.top}px`;
|
||||
el.style.left = `${pos.left}px`;
|
||||
el.style.transform = 'none';
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// Fall back to centered floating geometry below.
|
||||
}
|
||||
|
||||
el.style.top = '50%';
|
||||
el.style.left = '50%';
|
||||
el.style.transform = 'translate(-50%, -50%)';
|
||||
}
|
||||
|
||||
dockTo(container) {
|
||||
if (!container || !this.element) return;
|
||||
|
||||
if (!this.docked) {
|
||||
this.captureFloatingRect();
|
||||
}
|
||||
|
||||
if (this.fullscreen) {
|
||||
this.setFullScreen(false);
|
||||
}
|
||||
|
||||
this.docked = true;
|
||||
this.dockContainer = container;
|
||||
this.lastDockContainer = container;
|
||||
this.pendingDockToSidebar = false;
|
||||
this.backdrop.style.display = 'none';
|
||||
container.classList.add('ml-sidebar-dock-panel');
|
||||
this.element.classList.add('ml-is-docked');
|
||||
this.clearModalPlacementStyles();
|
||||
this.element.style.display = 'flex';
|
||||
|
||||
if (this.element.parentNode !== container) {
|
||||
container.appendChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
dockToSidebar() {
|
||||
if (this.docked) return;
|
||||
|
||||
this.pendingDockToSidebar = true;
|
||||
|
||||
if (this.isUsableDockContainer(this.lastDockContainer)) {
|
||||
this.dockTo(this.lastDockContainer);
|
||||
return;
|
||||
}
|
||||
|
||||
this.tryOpenComfySidebarState();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (!this.pendingDockToSidebar || this.docked) return;
|
||||
|
||||
const button = document.querySelector(`.${this.sidebarTabId}-tab-button`);
|
||||
if (button instanceof HTMLElement) {
|
||||
const isActive = button.matches([
|
||||
'[aria-pressed="true"]',
|
||||
'[aria-selected="true"]',
|
||||
'[data-active="true"]',
|
||||
'[data-selected="true"]',
|
||||
'.active',
|
||||
'.is-active',
|
||||
'.selected',
|
||||
'.p-highlight'
|
||||
].join(','));
|
||||
|
||||
if (!isActive) {
|
||||
button.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isUsableDockContainer(container) {
|
||||
if (!(container instanceof HTMLElement) || !container.isConnected) return false;
|
||||
|
||||
const style = getComputedStyle(container);
|
||||
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
return rect.width > 0 && rect.height > 0;
|
||||
}
|
||||
|
||||
tryOpenComfySidebarState() {
|
||||
const extensionManager = app.extensionManager;
|
||||
let opened = false;
|
||||
const candidates = [
|
||||
extensionManager?.sidebarTab,
|
||||
extensionManager?.sidebarTabs,
|
||||
extensionManager?.sidebar,
|
||||
extensionManager
|
||||
].filter(Boolean);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
if (typeof candidate.openSidebar === 'function') {
|
||||
candidate.openSidebar(this.sidebarTabId);
|
||||
opened = true;
|
||||
}
|
||||
if (typeof candidate.openSidebarTab === 'function') {
|
||||
candidate.openSidebarTab(this.sidebarTabId);
|
||||
opened = true;
|
||||
}
|
||||
if (typeof candidate.setActiveSidebarTab === 'function') {
|
||||
candidate.setActiveSidebarTab(this.sidebarTabId);
|
||||
opened = true;
|
||||
}
|
||||
if (typeof candidate.setActiveSidebarTabId === 'function') {
|
||||
candidate.setActiveSidebarTabId(this.sidebarTabId);
|
||||
opened = true;
|
||||
}
|
||||
if ('activeSidebarTabId' in candidate) {
|
||||
candidate.activeSidebarTabId = this.sidebarTabId;
|
||||
opened = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug('Model Linker: Sidebar state open attempt failed.', error);
|
||||
}
|
||||
}
|
||||
|
||||
return opened;
|
||||
}
|
||||
|
||||
undockToFloating({ persist = true, closeSidebar = true } = {}) {
|
||||
if (!this.element) return;
|
||||
|
||||
const wasDocked = this.docked;
|
||||
const dockContainer = this.dockContainer;
|
||||
this.docked = false;
|
||||
this.dockContainer = null;
|
||||
this.element.classList.remove('ml-is-docked');
|
||||
this.clearModalPlacementStyles();
|
||||
|
||||
if (this.element.parentNode !== document.body) {
|
||||
document.body.appendChild(this.element);
|
||||
}
|
||||
|
||||
this.element.style.display = 'flex';
|
||||
this.restoreFloatingGeometry();
|
||||
this.ensureModalHandleInViewport({ persist });
|
||||
|
||||
if (wasDocked && closeSidebar) {
|
||||
this.closeComfySidebar(dockContainer);
|
||||
}
|
||||
}
|
||||
|
||||
closeComfySidebar(dockContainer = null) {
|
||||
const closedByState = this.tryCloseComfySidebarState();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (dockContainer && !dockContainer.isConnected) return;
|
||||
|
||||
const button = document.querySelector(`.${this.sidebarTabId}-tab-button`);
|
||||
if (!(button instanceof HTMLElement)) return;
|
||||
|
||||
const isActive = button.matches([
|
||||
'[aria-pressed="true"]',
|
||||
'[aria-selected="true"]',
|
||||
'[data-active="true"]',
|
||||
'[data-selected="true"]',
|
||||
'.active',
|
||||
'.is-active',
|
||||
'.selected',
|
||||
'.p-highlight'
|
||||
].join(','));
|
||||
|
||||
if (!closedByState || isActive) {
|
||||
button.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tryCloseComfySidebarState() {
|
||||
const extensionManager = app.extensionManager;
|
||||
let closed = false;
|
||||
const candidates = [
|
||||
extensionManager?.sidebarTab,
|
||||
extensionManager?.sidebarTabs,
|
||||
extensionManager?.sidebar,
|
||||
extensionManager
|
||||
].filter(Boolean);
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
if (typeof candidate.closeSidebar === 'function') {
|
||||
candidate.closeSidebar();
|
||||
closed = true;
|
||||
}
|
||||
if (typeof candidate.closeSidebarTab === 'function') {
|
||||
candidate.closeSidebarTab(this.sidebarTabId);
|
||||
closed = true;
|
||||
}
|
||||
if (typeof candidate.setActiveSidebarTab === 'function') {
|
||||
candidate.setActiveSidebarTab(null);
|
||||
closed = true;
|
||||
}
|
||||
if (typeof candidate.setActiveSidebarTabId === 'function') {
|
||||
candidate.setActiveSidebarTabId(null);
|
||||
closed = true;
|
||||
}
|
||||
if ('activeSidebarTabId' in candidate) {
|
||||
candidate.activeSidebarTabId = null;
|
||||
closed = true;
|
||||
}
|
||||
if ('activeSidebarTab' in candidate && typeof candidate.activeSidebarTab !== 'function') {
|
||||
candidate.activeSidebarTab = null;
|
||||
closed = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.debug('Model Linker: Sidebar state close attempt failed.', error);
|
||||
}
|
||||
}
|
||||
|
||||
return closed;
|
||||
}
|
||||
|
||||
// Toggle full screen mode for the dialog
|
||||
toggleFullScreen() {
|
||||
@@ -2074,6 +2390,10 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
}
|
||||
|
||||
setFullScreen(enable) {
|
||||
if (enable && this.docked) {
|
||||
this.undockToFloating({ persist: false });
|
||||
}
|
||||
|
||||
this.fullscreen = !!enable;
|
||||
const el = this.element;
|
||||
if (!el) return;
|
||||
@@ -2176,6 +2496,8 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
}
|
||||
|
||||
saveModalPosition() {
|
||||
if (this.docked) return;
|
||||
|
||||
try {
|
||||
const el = this.element;
|
||||
if (!el) return;
|
||||
@@ -2185,6 +2507,7 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
}
|
||||
|
||||
ensureModalHandleInViewport({ persist = false } = {}) {
|
||||
if (this.docked) return;
|
||||
if (this.fullscreen) return;
|
||||
const el = this.element;
|
||||
if (!el || getComputedStyle(el).display === 'none') return;
|
||||
@@ -2216,6 +2539,8 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
|
||||
// Begin window drag
|
||||
startDrag(e) {
|
||||
if (this.docked) return;
|
||||
|
||||
try {
|
||||
const el = this.element;
|
||||
if (!el) return;
|
||||
@@ -3840,9 +4165,21 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
}
|
||||
|
||||
async show(workflow = null) {
|
||||
this.undockToFloating({ persist: false });
|
||||
await this.showContent(workflow, { restoreFullscreen: true });
|
||||
}
|
||||
|
||||
async showDocked(container, workflow = null) {
|
||||
this.dockTo(container);
|
||||
await this.showContent(workflow, { restoreFullscreen: false });
|
||||
}
|
||||
|
||||
async showContent(workflow = null, { restoreFullscreen = true } = {}) {
|
||||
this.backdrop.style.display = "none";
|
||||
this.element.style.display = "flex";
|
||||
this.scheduleModalViewportClamp();
|
||||
if (!this.docked) {
|
||||
this.scheduleModalViewportClamp();
|
||||
}
|
||||
|
||||
// Update button state in case there are active downloads
|
||||
this.updateDownloadAllButtonState();
|
||||
@@ -3855,7 +4192,7 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
// Restore fullscreen state if enabled
|
||||
try {
|
||||
const fs = localStorage.getItem('model_linker_modal_fullscreen');
|
||||
if (fs === '1') this.setFullScreen(true);
|
||||
if (!this.docked && restoreFullscreen && fs === '1') this.setFullScreen(true);
|
||||
} catch (e) { }
|
||||
|
||||
// Attach drag handle event listener (only once)
|
||||
@@ -3881,7 +4218,7 @@ class LinkerManagerDialog extends ComfyDialog {
|
||||
if (!handle || !topbar) return;
|
||||
|
||||
const onMouseDown = (e) => {
|
||||
if (this.fullscreen) return; // no drag in fullscreen
|
||||
if (this.fullscreen || this.docked) return; // no drag in fullscreen or docked mode
|
||||
if (e.button !== 0 || this.isTopbarDragExcluded(e.target)) return;
|
||||
topbar.classList.add('ml-is-dragging');
|
||||
this.startDrag(e);
|
||||
@@ -6225,6 +6562,9 @@ class ModelLinker {
|
||||
this.linkerButton = null;
|
||||
this.buttonGroup = null;
|
||||
this.buttonId = "model-linker-button";
|
||||
this.sidebarTabId = "comfyui-model-linker";
|
||||
this.sidebarRegistered = false;
|
||||
this.openTooltip = "Open Model Linker to find or download missing workflow models. Shortcut: Ctrl+Shift+L.";
|
||||
this.dialog = null;
|
||||
this.isCheckingMissing = false; // Prevent multiple simultaneous checks
|
||||
this.lastCheckedWorkflow = null; // Track to avoid duplicate checks
|
||||
@@ -6253,6 +6593,50 @@ class ModelLinker {
|
||||
// Listen for workflow load events to auto-check for missing models
|
||||
this.setupAutoOpenOnMissingModels();
|
||||
|
||||
const sidebarRegistered = this.registerSidebarButton();
|
||||
if (!sidebarRegistered) {
|
||||
await this.registerTopbarButton();
|
||||
}
|
||||
}
|
||||
|
||||
registerSidebarButton() {
|
||||
const registerSidebarTab = app.extensionManager?.registerSidebarTab;
|
||||
if (typeof registerSidebarTab !== "function") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.sidebarRegistered || window.__modelLinkerSidebarRegistered) {
|
||||
this.sidebarRegistered = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
registerSidebarTab.call(app.extensionManager, {
|
||||
id: this.sidebarTabId,
|
||||
icon: "mdi mdi-link-variant",
|
||||
title: "Model Linker",
|
||||
tooltip: this.openTooltip,
|
||||
type: "custom",
|
||||
render: (element) => this.renderSidebarPanel(element),
|
||||
});
|
||||
this.sidebarRegistered = true;
|
||||
window.__modelLinkerSidebarRegistered = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn("Model Linker: Sidebar tab registration failed, falling back to top menu button.", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
renderSidebarPanel(element) {
|
||||
element.replaceChildren();
|
||||
element.style.height = "100%";
|
||||
element.classList.add("ml-sidebar-dock-panel");
|
||||
|
||||
this.openLinkerManager({ dockContainer: element });
|
||||
}
|
||||
|
||||
async registerTopbarButton() {
|
||||
// Try to use new ComfyUI button system (like ComfyUI Manager does)
|
||||
try {
|
||||
// Dynamic imports for ComfyUI's button components
|
||||
@@ -6260,14 +6644,14 @@ class ModelLinker {
|
||||
const { ComfyButton } = await import("../../../scripts/ui/components/button.js");
|
||||
|
||||
// Create button group with Model Linker button
|
||||
const modelLinkerTooltip = "Open Model Linker to find or download missing workflow models. Shortcut: Ctrl+Shift+L.";
|
||||
const modelLinkerButton = new ComfyButton({
|
||||
icon: "link-variant",
|
||||
action: () => this.openLinkerManager(),
|
||||
content: "Model Linker",
|
||||
classList: "comfyui-button comfyui-menu-mobile-collapse"
|
||||
}).element;
|
||||
this.dialog.setTooltip(modelLinkerButton, modelLinkerTooltip);
|
||||
modelLinkerButton.id = this.buttonId;
|
||||
this.dialog.setTooltip(modelLinkerButton, this.openTooltip);
|
||||
this.buttonGroup = new ComfyButtonGroup(
|
||||
modelLinkerButton
|
||||
);
|
||||
@@ -6658,6 +7042,13 @@ class ModelLinker {
|
||||
existingButton.remove();
|
||||
}
|
||||
|
||||
document.querySelectorAll('.comfyui-button-group button.comfyui-button').forEach((button) => {
|
||||
const label = button.querySelector('span')?.textContent?.trim() || button.textContent?.trim();
|
||||
if (label === 'Model Linker') {
|
||||
button.closest('.comfyui-button-group')?.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Remove button group if it exists
|
||||
if (this.buttonGroup?.element?.parentNode) {
|
||||
this.buttonGroup.element.remove();
|
||||
@@ -6686,13 +7077,18 @@ class ModelLinker {
|
||||
this.dialog?.setTooltip(this.linkerButton, "Open Model Linker to find or download missing workflow models. Shortcut: Ctrl+Shift+L.");
|
||||
}
|
||||
|
||||
openLinkerManager() {
|
||||
async openLinkerManager(options = {}) {
|
||||
try {
|
||||
if (!this.dialog) {
|
||||
this.dialog = new LinkerManagerDialog();
|
||||
window.modelLinkerDialog = this.dialog;
|
||||
}
|
||||
this.dialog.show();
|
||||
if (options.dockContainer) {
|
||||
await this.dialog.showDocked(options.dockContainer, options.workflow || null);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.dialog.show(options.workflow || null);
|
||||
} catch (error) {
|
||||
console.error("🔗 Model Linker: Error creating/showing dialog:", error);
|
||||
alert("Error opening Model Linker: " + error.message);
|
||||
|
||||
@@ -7,6 +7,7 @@ const SVG_ICONS = {
|
||||
locate: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12h3"></path><path d="M19 12h3"></path><path d="M12 2v3"></path><path d="M12 19v3"></path><circle cx="12" cy="12" r="7"></circle><circle cx="12" cy="12" r="2.5"></circle></svg>`,
|
||||
download: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12"></path><path d="m7 10 5 5 5-5"></path><path d="M5 21h14"></path></svg>`,
|
||||
externalLink: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 3h6v6"></path><path d="M10 14 21 3"></path><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path></svg>`,
|
||||
internalLink: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 10v6h6"></path><path d="M21 3 8 16"></path><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path></svg>`,
|
||||
database: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><ellipse cx="12" cy="5" rx="8" ry="3"></ellipse><path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5"></path><path d="M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"></path></svg>`,
|
||||
archive: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16"></path><path d="M5 7l1 14h12l1-14"></path><path d="M8 7V3h8v4"></path><path d="M10 12h4"></path></svg>`,
|
||||
loraManager: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" aria-hidden="true"><rect width="400" height="400" fill="#d3e3e6"></rect><path fill="#1e406e" d="M127 101h55v132h62l41 55H127z"></path></svg>`,
|
||||
|
||||
Reference in New Issue
Block a user