707 lines
25 KiB
JavaScript
707 lines
25 KiB
JavaScript
import { app } from "/scripts/app.js";
|
|
import { NotesStore } from "./stores/NotesStore.js";
|
|
import { AppendMenu } from "./components/AppendMenu.js";
|
|
import { NotesApi } from "./api/notesApi.js";
|
|
import { isValidFilename } from "./utils/validation.js";
|
|
|
|
// Load CSS
|
|
const link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.href = '/extensions/comfyui-notes-sidebar/notes-sidebar.css';
|
|
document.head.appendChild(link);
|
|
|
|
class NotesSidebar {
|
|
constructor() {
|
|
this.store = new NotesStore();
|
|
this.tabsContainer = null;
|
|
this.contentArea = null;
|
|
this.textArea = null;
|
|
this.saveBtn = null;
|
|
this.renameBtn = null;
|
|
this.deleteBtn = null;
|
|
this.appendMenu = null;
|
|
this.searchInput = null;
|
|
this.searchResults = null;
|
|
this.isInitialized = false;
|
|
this.sidebarElement = null;
|
|
this.currentFontSize = app.extensionManager.setting.get('Notes Sidebar.Font Size') || 14;
|
|
this.leftButtonContainer = null;
|
|
|
|
// Listen to state changes
|
|
this.store.addEventListener('stateChanged', (event) => {
|
|
this.onStateChanged(event.detail);
|
|
});
|
|
}
|
|
|
|
onStateChanged(changeDetails) {
|
|
const { prevState, newState, updates } = changeDetails;
|
|
|
|
// Update tabs if notes changed, active note changed, or unsaved changes changed
|
|
if (updates.notes || updates.activeNote || updates.unsavedChanges) {
|
|
this.renderTabs();
|
|
}
|
|
|
|
// Update textarea if active note changed or note content changed
|
|
if (updates.activeNote || updates.noteContent) {
|
|
this.updateTextArea();
|
|
this.updateTextAreaState();
|
|
}
|
|
|
|
// Update button states if active note or unsaved changes changed
|
|
if (updates.activeNote || updates.unsavedChanges) {
|
|
this.updateButtonState();
|
|
}
|
|
|
|
// Update search results if active note changed and search is active
|
|
if (updates.activeNote && this.searchInput && this.searchInput.value.trim()) {
|
|
this.onSearchChange();
|
|
}
|
|
}
|
|
|
|
get notes() { return this.store.state.notes; }
|
|
get activeNote() { return this.store.state.activeNote; }
|
|
get unsavedChanges() { return this.store.state.unsavedChanges; }
|
|
get noteContent() { return this.store.state.noteContent; }
|
|
get savedContent() { return this.store.state.savedContent; }
|
|
|
|
updateTextArea() {
|
|
if (!this.textArea) return;
|
|
|
|
if (!this.activeNote) {
|
|
this.textArea.value = '';
|
|
return;
|
|
}
|
|
|
|
// Check if we have unsaved content for this note
|
|
if (this.noteContent.has(this.activeNote)) {
|
|
this.textArea.value = this.noteContent.get(this.activeNote);
|
|
} else {
|
|
this.textArea.value = '';
|
|
}
|
|
}
|
|
|
|
updateTextAreaState() {
|
|
if (!this.textArea) return;
|
|
|
|
const hasActiveNote = !!this.activeNote;
|
|
this.textArea.disabled = !hasActiveNote;
|
|
this.textArea.placeholder = hasActiveNote
|
|
? 'Start typing your notes...'
|
|
: 'Select a note to start editing...';
|
|
}
|
|
|
|
updateButtonState() {
|
|
if (!this.saveBtn || !this.renameBtn || !this.deleteBtn || !this.appendMenu) return;
|
|
|
|
const hasActiveNote = !!this.activeNote;
|
|
const hasUnsavedChanges = hasActiveNote && this.unsavedChanges.get(this.activeNote);
|
|
|
|
// Save button: enabled only when there's an active note AND unsaved changes
|
|
this.saveBtn.disabled = !hasActiveNote || !hasUnsavedChanges;
|
|
|
|
// Rename, delete, and append buttons: enabled only when there's an active note
|
|
this.renameBtn.disabled = !hasActiveNote;
|
|
this.deleteBtn.disabled = !hasActiveNote;
|
|
this.appendMenu.setDisabled(!hasActiveNote);
|
|
}
|
|
|
|
async init() {
|
|
await this.loadNotes();
|
|
}
|
|
|
|
async createSidebarContent(el) {
|
|
// Ensure the parent element has proper height
|
|
el.style.height = '100%';
|
|
el.style.display = 'flex';
|
|
el.style.flexDirection = 'column';
|
|
|
|
// If already initialized, just update the existing content
|
|
if (this.isInitialized && this.sidebarElement) {
|
|
el.innerHTML = '';
|
|
el.appendChild(this.sidebarElement);
|
|
// Trigger reactive updates when reopening
|
|
this.renderTabs();
|
|
this.updateTextArea();
|
|
this.updateTextAreaState();
|
|
return;
|
|
}
|
|
|
|
// Create new sidebar content
|
|
this.sidebarElement = document.createElement('div');
|
|
this.sidebarElement.className = 'notes-sidebar';
|
|
|
|
this.createTabsContainer(this.sidebarElement);
|
|
this.createMenuContainer(this.sidebarElement);
|
|
this.createSearchContainer(this.sidebarElement);
|
|
this.createContentArea(this.sidebarElement);
|
|
|
|
el.appendChild(this.sidebarElement);
|
|
this.isInitialized = true;
|
|
|
|
// Load notes after creating UI
|
|
await this.loadNotes();
|
|
|
|
// Ensure UI reflects the current state after initialization
|
|
this.renderTabs();
|
|
this.updateTextArea();
|
|
this.updateTextAreaState();
|
|
this.updateButtonState();
|
|
}
|
|
|
|
createTabsContainer(parent) {
|
|
const tabsWrapper = document.createElement('div');
|
|
tabsWrapper.className = 'notes-tabs-wrapper';
|
|
|
|
// Left side: tabs container for + button and note tabs
|
|
this.tabsContainer = document.createElement('div');
|
|
this.tabsContainer.className = 'notes-tabs-container';
|
|
|
|
const newNoteButtonGroup = document.createElement('div');
|
|
newNoteButtonGroup.className = 'notes-button-group';
|
|
|
|
const newTabBtn = document.createElement('button');
|
|
newTabBtn.innerHTML = '<i class="pi pi-plus"></i>';
|
|
newTabBtn.title = 'New Note';
|
|
newTabBtn.className = 'notes-new-tab-btn';
|
|
newTabBtn.onclick = () => this.createNewNote();
|
|
|
|
newNoteButtonGroup.appendChild(newTabBtn);
|
|
this.tabsContainer.appendChild(newNoteButtonGroup);
|
|
|
|
const rightControls = document.createElement('div');
|
|
rightControls.style.display = 'flex';
|
|
rightControls.style.gap = '10px';
|
|
|
|
const zoomButtonGroup = document.createElement('div');
|
|
zoomButtonGroup.className = 'notes-button-group';
|
|
|
|
const zoomOutBtn = document.createElement('button');
|
|
zoomOutBtn.innerHTML = '<i class="pi pi-search-minus"></i>';
|
|
zoomOutBtn.className = 'notes-btn';
|
|
zoomOutBtn.title = 'Zoom Out';
|
|
zoomOutBtn.onclick = () => this.zoomOut();
|
|
|
|
const zoomInBtn = document.createElement('button');
|
|
zoomInBtn.innerHTML = '<i class="pi pi-search-plus"></i>';
|
|
zoomInBtn.className = 'notes-btn';
|
|
zoomInBtn.title = 'Zoom In';
|
|
zoomInBtn.onclick = () => this.zoomIn();
|
|
|
|
zoomButtonGroup.appendChild(zoomOutBtn);
|
|
zoomButtonGroup.appendChild(zoomInBtn);
|
|
|
|
const refreshButtonGroup = document.createElement('div');
|
|
refreshButtonGroup.className = 'notes-button-group';
|
|
|
|
const refreshBtn = document.createElement('button');
|
|
refreshBtn.innerHTML = '<i class="pi pi-refresh"></i>';
|
|
refreshBtn.className = 'notes-btn refresh';
|
|
refreshBtn.title = 'Refresh';
|
|
refreshBtn.onclick = () => this.refreshNotes();
|
|
|
|
refreshButtonGroup.appendChild(refreshBtn);
|
|
|
|
rightControls.appendChild(zoomButtonGroup);
|
|
rightControls.appendChild(refreshButtonGroup);
|
|
|
|
tabsWrapper.appendChild(this.tabsContainer);
|
|
tabsWrapper.appendChild(rightControls);
|
|
parent.appendChild(tabsWrapper);
|
|
}
|
|
|
|
createMenuContainer(parent) {
|
|
const buttonContainer = document.createElement('div');
|
|
buttonContainer.className = 'notes-button-container';
|
|
|
|
this.leftButtonContainer = document.createElement('div');
|
|
this.leftButtonContainer.className = 'notes-button-group';
|
|
|
|
const rightButtonContainer = document.createElement('div');
|
|
rightButtonContainer.className = 'notes-button-group';
|
|
|
|
this.saveBtn = document.createElement('button');
|
|
this.saveBtn.innerHTML = '<i class="pi pi-save"></i>';
|
|
this.saveBtn.className = 'notes-btn save';
|
|
this.saveBtn.title = 'Save';
|
|
this.saveBtn.onclick = () => this.saveActiveNote();
|
|
this.saveBtn.disabled = true;
|
|
|
|
this.renameBtn = document.createElement('button');
|
|
this.renameBtn.innerHTML = '<i class="pi pi-file-edit"></i>';
|
|
this.renameBtn.className = 'notes-btn rename';
|
|
this.renameBtn.title = 'Rename';
|
|
this.renameBtn.onclick = () => this.renameActiveNote();
|
|
this.renameBtn.disabled = true;
|
|
|
|
this.deleteBtn = document.createElement('button');
|
|
this.deleteBtn.innerHTML = '<i class="pi pi-trash"></i>';
|
|
this.deleteBtn.className = 'notes-btn delete';
|
|
this.deleteBtn.title = 'Delete';
|
|
this.deleteBtn.onclick = () => this.deleteActiveNote();
|
|
this.deleteBtn.disabled = true;
|
|
|
|
this.leftButtonContainer.appendChild(this.saveBtn);
|
|
this.leftButtonContainer.appendChild(this.renameBtn);
|
|
// Append menu will be added later after textarea is created
|
|
rightButtonContainer.appendChild(this.deleteBtn);
|
|
|
|
buttonContainer.appendChild(this.leftButtonContainer);
|
|
buttonContainer.appendChild(rightButtonContainer);
|
|
|
|
parent.appendChild(buttonContainer);
|
|
}
|
|
|
|
createSearchContainer(parent) {
|
|
const searchContainer = document.createElement('div');
|
|
searchContainer.className = 'notes-search-container';
|
|
|
|
const inputWrapper = document.createElement('div');
|
|
inputWrapper.className = 'notes-search-input-wrapper';
|
|
|
|
this.searchInput = document.createElement('input');
|
|
this.searchInput.type = 'text';
|
|
this.searchInput.className = 'notes-search-input';
|
|
this.searchInput.placeholder = 'Search lines...';
|
|
this.searchInput.addEventListener('input', () => this.onSearchChange());
|
|
|
|
const clearButton = document.createElement('button');
|
|
clearButton.className = 'notes-search-clear';
|
|
clearButton.innerHTML = '<i class="pi pi-times-circle"></i>';
|
|
clearButton.title = 'Clear search';
|
|
clearButton.addEventListener('click', () => this.clearSearch());
|
|
|
|
this.searchResults = document.createElement('div');
|
|
this.searchResults.className = 'notes-search-results';
|
|
this.searchResults.style.display = 'none';
|
|
|
|
inputWrapper.appendChild(this.searchInput);
|
|
inputWrapper.appendChild(clearButton);
|
|
searchContainer.appendChild(inputWrapper);
|
|
searchContainer.appendChild(this.searchResults);
|
|
parent.appendChild(searchContainer);
|
|
}
|
|
|
|
createContentArea(parent) {
|
|
this.contentArea = document.createElement('div');
|
|
this.contentArea.className = 'notes-content-area';
|
|
|
|
this.textArea = document.createElement('textarea');
|
|
this.textArea.className = 'notes-textarea';
|
|
this.textArea.placeholder = 'Select a note to start editing...';
|
|
|
|
// Apply saved font size
|
|
this.updateFontSize();
|
|
|
|
// Initialize as disabled (will be enabled reactively if there's an active note)
|
|
this.textArea.disabled = true;
|
|
|
|
// Add event listener to track changes
|
|
this.textArea.addEventListener('input', () => this.onTextAreaChange());
|
|
|
|
// Add keydown event listener for Ctrl+S shortcut
|
|
this.textArea.addEventListener('keydown', (e) => this.onTextAreaKeyDown(e));
|
|
|
|
// Initialize AppendMenu with textarea reference and change handler
|
|
this.appendMenu = new AppendMenu(this.textArea, () => this.onTextAreaChange());
|
|
|
|
// Add append menu to the left button container
|
|
if (this.leftButtonContainer) {
|
|
this.leftButtonContainer.appendChild(this.appendMenu.createMenuElements());
|
|
}
|
|
|
|
this.contentArea.appendChild(this.textArea);
|
|
parent.appendChild(this.contentArea);
|
|
}
|
|
|
|
async loadNotes() {
|
|
try {
|
|
const notes = await NotesApi.getNotesList();
|
|
|
|
this.store.setNotes(notes);
|
|
|
|
if (notes.length > 0 && !this.activeNote) {
|
|
this.store.setActiveNote(notes[0]);
|
|
} else if (notes.length === 0) {
|
|
// Ensure active note is cleared when no notes exist
|
|
this.store.setActiveNote(null);
|
|
}
|
|
|
|
// Load content for active note after setting state
|
|
if (this.activeNote) {
|
|
await this.loadNoteContent(this.activeNote);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading notes:', error);
|
|
}
|
|
}
|
|
|
|
renderTabs() {
|
|
if (!this.tabsContainer) return;
|
|
|
|
const existingTabs = this.tabsContainer.querySelectorAll('.note-tab');
|
|
existingTabs.forEach(tab => tab.remove());
|
|
|
|
this.notes.forEach(noteName => {
|
|
const tab = document.createElement('button');
|
|
const isUnsaved = this.unsavedChanges.get(noteName) || false;
|
|
tab.textContent = (isUnsaved ? '* ' : '') + noteName;
|
|
tab.className = `note-tab ${this.activeNote === noteName ? 'active' : ''}`;
|
|
tab.title = noteName + (isUnsaved ? ' (unsaved)' : ''); // Full name in tooltip
|
|
tab.onclick = () => this.selectNote(noteName);
|
|
|
|
this.tabsContainer.appendChild(tab);
|
|
});
|
|
}
|
|
|
|
async selectNote(noteName) {
|
|
// Save current content before switching
|
|
if (this.activeNote) {
|
|
this.store.setNoteContent(this.activeNote, this.textArea.value);
|
|
}
|
|
|
|
this.store.setActiveNote(noteName);
|
|
await this.loadNoteContent(noteName);
|
|
}
|
|
|
|
async loadNoteContent(noteName) {
|
|
try {
|
|
// Check if we have unsaved content for this note
|
|
if (this.noteContent.has(noteName)) {
|
|
// Content is already in state, updateTextArea will handle it
|
|
return;
|
|
} else {
|
|
// Load from server
|
|
const content = await NotesApi.getNoteContent(noteName);
|
|
|
|
// Store both current and saved content
|
|
this.store.setNoteContent(noteName, content);
|
|
this.store.setSavedContent(noteName, content);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading note content:', error);
|
|
}
|
|
}
|
|
|
|
async saveActiveNote() {
|
|
try {
|
|
const content = this.textArea.value;
|
|
await NotesApi.saveNote(this.activeNote, content);
|
|
|
|
console.log('Note saved successfully');
|
|
// Mark as saved
|
|
this.store.setSavedContent(this.activeNote, content);
|
|
this.store.setNoteContent(this.activeNote, content);
|
|
this.store.setUnsavedChanges(this.activeNote, false);
|
|
} catch (error) {
|
|
console.error('Error saving note:', error);
|
|
}
|
|
}
|
|
|
|
async deleteActiveNote() {
|
|
if (!confirm(`Are you sure you want to delete "${this.activeNote}"?`)) return;
|
|
|
|
try {
|
|
await NotesApi.deleteNote(this.activeNote);
|
|
|
|
// Clean up tracking for deleted note
|
|
const deletedNote = this.activeNote;
|
|
|
|
// Clear active note first to ensure state change is triggered
|
|
this.store.setActiveNote(null);
|
|
this.store.deleteNoteFromState(deletedNote);
|
|
|
|
// Refresh notes list (this will handle setting activeNote correctly)
|
|
await this.refreshNotes();
|
|
} catch (error) {
|
|
console.error('Error deleting note:', error);
|
|
}
|
|
}
|
|
|
|
async renameActiveNote() {
|
|
let newName = prompt('Enter new note name:', this.activeNote);
|
|
if (!newName) return;
|
|
|
|
// Trim spaces
|
|
newName = newName.trim();
|
|
if (!newName || newName === this.activeNote) return;
|
|
|
|
try {
|
|
if (!isValidFilename(newName)) {
|
|
alert('Invalid filename! Please avoid special characters like / \\ : * ? " < > |');
|
|
return;
|
|
}
|
|
|
|
// Check if new name already exists
|
|
if (this.notes.includes(newName)) {
|
|
alert('A note with this name already exists!');
|
|
return;
|
|
}
|
|
|
|
// Get current content
|
|
const currentContent = this.textArea.value;
|
|
|
|
// Create new note with the content
|
|
await NotesApi.saveNote(newName, currentContent);
|
|
|
|
// Delete old note
|
|
await NotesApi.deleteNote(this.activeNote);
|
|
|
|
// Update tracking maps through store
|
|
const oldName = this.activeNote;
|
|
this.store.renameNoteInState(oldName, newName);
|
|
|
|
// Set saved content to what we just saved
|
|
this.store.setSavedContent(newName, currentContent);
|
|
this.store.setUnsavedChanges(newName, false);
|
|
|
|
// Update active note and refresh
|
|
this.store.setActiveNote(newName);
|
|
await this.refreshNotes();
|
|
} catch (error) {
|
|
console.error('Error renaming note:', error);
|
|
}
|
|
}
|
|
|
|
async createNewNote() {
|
|
let noteName;
|
|
|
|
// Keep asking for a name until user cancels or enters a valid unique name
|
|
while (true) {
|
|
noteName = prompt('Enter note name:');
|
|
if (!noteName) return; // User cancelled
|
|
|
|
// Trim spaces
|
|
noteName = noteName.trim();
|
|
|
|
// Validate filename safety
|
|
if (!isValidFilename(noteName)) {
|
|
alert('Invalid filename! Please avoid special characters like / \\ : * ? " < > |');
|
|
continue; // Ask again
|
|
}
|
|
|
|
// Check if note name already exists
|
|
if (this.notes.includes(noteName)) {
|
|
alert('A note with this name already exists!');
|
|
continue; // Ask again
|
|
}
|
|
|
|
break; // Valid unique name
|
|
}
|
|
|
|
try {
|
|
await NotesApi.saveNote(noteName, '');
|
|
|
|
await this.refreshNotes();
|
|
this.store.setActiveNote(noteName);
|
|
|
|
// Initialize tracking for new note
|
|
this.store.setNoteContent(noteName, '');
|
|
this.store.setSavedContent(noteName, '');
|
|
this.store.setUnsavedChanges(noteName, false);
|
|
} catch (error) {
|
|
console.error('Error creating note:', error);
|
|
}
|
|
}
|
|
|
|
async refreshNotes() {
|
|
// Preserve current content before refreshing
|
|
if (this.activeNote && this.textArea) {
|
|
this.store.setNoteContent(this.activeNote, this.textArea.value);
|
|
}
|
|
|
|
await this.loadNotes();
|
|
}
|
|
|
|
onTextAreaChange() {
|
|
const currentContent = this.textArea.value;
|
|
const savedContent = this.savedContent.get(this.activeNote) || '';
|
|
|
|
// Update current content
|
|
this.store.setNoteContent(this.activeNote, currentContent);
|
|
|
|
// Check if content has changed from saved version
|
|
const hasUnsavedChanges = currentContent !== savedContent;
|
|
this.store.setUnsavedChanges(this.activeNote, hasUnsavedChanges);
|
|
|
|
// Update search results if search is active
|
|
if (this.searchInput && this.searchInput.value.trim()) {
|
|
this.onSearchChange();
|
|
}
|
|
}
|
|
|
|
onTextAreaKeyDown(event) {
|
|
// Handle Ctrl+S shortcut for saving notes
|
|
if (event.ctrlKey && event.key === 's') {
|
|
const saveShortcutSetting = app.extensionManager.setting.get('Notes Sidebar.Save Shortcut') || 'note_only';
|
|
|
|
if (saveShortcutSetting === 'none') {
|
|
// No shortcut behavior - let ComfyUI handle it normally
|
|
return;
|
|
}
|
|
|
|
if (saveShortcutSetting === 'note_only') {
|
|
// Prevent ComfyUI from seeing the event
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
} else if (saveShortcutSetting === 'both') {
|
|
// Only prevent browser default, let ComfyUI handle it too
|
|
event.preventDefault();
|
|
}
|
|
|
|
// Save note if there's an active note and unsaved changes
|
|
if (this.activeNote && this.unsavedChanges.get(this.activeNote)) {
|
|
this.saveActiveNote();
|
|
}
|
|
}
|
|
}
|
|
|
|
onSearchChange() {
|
|
const searchText = this.searchInput.value.trim();
|
|
|
|
if (!searchText) {
|
|
// Hide search results when search is empty
|
|
this.searchResults.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
if (!this.activeNote || !this.textArea.value) {
|
|
// No active note or content to search
|
|
this.searchResults.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
const content = this.textArea.value;
|
|
const lines = content.split('\n');
|
|
const matchingLines = [];
|
|
|
|
lines.forEach((line, index) => {
|
|
if (line.toLowerCase().includes(searchText.toLowerCase())) {
|
|
matchingLines.push({
|
|
lineNumber: index + 1,
|
|
content: line
|
|
});
|
|
}
|
|
});
|
|
|
|
if (matchingLines.length === 0) {
|
|
this.searchResults.innerHTML = '<div class="notes-search-no-results">No matching lines found</div>';
|
|
} else {
|
|
this.searchResults.innerHTML = matchingLines.map(line =>
|
|
`<div class="notes-search-result" onclick="this.parentNode.parentNode.parentNode.querySelector('.notes-textarea').focus(); this.parentNode.parentNode.parentNode.querySelector('.notes-textarea').setSelectionRange(${this.getLineStartPosition(content, line.lineNumber - 1)}, ${this.getLineEndPosition(content, line.lineNumber - 1)});">
|
|
<span class="notes-search-line-number">${line.lineNumber}:</span>
|
|
<span class="notes-search-line-content">${this.highlightMatch(this.escapeHtml(line.content), searchText)}</span>
|
|
</div>`
|
|
).join('');
|
|
}
|
|
|
|
this.searchResults.style.display = 'block';
|
|
}
|
|
|
|
clearSearch() {
|
|
this.searchInput.value = '';
|
|
this.searchResults.style.display = 'none';
|
|
this.searchInput.focus();
|
|
}
|
|
|
|
getLineStartPosition(content, lineIndex) {
|
|
const lines = content.split('\n');
|
|
let position = 0;
|
|
for (let i = 0; i < lineIndex; i++) {
|
|
position += lines[i].length + 1; // +1 for newline character
|
|
}
|
|
return position;
|
|
}
|
|
|
|
getLineEndPosition(content, lineIndex) {
|
|
const lines = content.split('\n');
|
|
let position = 0;
|
|
for (let i = 0; i <= lineIndex; i++) {
|
|
position += lines[i].length;
|
|
if (i < lineIndex) position += 1; // +1 for newline character
|
|
}
|
|
return position;
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
highlightMatch(text, filterText) {
|
|
const escapedFilter = filterText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
const regex = new RegExp(escapedFilter, 'gi');
|
|
return text.replace(regex, function(match) {
|
|
return `<mark>${match}</mark>`;
|
|
});
|
|
}
|
|
|
|
zoomIn() {
|
|
if (this.currentFontSize < 24) { // Max font size
|
|
this.currentFontSize += 1;
|
|
this.updateFontSize();
|
|
}
|
|
}
|
|
|
|
zoomOut() {
|
|
if (this.currentFontSize > 8) { // Min font size
|
|
this.currentFontSize -= 1;
|
|
this.updateFontSize();
|
|
}
|
|
}
|
|
|
|
updateFontSize() {
|
|
if (this.textArea) {
|
|
this.textArea.style.fontSize = `${this.currentFontSize}px`;
|
|
}
|
|
// Save to settings
|
|
app.extensionManager.setting.set('Notes Sidebar.Font Size', this.currentFontSize);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
const notesSidebar = new NotesSidebar();
|
|
|
|
app.registerExtension({
|
|
name: "comfyui.notes.sidebar",
|
|
settings: [
|
|
{
|
|
id: "Notes Sidebar.Save Shortcut",
|
|
name: "Notes Save Shortcut",
|
|
type: "combo",
|
|
options: [
|
|
{ value: "none", text: "None (click save button)" },
|
|
{ value: "note_only", text: "Ctrl+S (note only, when focused)" },
|
|
{ value: "both", text: "Ctrl+S (note + workflow)" }
|
|
],
|
|
defaultValue: "note_only"
|
|
},
|
|
{
|
|
id: "Notes Sidebar.Font Size",
|
|
name: "Notes Font Size",
|
|
type: "slider",
|
|
attrs: {
|
|
min: 8,
|
|
max: 24,
|
|
step: 1
|
|
},
|
|
defaultValue: 14
|
|
}
|
|
],
|
|
async setup() {
|
|
await notesSidebar.init();
|
|
|
|
app.extensionManager.registerSidebarTab({
|
|
id: "notes",
|
|
icon: "pi pi-pen-to-square",
|
|
title: "Notes",
|
|
tooltip: "Notes",
|
|
type: "custom",
|
|
render: (el) => {
|
|
notesSidebar.createSidebarContent(el);
|
|
},
|
|
});
|
|
}
|
|
});
|