-
🌐 admin_panel.phpWeb, 1.54 kB⬇
-
🌐 api.jsWeb, 5.89 kB⬇
-
🌐 api.phpWeb, 6.54 kB⬇
-
🌐 app.jsWeb, 84.72 kB⬇
-
🌐 base.cssWeb, 2.23 kB⬇
-
🌐 context.jsWeb, 9.37 kB⬇
-
🌐 conversation-index.jsWeb, 10.19 kB⬇
-
🌐 conversation.jsWeb, 16.61 kB⬇
-
🌐 conversations.jsWeb, 15.16 kB⬇
-
🌐 diction.jsWeb, 935 B⬇
-
🌐 document.jsWeb, 947 B⬇
-
🌐 dynamic.phpWeb, 430 B⬇
-
📷 favicon-lg.pngImage, 9.11 kB⬇
-
📷 favicon.pngImage, 3.12 kB⬇
-
🌐 index.phpWeb, 17.38 kB⬇
-
🌐 indexjs.jsWeb, 572 B⬇
-
🌐 login.phpWeb, 767 B⬇
-
❓ manifest.webmanifestUnknown, 262 B⬇
-
🌐 memory.jsWeb, 407 B⬇
-
🌐 models.jsonWeb, 4.27 kB⬇
-
🌐 notebook-index.jsWeb, 721 B⬇
-
⚠️ php.iniOS File, 356 B⬇
-
🌐 scratchpad-conversation.jsWeb, 2.09 kB⬇
-
🌐 scratchpad-index.jsWeb, 10.19 kB⬇
-
🌐 storage.jsWeb, 15.25 kB⬇
-
🌐 style.cssWeb, 20.81 kB⬇
-
🌐 theme_dark.jsonWeb, 266 B⬇
-
🌐 theme_light.jsonWeb, 267 B⬇
-
🌐 users.jsWeb, 18.18 kB⬇
import { createElementFromHTML } from './util/dom-utils.js';
import { SelectionManager } from './util/selection-manager.js';
import { customConfirm } from './util/confirm-dialog.js';
/**
* class ScratchpadIndex
* Manages the secondary navigation index for Scratchpad conversations.
* Handles the display, selection, bookmarking, and deletion of scratchpad conversations
* within the index panel.
*/
class ScratchpadIndex {
#storage;
#app_callbacks;
#selection;
div_index_list;
btn_conversation_index_select;
btn_conversation_index_favorite;
btn_conversation_index_delete;
BREAKPOINT_MOBILE;
/**
* Initializes the ScratchpadIndex instance with storage, callbacks, element references, and breakpoints.
* @param {Storage} storage_instance - Storage manager instance.
* @param {object} app_callbacks - Application callback methods.
* @param {object} elements - DOM element references.
* @param {object} breakpoints - Responsive breakpoint constants.
*/
constructor(storage_instance, app_callbacks, elements, breakpoints) {
this.#storage = storage_instance;
this.#app_callbacks = app_callbacks;
this.div_index_list = elements.div_index_list;
this.btn_conversation_index_select = elements.btn_conversation_index_select;
this.btn_conversation_index_favorite = elements.btn_conversation_index_favorite;
this.btn_conversation_index_delete = elements.btn_conversation_index_delete;
this.BREAKPOINT_MOBILE = breakpoints.BREAKPOINT_MOBILE;
this.#selection = new SelectionManager(() => this._update_selection_ui());
}
//region Scratchpad Selection and Actions
/**
* Updates the selection controls UI state.
* @private
*/
_update_selection_ui() {
this.#selection.updateControls(
this.btn_conversation_index_select,
[this.btn_conversation_index_favorite, this.btn_conversation_index_delete],
'Select'
);
}
/**
* Toggles multi-selection mode for scratchpad conversations in the index.
*/
toggle_response_selection_mode() {
this.#selection.toggleMode();
this._update_selection_ui();
this.on_conversation_index_updated();
}
/**
* Toggles the bookmarked status of all scratchpad conversations currently selected.
*/
bookmark_selected_responses() {
const selectedGuids = this.#selection.selectedItems;
if (selectedGuids.length === 0) return;
selectedGuids.forEach(guid => {
const conversation = this.#storage.get_conversation(guid);
if (conversation) {
const isBookmarked = conversation.BOOKMARKED || false;
conversation.BOOKMARKED = !isBookmarked;
this.#storage.save_conversation(guid, conversation);
}
});
this.#selection.clear();
this._update_selection_ui();
this.on_conversation_index_updated();
this.#app_callbacks.on_conversation_updated_main_panel(false);
}
/**
* Deletes all scratchpad conversations currently selected from the index after user confirmation.
*/
async delete_selected_responses() {
const selectedGuids = this.#selection.selectedItems;
const count = selectedGuids.length;
if (count === 0) return;
const message = count === 1
? 'Are you sure you want to delete this scratchpad?'
: `Are you sure you want to delete ${count} selected scratchpads?`;
if (await customConfirm('Delete Scratchpads', message)) {
const config = this.#storage.get_app_config();
const selected_guid = config[this.#storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
const toDelete = [...selectedGuids];
toDelete.forEach(guid => {
this.#storage.index_delete(guid);
});
this.#selection.clear();
this._update_selection_ui();
const remainingCount = this.get_scratchpad_conversations().length;
if (remainingCount > 0) {
if (toDelete.includes(selected_guid)) {
this.#app_callbacks.update_app_config(this.#storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID, 'scratchpad');
}
this.on_conversation_index_updated();
this.#app_callbacks.on_conversation_updated_main_panel(false);
} else {
this.#app_callbacks.close_conversation();
this.#app_callbacks.on_conversation_updated_main_panel(false);
}
}
}
//endregion
//region HTML Generation
/**
* Creates and returns a DOM element for a single scratchpad entry in the index.
* @param {Object} conversation - The conversation object.
* @param {string} guid - The unique identifier of the scratchpad.
* @returns {HTMLElement} The created index item element.
*/
create_scratchpad_index_item(conversation, guid) {
const isChecked = this.#selection.isSelected(guid);
const title = this.get_scratchpad_title(conversation);
const isBookmarked = conversation.BOOKMARKED || false;
const html = this._create_scratchpad_item_html(title, guid, isBookmarked, this.#selection.isSelectionMode, isChecked);
const index_item = createElementFromHTML(html);
if (!index_item) return document.createElement('div');
const chk = index_item.querySelector('.response-item-checkbox');
if (chk) {
chk.onclick = (e) => {
e.stopPropagation();
this.#selection.toggleItem(guid, chk.checked);
this._update_selection_ui();
};
}
index_item.onclick = () => {
if (window.innerWidth <= this.BREAKPOINT_MOBILE) {
this.#app_callbacks.set_i_open(false);
this.#app_callbacks.apply_panels_layout();
}
this.#app_callbacks.update_app_config(this.#storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID, guid);
this.#app_callbacks.on_conversation_updated_main_panel();
};
return index_item;
}
/**
* Generates the HTML string for a single scratchpad index item.
* @param {string} title - The title of the scratchpad.
* @param {string} guid - The GUID of the scratchpad.
* @param {boolean} isBookmarked - Whether it is bookmarked.
* @param {boolean} isSelectionMode - Whether selection mode is active.
* @param {boolean} isChecked - Whether checked.
* @returns {string} The HTML string.
*/
_create_scratchpad_item_html(title, guid, isBookmarked, isSelectionMode, isChecked) {
const iconClass = isBookmarked ? 'bookmark-icon-active' : 'bookmark-icon-inactive';
return `
<div class="div-index-item div-index-item-flex ${isBookmarked ? 'div-index-item-bookmarked' : ''}"
data-guid="${guid}">
${isSelectionMode ? `<input type="checkbox" ${isChecked ? 'checked' : ''} class="response-item-checkbox index-item-checkbox-margin">` : ''}
<span class="index-item-icon as-icon ${iconClass}">🔖</span>
<span class="index-item-text-grow">${title}</span>
</div>
`;
}
/**
* Determines the display title for a scratchpad conversation.
* @param {Object} conversation - The conversation object.
* @returns {string} The title.
*/
get_scratchpad_title(conversation) {
if (!conversation) return 'New Scratchpad';
const history = conversation[this.#storage.KEY_CONVERSATION_HISTORY] || [];
if (history.length > 0) {
for (let i = history.length - 1; i >= 0; i--) {
const item = history[i];
if (item && item.title) {
return item.title;
}
}
}
return conversation[this.#storage.KEY_CONVERSATION_TITLE] || 'New Scratchpad';
}
/**
* Retrieves all scratchpad conversations from the storage index.
* @returns {Array<Object>} The scratchpad conversations.
*/
get_scratchpad_conversations() {
const index = this.#storage.get_app_index() || [];
const scratchpads = [];
index.forEach(item => {
const guid = item[this.#storage.KEY_INDEX_GUID];
const conversation = this.#storage.get_conversation(guid);
const type = conversation?.[this.#storage.KEY_CONVERSATION_TYPE] || item?.[this.#storage.KEY_CONVERSATION_TYPE];
if (type === this.#storage.CONVERSATION_TYPE_SCRATCHPAD) {
scratchpads.push({ guid, conversation: conversation || {}, date_updated: item[this.#storage.KEY_INDEX_DATE_UPDATED] });
}
});
scratchpads.sort((a, b) => (b.date_updated || 0) - (a.date_updated || 0));
return scratchpads;
}
//endregion
//region UI Updates
/**
* Highlights the active scratchpad conversation in the index.
*/
highlight_active_index_item() {
if (!this.div_index_list) return;
const config = this.#storage.get_app_config();
const selected_guid = config[this.#storage.KEY_CONFIG_SELECTED_CONVERSATION_GUID];
const indexItems = this.div_index_list.querySelectorAll('.div-index-item');
indexItems.forEach((item) => {
if (item.dataset.guid === selected_guid) {
item.classList.add('div-index-item-selected');
} else {
item.classList.remove('div-index-item-selected');
}
});
}
/**
* Creates and returns the DOM element for the "New Scratchpad" trigger button.
* @returns {HTMLElement}
*/
create_new_scratchpad_trigger_item() {
const html = `
<button class="btn-new-scratchpad">
<span class="index-item-icon as-icon" style="margin-right: 0.5em;">➕</span>
<span>New Scratchpad</span>
</button>
`;
const div = createElementFromHTML(html);
if (!div) return document.createElement('button');
div.onclick = (e) => {
e.stopPropagation();
this.#app_callbacks.create_new_scratchpad();
};
return div;
}
/**
* Renders the scratchpad index panel.
*/
on_conversation_index_updated() {
const scratchpads = this.get_scratchpad_conversations();
if (!this.div_index_list) return;
this.div_index_list.innerHTML = '';
// Add "+ New Scratchpad" creator item at the very top
this.div_index_list.appendChild(this.create_new_scratchpad_trigger_item());
if (scratchpads.length > 0) {
if (this.btn_conversation_index_select) this.btn_conversation_index_select.disabled = false;
} else {
if (this.btn_conversation_index_select) this.btn_conversation_index_select.disabled = true;
}
this._update_selection_ui();
if (scratchpads.length > 0) {
const bookmarked = scratchpads.filter(item => item.conversation.BOOKMARKED === true);
const others = scratchpads.filter(item => item.conversation.BOOKMARKED !== true);
bookmarked.forEach(item => {
this.div_index_list.appendChild(this.create_scratchpad_index_item(item.conversation, item.guid));
});
if (bookmarked.length > 0 && others.length > 0) {
const hr = document.createElement('hr');
hr.className = 'hr-chat-response-divider';
this.div_index_list.appendChild(hr);
}
others.forEach(item => {
this.div_index_list.appendChild(this.create_scratchpad_index_item(item.conversation, item.guid));
});
}
this.highlight_active_index_item();
}
//endregion
}
export default ScratchpadIndex;
scratchpad-index.js
×
Type: Web, text/plain
10.19 Kilobytes
Last Modified 2026-09-04 14:24:09
⬇ Download File
Type: Web, text/plain
10.19 Kilobytes
Last Modified 2026-09-04 14:24:09
⬇ Download File