Implement creator's note style tag preferences (#3979) * Implement creator's note style tag preferences * Decouple external media preference from style preference * Allow explicitly empty prefixes in decodeStyleTags * Fix Copilot comments * Refactor global styles management into StylesPreference class * Refactor openAttachmentManager to return an object instead of an array * Unify header structure * Re-render characters panel on setting initial preference * Add note about classname prefixing * Rename event handler
Signed| @@ -5451,9 +5451,10 @@ | |||
| 5451 | </div> | 5451 | </div> |
| 5452 | <hr> | 5452 | <hr> |
| 5453 | <div id="spoiler_free_desc" class="flex-container flexFlowColumn flex1 flexNoGap"> | 5453 | <div id="spoiler_free_desc" class="flex-container flexFlowColumn flex1 flexNoGap"> |
| 5454 | <div id="creators_notes_div" class="title_restorable"> | 5454 | <div id="creators_notes_div" class="title_restorable flexGap5"> |
| 5455 | <span data-i18n="Creator's Notes">Creator's Notes</span> | 5455 | <span class="flex1" data-i18n="Creator's Notes">Creator's Notes</span> |
| 5456 | <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small> | 5456 | <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small> |
| 5457 | <div id="creators_note_styles_button" class="margin0 menu_button fa-solid fa-palette fa-fw" title="Allow / Forbid the use of global styles for this character." data-i18n="[title]Allow / Forbid the use of global styles for this character."></div> | ||
| 5457 | <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div> | 5458 | <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div> |
| 5458 | </div> | 5459 | </div> |
| 5459 | <div id="creator_notes_spoiler" class="flex1"></div> | 5460 | <div id="creator_notes_spoiler" class="flex1"></div> |
| @@ -251,7 +251,7 @@ import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_set | |||
| 251 | import { hideLoader, showLoader } from './scripts/loader.js'; | 251 | import { hideLoader, showLoader } from './scripts/loader.js'; |
| 252 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; | 252 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; |
| 253 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js'; | 253 | import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js'; |
| 254 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js'; | 254 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities } from './scripts/chats.js'; |
| 255 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; | 255 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; |
| 256 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; | 256 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; |
| 257 | import { currentUser, setUserControls } from './scripts/user.js'; | 257 | import { currentUser, setUserControls } from './scripts/user.js'; |
| @@ -544,7 +544,7 @@ console.debug('Character context menu initialized', characterContextMenu); | |||
| 544 | // Markdown converter | 544 | // Markdown converter |
| 545 | export let mesForShowdownParse; //intended to be used as a context to compare showdown strings against | 545 | export let mesForShowdownParse; //intended to be used as a context to compare showdown strings against |
| 546 | /** @type {import('showdown').Converter} */ | 546 | /** @type {import('showdown').Converter} */ |
| 547 | let converter; | 547 | export let converter; |
| 548 | 548 | ||
| 549 | // array for prompt token calculations | 549 | // array for prompt token calculations |
| 550 | console.debug('initializing Prompt Itemization Array on Startup'); | 550 | console.debug('initializing Prompt Itemization Array on Startup'); |
| @@ -978,6 +978,7 @@ async function firstLoadInit() { | |||
| 978 | await getClientVersion(); | 978 | await getClientVersion(); |
| 979 | await readSecretState(); | 979 | await readSecretState(); |
| 980 | await initLocales(); | 980 | await initLocales(); |
| 981 | initChatUtilities(); | ||
| 981 | initDefaultSlashCommands(); | 982 | initDefaultSlashCommands(); |
| 982 | initTextGenModels(); | 983 | initTextGenModels(); |
| 983 | initOpenAI(); | 984 | initOpenAI(); |
| @@ -2232,7 +2233,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san | |||
| 2232 | }; | 2233 | }; |
| 2233 | mes = encodeStyleTags(mes); | 2234 | mes = encodeStyleTags(mes); |
| 2234 | mes = DOMPurify.sanitize(mes, config); | 2235 | mes = DOMPurify.sanitize(mes, config); |
| 2235 | mes = decodeStyleTags(mes); | 2236 | mes = decodeStyleTags(mes, { prefix: '.mes_text ' }); |
| 2236 | 2237 | ||
| 2237 | return mes; | 2238 | return mes; |
| 2238 | } | 2239 | } |
| @@ -8248,7 +8249,7 @@ export function select_selected_character(chid, { switchMenu = true } = {}) { | |||
| 8248 | $('#description_textarea').val(characters[chid].description); | 8249 | $('#description_textarea').val(characters[chid].description); |
| 8249 | $('#character_world').val(characters[chid].data?.extensions?.world || ''); | 8250 | $('#character_world').val(characters[chid].data?.extensions?.world || ''); |
| 8250 | $('#creator_notes_textarea').val(characters[chid].data?.creator_notes || characters[chid].creatorcomment); | 8251 | $('#creator_notes_textarea').val(characters[chid].data?.creator_notes || characters[chid].creatorcomment); |
| 8251 | $('#creator_notes_spoiler').html(DOMPurify.sanitize(converter.makeHtml(substituteParams(characters[chid].data?.creator_notes) || characters[chid].creatorcomment), { MESSAGE_SANITIZE: true })); | 8252 | $('#creator_notes_spoiler').html(formatCreatorNotes(characters[chid].data?.creator_notes || characters[chid].creatorcomment, characters[chid].avatar)); |
| 8252 | $('#character_version_textarea').val(characters[chid].data?.character_version || ''); | 8253 | $('#character_version_textarea').val(characters[chid].data?.character_version || ''); |
| 8253 | $('#system_prompt_textarea').val(characters[chid].data?.system_prompt || ''); | 8254 | $('#system_prompt_textarea').val(characters[chid].data?.system_prompt || ''); |
| 8254 | $('#post_history_instructions_textarea').val(characters[chid].data?.post_history_instructions || ''); | 8255 | $('#post_history_instructions_textarea').val(characters[chid].data?.post_history_instructions || ''); |
| @@ -8329,7 +8330,7 @@ function select_rm_create({ switchMenu = true } = {}) { | |||
| 8329 | $('#description_textarea').val(create_save.description); | 8330 | $('#description_textarea').val(create_save.description); |
| 8330 | $('#character_world').val(create_save.world); | 8331 | $('#character_world').val(create_save.world); |
| 8331 | $('#creator_notes_textarea').val(create_save.creator_notes); | 8332 | $('#creator_notes_textarea').val(create_save.creator_notes); |
| 8332 | $('#creator_notes_spoiler').html(DOMPurify.sanitize(converter.makeHtml(create_save.creator_notes), { MESSAGE_SANITIZE: true })); | 8333 | $('#creator_notes_spoiler').html(formatCreatorNotes(create_save.creator_notes, '')); |
| 8333 | $('#post_history_instructions_textarea').val(create_save.post_history_instructions); | 8334 | $('#post_history_instructions_textarea').val(create_save.post_history_instructions); |
| 8334 | $('#system_prompt_textarea').val(create_save.system_prompt); | 8335 | $('#system_prompt_textarea').val(create_save.system_prompt); |
| 8335 | $('#tags_textarea').val(create_save.tags); | 8336 | $('#tags_textarea').val(create_save.tags); |
| @@ -1,6 +1,6 @@ | |||
| 1 | // Move chat functions here from script.js (eventually) | 1 | // Move chat functions here from script.js (eventually) |
| 2 | 2 | ||
| 3 | import { Popper, css } from '../lib.js'; | 3 | import { Popper, css, DOMPurify } from '../lib.js'; |
| 4 | import { | 4 | import { |
| 5 | addCopyToCodeBlocks, | 5 | addCopyToCodeBlocks, |
| 6 | appendMediaToMessage, | 6 | appendMediaToMessage, |
| @@ -23,6 +23,8 @@ import { | |||
| 23 | neutralCharacterName, | 23 | neutralCharacterName, |
| 24 | updateChatMetadata, | 24 | updateChatMetadata, |
| 25 | system_message_types, | 25 | system_message_types, |
| 26 | converter, | ||
| 27 | substituteParams, | ||
| 26 | getSystemMessageByType, | 28 | getSystemMessageByType, |
| 27 | printMessages, | 29 | printMessages, |
| 28 | clearChat, | 30 | clearChat, |
| @@ -475,10 +477,12 @@ export function encodeStyleTags(text) { | |||
| 475 | /** | 477 | /** |
| 476 | * Sanitizes custom style tags in the message text to prevent DOM pollution. | 478 | * Sanitizes custom style tags in the message text to prevent DOM pollution. |
| 477 | * @param {string} text Message text | 479 | * @param {string} text Message text |
| 480 | * @param {object} options Options object | ||
| 481 | * @param {string} options.prefix Prefix the selectors with this value | ||
| 478 | * @returns {string} Sanitized message text | 482 | * @returns {string} Sanitized message text |
| 479 | * @copyright https://github.com/kwaroran/risuAI | 483 | * @copyright https://github.com/kwaroran/risuAI |
| 480 | */ | 484 | */ |
| 481 | export function decodeStyleTags(text) { | 485 | export function decodeStyleTags(text, { prefix } = { prefix: '.mes_text ' }) { |
| 482 | const styleDecodeRegex = /<custom-style>(.+?)<\/custom-style>/gms; | 486 | const styleDecodeRegex = /<custom-style>(.+?)<\/custom-style>/gms; |
| 483 | const mediaAllowed = isExternalMediaAllowed(); | 487 | const mediaAllowed = isExternalMediaAllowed(); |
| 484 | 488 | ||
| @@ -494,7 +498,7 @@ export function decodeStyleTags(text) { | |||
| 494 | return v; | 498 | return v; |
| 495 | }).join(' '); | 499 | }).join(' '); |
| 496 | 500 | ||
| 497 | rule.selectors[i] = '.mes_text ' + selectors; | 501 | rule.selectors[i] = prefix + selectors; |
| 498 | } | 502 | } |
| 499 | } | 503 | } |
| 500 | } | 504 | } |
| @@ -532,6 +536,200 @@ export function decodeStyleTags(text) { | |||
| 532 | }); | 536 | }); |
| 533 | } | 537 | } |
| 534 | 538 | ||
| 539 | /** | ||
| 540 | * Class to manage style preferences for characters. | ||
| 541 | */ | ||
| 542 | class StylesPreference { | ||
| 543 | /** | ||
| 544 | * Creates a new StylesPreference instance. | ||
| 545 | * @param {string|null} avatarId - The avatar ID of the character | ||
| 546 | */ | ||
| 547 | constructor(avatarId) { | ||
| 548 | this.avatarId = avatarId; | ||
| 549 | } | ||
| 550 | |||
| 551 | /** | ||
| 552 | * Gets the account storage key for the style preference. | ||
| 553 | */ | ||
| 554 | get key() { | ||
| 555 | return `AllowGlobalStyles-${this.avatarId}`; | ||
| 556 | } | ||
| 557 | |||
| 558 | /** | ||
| 559 | * Checks if a preference exists for this character. | ||
| 560 | * @returns {boolean} True if preference exists, false otherwise | ||
| 561 | */ | ||
| 562 | exists() { | ||
| 563 | return this.avatarId | ||
| 564 | ? accountStorage.getItem(this.key) !== null | ||
| 565 | : true; // No character == assume preference is set | ||
| 566 | } | ||
| 567 | |||
| 568 | /** | ||
| 569 | * Gets the current style preference. | ||
| 570 | * @returns {boolean} True if global styles are allowed, false otherwise | ||
| 571 | */ | ||
| 572 | get() { | ||
| 573 | return this.avatarId | ||
| 574 | ? accountStorage.getItem(this.key) === 'true' | ||
| 575 | : false; // Always disabled when creating a new character | ||
| 576 | } | ||
| 577 | |||
| 578 | /** | ||
| 579 | * Sets the global styles preference. | ||
| 580 | * @param {boolean} allowed - Whether global styles are allowed | ||
| 581 | */ | ||
| 582 | set(allowed) { | ||
| 583 | if (this.avatarId) { | ||
| 584 | accountStorage.setItem(this.key, String(allowed)); | ||
| 585 | } | ||
| 586 | } | ||
| 587 | } | ||
| 588 | |||
| 589 | /** | ||
| 590 | * Formats creator notes in the message text. | ||
| 591 | * @param {string} text Raw Markdown text | ||
| 592 | * @param {string} avatarId Avatar ID | ||
| 593 | * @returns {string} Formatted HTML text | ||
| 594 | */ | ||
| 595 | export function formatCreatorNotes(text, avatarId) { | ||
| 596 | const preference = new StylesPreference(avatarId); | ||
| 597 | const sanitizeStyles = !preference.get(); | ||
| 598 | const decodeStyleParam = { prefix: sanitizeStyles ? '#creator_notes_spoiler ' : '' }; | ||
| 599 | /** @type {import('dompurify').Config & { MESSAGE_SANITIZE: boolean }} */ | ||
| 600 | const config = { | ||
| 601 | RETURN_DOM: false, | ||
| 602 | RETURN_DOM_FRAGMENT: false, | ||
| 603 | RETURN_TRUSTED_TYPE: false, | ||
| 604 | MESSAGE_SANITIZE: true, | ||
| 605 | ADD_TAGS: ['custom-style'], | ||
| 606 | }; | ||
| 607 | |||
| 608 | let html = converter.makeHtml(substituteParams(text)); | ||
| 609 | html = encodeStyleTags(html); | ||
| 610 | html = DOMPurify.sanitize(html, config); | ||
| 611 | html = decodeStyleTags(html, decodeStyleParam); | ||
| 612 | |||
| 613 | return html; | ||
| 614 | } | ||
| 615 | |||
| 616 | async function openGlobalStylesPreferenceDialog() { | ||
| 617 | if (selected_group) { | ||
| 618 | toastr.info(t`To change the global styles preference, please select a character individually.`); | ||
| 619 | return; | ||
| 620 | } | ||
| 621 | |||
| 622 | const entityId = getCurrentEntityId(); | ||
| 623 | const preference = new StylesPreference(entityId); | ||
| 624 | const currentValue = preference.get(); | ||
| 625 | |||
| 626 | const template = $(await renderTemplateAsync('globalStylesPreference')); | ||
| 627 | |||
| 628 | const allowedRadio = template.find('#global_styles_allowed'); | ||
| 629 | const forbiddenRadio = template.find('#global_styles_forbidden'); | ||
| 630 | |||
| 631 | allowedRadio.on('change', () => { | ||
| 632 | preference.set(true); | ||
| 633 | allowedRadio.prop('checked', true); | ||
| 634 | forbiddenRadio.prop('checked', false); | ||
| 635 | }); | ||
| 636 | |||
| 637 | forbiddenRadio.on('change', () => { | ||
| 638 | preference.set(false); | ||
| 639 | allowedRadio.prop('checked', false); | ||
| 640 | forbiddenRadio.prop('checked', true); | ||
| 641 | }); | ||
| 642 | |||
| 643 | const currentPreferenceRadio = currentValue ? allowedRadio : forbiddenRadio; | ||
| 644 | template.find(currentPreferenceRadio).prop('checked', true); | ||
| 645 | |||
| 646 | await callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: false, large: false }); | ||
| 647 | |||
| 648 | // Re-render the notes if the preference changed | ||
| 649 | const newValue = preference.get(); | ||
| 650 | if (newValue !== currentValue) { | ||
| 651 | $('#rm_button_selected_ch').trigger('click'); | ||
| 652 | setGlobalStylesButtonClass(newValue); | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | async function checkForCreatorNotesStyles() { | ||
| 657 | // Don't do anything if in group chat or not in a chat | ||
| 658 | if (selected_group || this_chid === undefined) { | ||
| 659 | return; | ||
| 660 | } | ||
| 661 | |||
| 662 | const notes = characters[this_chid].data?.creator_notes || characters[this_chid].creatorcomment; | ||
| 663 | const avatarId = characters[this_chid].avatar; | ||
| 664 | const styleContents = getStyleContentsFromMarkdown(notes); | ||
| 665 | |||
| 666 | if (!styleContents) { | ||
| 667 | setGlobalStylesButtonClass(null); | ||
| 668 | return; | ||
| 669 | } | ||
| 670 | |||
| 671 | const preference = new StylesPreference(avatarId); | ||
| 672 | const hasPreference = preference.exists(); | ||
| 673 | if (!hasPreference) { | ||
| 674 | const template = $(await renderTemplateAsync('globalStylesPopup')); | ||
| 675 | template.find('textarea').val(styleContents); | ||
| 676 | const confirmResult = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { | ||
| 677 | wide: false, | ||
| 678 | large: false, | ||
| 679 | okButton: t`Just to Creator's Notes`, | ||
| 680 | cancelButton: t`Apply to the entire app`, | ||
| 681 | }); | ||
| 682 | |||
| 683 | switch (confirmResult) { | ||
| 684 | case POPUP_RESULT.AFFIRMATIVE: | ||
| 685 | preference.set(false); | ||
| 686 | break; | ||
| 687 | case POPUP_RESULT.NEGATIVE: | ||
| 688 | preference.set(true); | ||
| 689 | break; | ||
| 690 | case POPUP_RESULT.CANCELLED: | ||
| 691 | preference.set(false); | ||
| 692 | break; | ||
| 693 | } | ||
| 694 | |||
| 695 | $('#rm_button_selected_ch').trigger('click'); | ||
| 696 | } | ||
| 697 | |||
| 698 | const currentPreference = preference.get(); | ||
| 699 | setGlobalStylesButtonClass(currentPreference); | ||
| 700 | } | ||
| 701 | |||
| 702 | /** | ||
| 703 | * Sets the class of the global styles button based on the state. | ||
| 704 | * @param {boolean|null} state State of the button | ||
| 705 | */ | ||
| 706 | function setGlobalStylesButtonClass(state) { | ||
| 707 | const button = $('#creators_note_styles_button'); | ||
| 708 | button.toggleClass('empty', state === null); | ||
| 709 | button.toggleClass('allowed', state === true); | ||
| 710 | button.toggleClass('forbidden', state === false); | ||
| 711 | } | ||
| 712 | |||
| 713 | /** | ||
| 714 | * Extracts the contents of all style elements from the Markdown text. | ||
| 715 | * @param {string} text Markdown text | ||
| 716 | * @returns {string} The joined contents of all style elements | ||
| 717 | */ | ||
| 718 | function getStyleContentsFromMarkdown(text) { | ||
| 719 | if (!text) { | ||
| 720 | return ''; | ||
| 721 | } | ||
| 722 | |||
| 723 | const div = document.createElement('div'); | ||
| 724 | const html = converter.makeHtml(substituteParams(text)); | ||
| 725 | div.innerHTML = html; | ||
| 726 | const styleElements = Array.from(div.querySelectorAll('style')); | ||
| 727 | return styleElements | ||
| 728 | .filter(s => s.textContent.trim().length > 0) | ||
| 729 | .map(s => s.textContent.trim()) | ||
| 730 | .join('\n\n'); | ||
| 731 | } | ||
| 732 | |||
| 535 | async function openExternalMediaOverridesDialog() { | 733 | async function openExternalMediaOverridesDialog() { |
| 536 | const entityId = getCurrentEntityId(); | 734 | const entityId = getCurrentEntityId(); |
| 537 | 735 | ||
| @@ -1037,12 +1235,12 @@ async function openAttachmentManager() { | |||
| 1037 | popper.update(); | 1235 | popper.update(); |
| 1038 | }); | 1236 | }); |
| 1039 | 1237 | ||
| 1040 | return [popper, bodyListener]; | 1238 | return { popper, bodyListener }; |
| 1041 | }).filter(Boolean); | 1239 | }).filter(Boolean); |
| 1042 | 1240 | ||
| 1043 | return () => { | 1241 | return () => { |
| 1044 | modalButtonData.forEach(p => { | 1242 | modalButtonData.forEach(p => { |
| 1045 | const [popper, bodyListener] = p; | 1243 | const { popper,bodyListener } = p; |
| 1046 | popper.destroy(); | 1244 | popper.destroy(); |
| 1047 | document.body.removeEventListener('click', bodyListener); | 1245 | document.body.removeEventListener('click', bodyListener); |
| 1048 | }); | 1246 | }); |
| @@ -1466,7 +1664,7 @@ export function registerFileConverter(mimeType, converter) { | |||
| 1466 | converters[mimeType] = converter; | 1664 | converters[mimeType] = converter; |
| 1467 | } | 1665 | } |
| 1468 | 1666 | ||
| 1469 | jQuery(function () { | 1667 | export function initChatUtilities() { |
| 1470 | $(document).on('click', '.mes_hide', async function () { | 1668 | $(document).on('click', '.mes_hide', async function () { |
| 1471 | const messageBlock = $(this).closest('.mes'); | 1669 | const messageBlock = $(this).closest('.mes'); |
| 1472 | const messageId = Number(messageBlock.attr('mesid')); | 1670 | const messageId = Number(messageBlock.attr('mesid')); |
| @@ -1645,6 +1843,10 @@ jQuery(function () { | |||
| 1645 | reloadCurrentChat(); | 1843 | reloadCurrentChat(); |
| 1646 | }); | 1844 | }); |
| 1647 | 1845 | ||
| 1846 | $('#creators_note_styles_button').on('click', function () { | ||
| 1847 | openGlobalStylesPreferenceDialog(); | ||
| 1848 | }); | ||
| 1849 | |||
| 1648 | $(document).on('click', '.mes_img', expandMessageImage); | 1850 | $(document).on('click', '.mes_img', expandMessageImage); |
| 1649 | $(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage); | 1851 | $(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage); |
| 1650 | $(document).on('click', '.mes_img_delete', deleteMessageImage); | 1852 | $(document).on('click', '.mes_img_delete', deleteMessageImage); |
| @@ -1679,4 +1881,6 @@ jQuery(function () { | |||
| 1679 | fileInput.files = dataTransfer.files; | 1881 | fileInput.files = dataTransfer.files; |
| 1680 | await onFileAttach(fileInput.files[0]); | 1882 | await onFileAttach(fileInput.files[0]); |
| 1681 | }); | 1883 | }); |
| 1682 | }); | 1884 | |
| 1885 | eventSource.on(event_types.CHAT_CHANGED, checkForCreatorNotesStyles); | ||
| 1886 | } | ||
| @@ -0,0 +1,27 @@ | |||
| 1 | <div class="flex-container flexFlowColumn"> | ||
| 2 | <h3 data-i18n="Creator's Notes contain CSS style tags. Do you want to apply them just to Creator's Notes or to the entire application?" class="margin0"> | ||
| 3 | Creator's Notes contain CSS style tags. Do you want to apply them just to Creator's Notes or to the entire application? | ||
| 4 | </h3> | ||
| 5 | <h4 data-i18n="CAUTION: Malformed styles may cause issues." class="neutral_warning"> | ||
| 6 | CAUTION: Malformed styles may cause issues. | ||
| 7 | </h4> | ||
| 8 | <hr> | ||
| 9 | <small> | ||
| 10 | <span data-i18n="To change the preference later, use the"> | ||
| 11 | To change the preference later, use the | ||
| 12 | </span> | ||
| 13 | <code class="fa-solid fa-palette"></code> | ||
| 14 | <span data-i18n="button in the Creator's Notes block."> | ||
| 15 | button in the Creator's Notes block. | ||
| 16 | </span> | ||
| 17 | </small> | ||
| 18 | <textarea class="text_pole textarea_compact monospace" rows="8" readonly></textarea> | ||
| 19 | <small class="justifyLeft"> | ||
| 20 | <b data-i18n="Note:"> | ||
| 21 | Note: | ||
| 22 | </b> | ||
| 23 | <span data-i18n="Class names will be automatically prefixed with 'custom-'."> | ||
| 24 | Class names will be automatically prefixed with 'custom-'. | ||
| 25 | </span> | ||
| 26 | </small> | ||
| 27 | </div> | ||
| @@ -0,0 +1,16 @@ | |||
| 1 | <div class="flex-container flexFlowColumn"> | ||
| 2 | <h3 data-i18n="Choose how to apply CSS style tags if they are defined in Creator's Notes of this character:" class="margin0"> | ||
| 3 | Choose how to apply CSS style tags if they are defined in Creator's Notes of this character: | ||
| 4 | </h3> | ||
| 5 | <h4 data-i18n="CAUTION: Malformed styles may cause issues." class="neutral_warning"> | ||
| 6 | CAUTION: Malformed styles may cause issues. | ||
| 7 | </h4> | ||
| 8 | <label class="checkbox_label" for="global_styles_forbidden"> | ||
| 9 | <input type="radio" id="global_styles_forbidden" name="global_styles_preference" /> | ||
| 10 | <span data-i18n="Just to Creator's Notes">Just to Creator's Notes</span> | ||
| 11 | </label> | ||
| 12 | <label class="checkbox_label" for="global_styles_allowed"> | ||
| 13 | <input type="radio" id="global_styles_allowed" name="global_styles_preference" /> | ||
| 14 | <span data-i18n="Apply to the entire app">Apply to the entire app</span> | ||
| 15 | </label> | ||
| 16 | </div> | ||