migrate
This commit is contained in:
4
public/scripts/extensions/attachments/attach-button.html
Normal file
4
public/scripts/extensions/attachments/attach-button.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div id="attachFile" class="list-group-item flex-container flexGap5" data-i18n="[title]Attach a file or image to a current chat." title="Attach a file or image to a current chat.">
|
||||
<div class="fa-fw fa-solid fa-paperclip extensionsMenuExtensionButton"></div>
|
||||
<span data-i18n="Attach a File">Attach a File</span>
|
||||
</div>
|
51
public/scripts/extensions/attachments/fandom-scrape.html
Normal file
51
public/scripts/extensions/attachments/fandom-scrape.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label for="fandomScrapeInput" data-i18n="Enter a URL or the ID of a Fandom wiki page to scrape:">
|
||||
Enter a URL or the ID of a Fandom wiki page to scrape:
|
||||
</label>
|
||||
<small>
|
||||
<span data-i18n="Examples:">Examples:</span>
|
||||
<code>https://harrypotter.fandom.com/</code>
|
||||
<span data-i18n="or">or</span>
|
||||
<code>harrypotter</code>
|
||||
</small>
|
||||
<input type="text" id="fandomScrapeInput" name="fandomScrapeInput" class="text_pole" placeholder="">
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label for="fandomScrapeFilter">
|
||||
Optional regex to pick the content by its title:
|
||||
</label>
|
||||
<small>
|
||||
<span data-i18n="Example:">Example:</span>
|
||||
<code>/(Azkaban|Weasley)/gi</code>
|
||||
</small>
|
||||
<input type="text" id="fandomScrapeFilter" name="fandomScrapeFilter" class="text_pole" placeholder="">
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label>
|
||||
Output format:
|
||||
</label>
|
||||
<label class="checkbox_label justifyLeft" for="fandomScrapeOutputSingle">
|
||||
<input id="fandomScrapeOutputSingle" type="radio" name="fandomScrapeOutput" value="single" checked>
|
||||
<div class="flex-container flexFlowColumn flexNoGap">
|
||||
<span data-i18n="Single file">
|
||||
Single file
|
||||
</span>
|
||||
<small data-i18n="All articles will be concatenated into a single file.">
|
||||
All articles will be concatenated into a single file.
|
||||
</small>
|
||||
</div>
|
||||
</label>
|
||||
<label class="checkbox_label justifyLeft" for="fandomScrapeOutputMulti">
|
||||
<input id="fandomScrapeOutputMulti" type="radio" name="fandomScrapeOutput" value="multi">
|
||||
<div class="flex-container flexFlowColumn flexNoGap">
|
||||
<span data-i18n="File per article">
|
||||
File per article
|
||||
</span>
|
||||
<small data-i18n="Each article will be saved as a separate file.">
|
||||
Not recommended. Each article will be saved as a separate file.
|
||||
</small>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
8
public/scripts/extensions/attachments/files-dropped.html
Normal file
8
public/scripts/extensions/attachments/files-dropped.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<div class="flex-container justifyCenter alignItemsBaseline">
|
||||
<span>Save <span class="droppedFilesCount">{{count}}</span> file(s) to...</span>
|
||||
<select class="droppedFilesTarget">
|
||||
{{#each targets}}
|
||||
<option value="{{this}}">{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
381
public/scripts/extensions/attachments/index.js
Normal file
381
public/scripts/extensions/attachments/index.js
Normal file
@@ -0,0 +1,381 @@
|
||||
import { event_types, eventSource, saveSettingsDebounced } from '../../../script.js';
|
||||
import { deleteAttachment, getDataBankAttachments, getDataBankAttachmentsForSource, getFileAttachment, uploadFileAttachmentToServer } from '../../chats.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
|
||||
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandExecutor } from '../../slash-commands/SlashCommandExecutor.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
|
||||
/**
|
||||
* List of attachment sources
|
||||
* @type {string[]}
|
||||
*/
|
||||
const TYPES = ['global', 'character', 'chat'];
|
||||
const FIELDS = ['name', 'url'];
|
||||
|
||||
/**
|
||||
* Get attachments from the data bank. Includes disabled attachments.
|
||||
* @param {string} [source] Source for the attachments
|
||||
* @returns {import('../../chats').FileAttachment[]} List of attachments
|
||||
*/
|
||||
function getAttachments(source) {
|
||||
if (!source || !TYPES.includes(source)) {
|
||||
return getDataBankAttachments(true);
|
||||
}
|
||||
|
||||
return getDataBankAttachmentsForSource(source, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment by a single name or URL.
|
||||
* @param {import('../../chats').FileAttachment[]} attachments List of attachments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {import('../../chats').FileAttachment} Attachment
|
||||
*/
|
||||
function getAttachmentByField(attachments, value) {
|
||||
const match = (a) => String(a).trim().toLowerCase() === String(value).trim().toLowerCase();
|
||||
const fullMatchByURL = attachments.find(it => match(it.url));
|
||||
const fullMatchByName = attachments.find(it => match(it.name));
|
||||
return fullMatchByURL || fullMatchByName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment by multiple fields.
|
||||
* @param {import('../../chats').FileAttachment[]} attachments List of attachments
|
||||
* @param {string[]} values Name and URL of the attachment to search for
|
||||
* @returns
|
||||
*/
|
||||
function getAttachmentByFields(attachments, values) {
|
||||
for (const value of values) {
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
if (attachment) {
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for listing attachments in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @returns {string} JSON string of the list of attachments
|
||||
*/
|
||||
function listDataBankAttachments(args) {
|
||||
const attachments = getAttachments(args?.source);
|
||||
const field = args?.field;
|
||||
return JSON.stringify(attachments.map(a => FIELDS.includes(field) ? a[field] : a.url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for getting text from an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Content of the attachment
|
||||
*/
|
||||
async function getDataBankText(args, value) {
|
||||
if (!value) {
|
||||
toastr.warning('No attachment name or URL provided.');
|
||||
return;
|
||||
}
|
||||
|
||||
const attachments = getAttachments(args?.source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await getFileAttachment(attachment.url);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for adding an attachment to the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Content of the attachment
|
||||
* @returns {Promise<string>} URL of the attachment
|
||||
*/
|
||||
async function uploadDataBankAttachment(args, value) {
|
||||
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
||||
const name = args?.name || new Date().toLocaleString();
|
||||
const file = new File([value], name, { type: 'text/plain' });
|
||||
const url = await uploadFileAttachmentToServer(file, source);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for updating an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Content of the attachment
|
||||
* @returns {Promise<string>} URL of the attachment
|
||||
*/
|
||||
async function updateDataBankAttachment(args, value) {
|
||||
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
||||
const attachments = getAttachments(source);
|
||||
const attachment = getAttachmentByFields(attachments, [args?.url, args?.name]);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
await deleteAttachment(attachment, source, () => { }, false);
|
||||
const file = new File([value], attachment.name, { type: 'text/plain' });
|
||||
const url = await uploadFileAttachmentToServer(file, source);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for deleting an attachment from the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Empty string
|
||||
*/
|
||||
async function deleteDataBankAttachment(args, value) {
|
||||
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
||||
const attachments = getAttachments(source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
await deleteAttachment(attachment, source, () => { }, false);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for disabling an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Empty string
|
||||
*/
|
||||
async function disableDataBankAttachment(args, value) {
|
||||
const attachments = getAttachments(args?.source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (extension_settings.disabled_attachments.includes(attachment.url)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
extension_settings.disabled_attachments.push(attachment.url);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for enabling an attachment in the data bank.
|
||||
* @param {object} args Named arguments
|
||||
* @param {string} value Name or URL of the attachment
|
||||
* @returns {Promise<string>} Empty string
|
||||
*/
|
||||
async function enableDataBankAttachment(args, value) {
|
||||
const attachments = getAttachments(args?.source);
|
||||
const attachment = getAttachmentByField(attachments, value);
|
||||
|
||||
if (!attachment) {
|
||||
toastr.warning('Attachment not found.');
|
||||
return '';
|
||||
}
|
||||
|
||||
const index = extension_settings.disabled_attachments.indexOf(attachment.url);
|
||||
if (index === -1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
extension_settings.disabled_attachments.splice(index, 1);
|
||||
return '';
|
||||
}
|
||||
|
||||
function cleanUpAttachments() {
|
||||
let shouldSaveSettings = false;
|
||||
if (extension_settings.character_attachments) {
|
||||
Object.values(extension_settings.character_attachments).flat().filter(a => a.text).forEach(a => {
|
||||
shouldSaveSettings = true;
|
||||
delete a.text;
|
||||
});
|
||||
}
|
||||
if (Array.isArray(extension_settings.attachments)) {
|
||||
extension_settings.attachments.filter(a => a.text).forEach(a => {
|
||||
shouldSaveSettings = true;
|
||||
delete a.text;
|
||||
});
|
||||
}
|
||||
if (shouldSaveSettings) {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
eventSource.on(event_types.APP_READY, cleanUpAttachments);
|
||||
const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {});
|
||||
const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {});
|
||||
$('#data_bank_wand_container').append(manageButton);
|
||||
$('#attach_file_wand_container').append(attachButton);
|
||||
|
||||
/** A collection of local enum providers for this context of data bank */
|
||||
const localEnumProviders = {
|
||||
/**
|
||||
* All attachments in the data bank based on the source argument. If not provided, defaults to 'chat'.
|
||||
* @param {'name' | 'url'} returnField - Whether the enum should return the 'name' field or the 'url'
|
||||
* @param {'chat' | 'character' | 'global' | ''} fallbackSource - The source to use if the source argument is not provided. Empty string to use all sources.
|
||||
* */
|
||||
attachments: (returnField = 'name', fallbackSource = 'chat') => (/** @type {SlashCommandExecutor} */ executor) => {
|
||||
const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? fallbackSource;
|
||||
if (source instanceof SlashCommandClosure) throw new Error('Argument \'source\' does not support closures');
|
||||
const attachments = getAttachments(source);
|
||||
|
||||
return attachments.map(attachment => new SlashCommandEnumValue(
|
||||
returnField === 'name' ? attachment.name : attachment.url,
|
||||
`${enumIcons.getStateIcon(!extension_settings.disabled_attachments.includes(attachment.url))} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`,
|
||||
enumTypes.enum, enumIcons.file));
|
||||
},
|
||||
};
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db',
|
||||
callback: () => {
|
||||
document.getElementById('manageAttachments')?.click();
|
||||
return '';
|
||||
},
|
||||
aliases: ['databank', 'data-bank'],
|
||||
helpString: 'Open the data bank',
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-list',
|
||||
callback: listDataBankAttachments,
|
||||
aliases: ['databank-list', 'data-bank-list'],
|
||||
helpString: 'List attachments in the Data Bank as a JSON-serialized array. Optionally, provide the source of the attachments and the field to list by.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachments.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
new SlashCommandNamedArgument('field', 'The field to list by.', ARGUMENT_TYPE.STRING, false, false, 'url', FIELDS),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.LIST,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-get',
|
||||
callback: getDataBankText,
|
||||
aliases: ['databank-get', 'data-bank-get'],
|
||||
helpString: 'Get attachment text from the Data Bank. Either provide the name or URL of the attachment. Optionally, provide the source of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
acceptsMultiple: false,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-add',
|
||||
callback: uploadDataBankAttachment,
|
||||
aliases: ['databank-add', 'data-bank-add'],
|
||||
helpString: 'Add an attachment to the Data Bank. If name is not provided, it will be generated automatically. Returns the URL of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
new SlashCommandNamedArgument('name', 'The name of the attachment.', ARGUMENT_TYPE.STRING, false, false),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-update',
|
||||
callback: updateDataBankAttachment,
|
||||
aliases: ['databank-update', 'data-bank-update'],
|
||||
helpString: 'Update an attachment in the Data Bank, preserving its name. Returns a new URL of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'name',
|
||||
description: 'The name of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.attachments('name'),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'url',
|
||||
description: 'The URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: localEnumProviders.attachments('url'),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
|
||||
],
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-disable',
|
||||
callback: disableDataBankAttachment,
|
||||
aliases: ['databank-disable', 'data-bank-disable'],
|
||||
helpString: 'Disable an attachment in the Data Bank by its name or URL. Optionally, provide the source of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-enable',
|
||||
callback: enableDataBankAttachment,
|
||||
aliases: ['databank-enable', 'data-bank-enable'],
|
||||
helpString: 'Enable an attachment in the Data Bank by its name or URL. Optionally, provide the source of the attachment.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments('name', ''),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'db-delete',
|
||||
callback: deleteDataBankAttachment,
|
||||
aliases: ['databank-delete', 'data-bank-delete'],
|
||||
helpString: 'Delete an attachment from the Data Bank.',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'The name or URL of the attachment.',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: localEnumProviders.attachments(),
|
||||
}),
|
||||
],
|
||||
}));
|
||||
});
|
6
public/scripts/extensions/attachments/manage-button.html
Normal file
6
public/scripts/extensions/attachments/manage-button.html
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
<div id="manageAttachments" class="list-group-item flex-container flexGap5" title="View global, character, or data files.">
|
||||
<div class="fa-fw fa-solid fa-book-open-reader extensionsMenuExtensionButton"></div>
|
||||
<span data-i18n="Open Data Bank">Open Data Bank</span>
|
||||
</div>
|
157
public/scripts/extensions/attachments/manager.html
Normal file
157
public/scripts/extensions/attachments/manager.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<div class="wide100p padding5 dataBankAttachments">
|
||||
<h2 class="marginBot5">
|
||||
<span data-i18n="Data Bank">
|
||||
Data Bank
|
||||
</span>
|
||||
</h2>
|
||||
<div data-i18n="These files will be available for extensions that support attachments (e.g. Vector Storage).">
|
||||
These files will be available for extensions that support attachments (e.g. Vector Storage).
|
||||
</div>
|
||||
<div class="marginTopBot5">
|
||||
<span data-i18n="Supported file types: Plain Text, PDF, Markdown, HTML, EPUB." >
|
||||
Supported file types: Plain Text, PDF, Markdown, HTML, EPUB.
|
||||
</span>
|
||||
<span data-i18n="Drag and drop files here to upload.">
|
||||
Drag and drop files here to upload.
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex-container marginTopBot5">
|
||||
<input type="search" id="attachmentSearch" class="attachmentSearch text_pole margin0 flex1" placeholder="Search...">
|
||||
<select id="attachmentSort" class="attachmentSort text_pole margin0 flex1 textarea_compact">
|
||||
<option data-sort-field="created" data-sort-order="desc" data-i18n="Date (Newest First)">
|
||||
Date (Newest First)
|
||||
</option>
|
||||
<option data-sort-field="created" data-sort-order="asc" data-i18n="Date (Oldest First)">
|
||||
Date (Oldest First)
|
||||
</option>
|
||||
<option data-sort-field="name" data-sort-order="asc" data-i18n="Name (A-Z)">
|
||||
Name (A-Z)
|
||||
</option>
|
||||
<option data-sort-field="name" data-sort-order="desc" data-i18n="Name (Z-A)">
|
||||
Name (Z-A)
|
||||
</option>
|
||||
<option data-sort-field="size" data-sort-order="asc" data-i18n="Size (Smallest First)">
|
||||
Size (Smallest First)
|
||||
</option>
|
||||
<option data-sort-field="size" data-sort-order="desc" data-i18n="Size (Largest First)">
|
||||
Size (Largest First)
|
||||
</option>
|
||||
</select>
|
||||
<label class="margin0 menu_button menu_button_icon attachmentsBulkEditButton">
|
||||
<i class="fa-solid fa-edit"></i>
|
||||
<span data-i18n="Bulk Edit">Bulk Edit</span>
|
||||
<input type="checkbox" class="displayNone attachmentsBulkEditCheckbox" hidden>
|
||||
</label>
|
||||
</div>
|
||||
<div class="attachmentBulkActionsContainer flex-container marginTopBot5 alignItemsBaseline">
|
||||
<div class="flex-container">
|
||||
<div class="menu_button menu_button_icon bulkActionSelectAll" title="Select all *visible* attachments">
|
||||
<i class="fa-solid fa-check-square"></i>
|
||||
<span data-i18n="Select All">Select All</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionSelectNone" title="Deselect all *visible* attachments">
|
||||
<i class="fa-solid fa-square"></i>
|
||||
<span data-i18n="Select None">Select None</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionDisable" title="Disable selected attachments">
|
||||
<i class="fa-solid fa-comment-slash"></i>
|
||||
<span data-i18n="Disable">Disable</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionEnable" title="Enable selected attachments">
|
||||
<i class="fa-solid fa-comment"></i>
|
||||
<span data-i18n="Enable">Enable</span>
|
||||
</div>
|
||||
<div class="menu_button menu_button_icon bulkActionDelete" title="Delete selected attachments">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
<span data-i18n="Delete">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="justifyLeft globalAttachmentsBlock marginBot10">
|
||||
<h3 class="globalAttachmentsTitle margin0 title_restorable">
|
||||
<span data-i18n="Global Attachments">
|
||||
Global Attachments
|
||||
</span>
|
||||
<div class="openActionModalButton menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<span data-i18n="Add">Add</span>
|
||||
</div>
|
||||
</h3>
|
||||
<small data-i18n="These files are available for all characters in all chats.">
|
||||
These files are available for all characters in all chats.
|
||||
</small>
|
||||
<div class="globalAttachmentsList attachmentsList"></div>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="justifyLeft characterAttachmentsBlock marginBot10">
|
||||
<h3 class="characterAttachmentsTitle margin0 title_restorable">
|
||||
<span data-i18n="Character Attachments">
|
||||
Character Attachments
|
||||
</span>
|
||||
<div class="openActionModalButton menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<span data-i18n="Add">Add</span>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<strong><small class="characterAttachmentsName"></small></strong>
|
||||
<small>
|
||||
<span data-i18n="These files are available for the current character in all chats they are in.">
|
||||
These files are available for the current character in all chats they are in.
|
||||
</span>
|
||||
<span>
|
||||
<span data-i18n="Saved locally. Not exported.">
|
||||
Saved locally. Not exported.
|
||||
</span>
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
<div class="characterAttachmentsList attachmentsList"></div>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="justifyLeft chatAttachmentsBlock marginBot10">
|
||||
<h3 class="chatAttachmentsTitle margin0 title_restorable">
|
||||
<span data-i18n="Chat Attachments">
|
||||
Chat Attachments
|
||||
</span>
|
||||
<div class="openActionModalButton menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<span data-i18n="Add">Add</span>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<strong><small class="chatAttachmentsName"></small></strong>
|
||||
<small data-i18n="These files are available for all characters in the current chat.">
|
||||
These files are available for all characters in the current chat.
|
||||
</small>
|
||||
</div>
|
||||
<div class="chatAttachmentsList attachmentsList"></div>
|
||||
</div>
|
||||
|
||||
<div class="attachmentListItemTemplate template_element">
|
||||
<div class="attachmentListItem flex-container alignItemsCenter flexGap10">
|
||||
<div class="attachmentListItemCheckboxContainer"><input type="checkbox" class="attachmentListItemCheckbox"></div>
|
||||
<div class="attachmentFileIcon fa-solid fa-file-alt"></div>
|
||||
<div class="attachmentListItemName flex1"></div>
|
||||
<small class="attachmentListItemCreated"></small>
|
||||
<small class="attachmentListItemSize"></small>
|
||||
<div class="viewAttachmentButton right_menu_button fa-fw fa-solid fa-magnifying-glass" title="View attachment content"></div>
|
||||
<div class="disableAttachmentButton right_menu_button fa-fw fa-solid fa-comment" title="Disable attachment"></div>
|
||||
<div class="enableAttachmentButton right_menu_button fa-fw fa-solid fa-comment-slash" title="Enable attachment"></div>
|
||||
<div class="moveAttachmentButton right_menu_button fa-fw fa-solid fa-arrows-alt" title="Move attachment"></div>
|
||||
<div class="editAttachmentButton right_menu_button fa-fw fa-solid fa-pencil" title="Edit attachment"></div>
|
||||
<div class="downloadAttachmentButton right_menu_button fa-fw fa-solid fa-download" title="Download attachment"></div>
|
||||
<div class="deleteAttachmentButton right_menu_button fa-fw fa-solid fa-trash" title="Delete attachment"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actionButtonTemplate">
|
||||
<div class="actionButton list-group-item flex-container flexGap5" style="align-items: center;" title="">
|
||||
<i class="actionButtonIcon"></i>
|
||||
<img class="actionButtonImg"/>
|
||||
<span class="actionButtonText"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actionButtonsModal popper-modal options-content list-group"></div>
|
||||
</div>
|
11
public/scripts/extensions/attachments/manifest.json
Normal file
11
public/scripts/extensions/attachments/manifest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"display_name": "Data Bank (Chat Attachments)",
|
||||
"loading_order": 3,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Cohee1207",
|
||||
"version": "1.0.0",
|
||||
"homePage": "https://github.com/ChuQuadrant/ChuQuadrant"
|
||||
}
|
54
public/scripts/extensions/attachments/mediawiki-scrape.html
Normal file
54
public/scripts/extensions/attachments/mediawiki-scrape.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label for="scrapeInput" data-i18n="Enter a base URL of the MediaWiki to scrape.">
|
||||
Enter a <strong>base URL</strong> of the MediaWiki to scrape.
|
||||
</label>
|
||||
<i data-i18n="Don't include the page name!">
|
||||
Don't include the page name!
|
||||
</i>
|
||||
<small>
|
||||
<span data-i18n="Examples:">Examples:</span>
|
||||
<code>https://streetcat.wiki/index.php</code>
|
||||
<span data-i18n="or">or</span>
|
||||
<code>https://tcrf.net</code>
|
||||
</small>
|
||||
<input type="text" id="scrapeInput" name="scrapeInput" class="text_pole" placeholder="">
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label for="scrapeFilter">
|
||||
Optional regex to pick the content by its title:
|
||||
</label>
|
||||
<small>
|
||||
<span data-i18n="Example:">Example:</span>
|
||||
<code>/Mr. (Fresh|Snack)/gi</code>
|
||||
</small>
|
||||
<input type="text" id="scrapeFilter" name="scrapeFilter" class="text_pole" placeholder="">
|
||||
</div>
|
||||
<div class="flex-container flexFlowColumn">
|
||||
<label>
|
||||
Output format:
|
||||
</label>
|
||||
<label class="checkbox_label justifyLeft" for="scrapeOutputSingle">
|
||||
<input id="scrapeOutputSingle" type="radio" name="scrapeOutput" value="single" checked>
|
||||
<div class="flex-container flexFlowColumn flexNoGap">
|
||||
<span data-i18n="Single file">
|
||||
Single file
|
||||
</span>
|
||||
<small data-i18n="All articles will be concatenated into a single file.">
|
||||
All articles will be concatenated into a single file.
|
||||
</small>
|
||||
</div>
|
||||
</label>
|
||||
<label class="checkbox_label justifyLeft" for="scrapeOutputMulti">
|
||||
<input id="scrapeOutputMulti" type="radio" name="scrapeOutput" value="multi">
|
||||
<div class="flex-container flexFlowColumn flexNoGap">
|
||||
<span data-i18n="File per article">
|
||||
File per article
|
||||
</span>
|
||||
<small data-i18n="Each article will be saved as a separate file.">
|
||||
Not recommended. Each article will be saved as a separate file.
|
||||
</small>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,8 @@
|
||||
<div class="flex-container justifyCenter alignItemsBaseline">
|
||||
<span>Move <strong class="moveAttachmentName">{{name}}</strong> to...</span>
|
||||
<select class="moveAttachmentTarget">
|
||||
{{#each targets}}
|
||||
<option value="{{this}}">{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
10
public/scripts/extensions/attachments/notepad.html
Normal file
10
public/scripts/extensions/attachments/notepad.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<div class="flex-container flexFlowColumn height100p">
|
||||
<label for="notepadFileName">
|
||||
File Name
|
||||
</label>
|
||||
<input type="text" class="text_pole" id="notepadFileName" name="notepadFileName" value="" />
|
||||
<labels>
|
||||
File Content
|
||||
</label>
|
||||
<textarea id="notepadFileContent" name="notepadFileContent" class="text_pole textarea_compact monospace flex1" placeholder="Enter your notes here."></textarea>
|
||||
</div>
|
63
public/scripts/extensions/attachments/style.css
Normal file
63
public/scripts/extensions/attachments/style.css
Normal file
@@ -0,0 +1,63 @@
|
||||
.attachmentsList:empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.attachmentsList:empty::before {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
content: "No data";
|
||||
font-weight: bolder;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.8;
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
.attachmentListItem {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.attachmentListItem.disabled .attachmentListItemName {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.attachmentListItem.disabled .attachmentFileIcon {
|
||||
opacity: 0.75;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.attachmentListItemSize {
|
||||
min-width: 4em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.attachmentListItemCreated {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.attachmentListItemCheckboxContainer,
|
||||
.attachmentBulkActionsContainer,
|
||||
.attachmentsBulkEditCheckbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@supports selector(:has(*)) {
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentsBulkEditButton {
|
||||
color: var(--golden);
|
||||
}
|
||||
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentBulkActionsContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentListItemCheckboxContainer {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.dataBankAttachments:has(.attachmentsBulkEditCheckbox:checked) .attachmentFileIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
3
public/scripts/extensions/attachments/web-scrape.html
Normal file
3
public/scripts/extensions/attachments/web-scrape.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div data-i18n="Enter web URLs to scrape (one per line):">
|
||||
Enter web URLs to scrape (one per line):
|
||||
</div>
|
20
public/scripts/extensions/attachments/youtube-scrape.html
Normal file
20
public/scripts/extensions/attachments/youtube-scrape.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<div>
|
||||
<strong data-i18n="Enter a video URL to download its transcript.">
|
||||
Enter a video URL or ID to download its transcript.
|
||||
</strong>
|
||||
<div data-i18n="Examples:" class="m-t-1">
|
||||
Examples:
|
||||
</div>
|
||||
<ul class="justifyLeft">
|
||||
<li>https://www.youtube.com/watch?v=jV1vkHv4zq8</li>
|
||||
<li>https://youtu.be/nlLhw1mtCFA</li>
|
||||
<li>TDpxx5UqrVU</li>
|
||||
</ul>
|
||||
<label>
|
||||
Language code (optional 2-letter ISO code):
|
||||
</label>
|
||||
<input type="text" class="text_pole" name="youtubeLanguageCode" placeholder="e.g. en">
|
||||
<label>
|
||||
Video ID:
|
||||
</label>
|
||||
</div>
|
Reference in New Issue
Block a user