Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@whiteball
Last active January 1, 2023 05:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whiteball/20013e480b3cba97f829fcb9b1c5e458 to your computer and use it in GitHub Desktop.
Save whiteball/20013e480b3cba97f829fcb9b1c5e458 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name AIのべりすと メモリと脚注をタグでグループ化
// @namespace https://ai-novelist-share.geo.jp/
// @version 0.1
// @description AIのべりすとのメモリと脚注をそれぞれ、特定のルールで追加したコメント行により内容をグループ化し、プルダウンリストで有効にしたいテキストを選んで切り替えられるようにするスクリプトです。
// @author しらたま
// @match https://ai-novel.com/novel.php
// @icon https://www.google.com/s2/favicons?sz=64&domain=ai-novel.com
// @updateURL https://gist.github.com/whiteball/20013e480b3cba97f829fcb9b1c5e458/raw/ai_novelist_tagged_memory.user.js
// @downloadURL https://gist.github.com/whiteball/20013e480b3cba97f829fcb9b1c5e458/raw/ai_novelist_tagged_memory.user.js
// @supportURL https://gist.github.com/whiteball/20013e480b3cba97f829fcb9b1c5e458
// @grant none
// ==/UserScript==
(function() {
'use strict';
const targetList = []
for (const target of ['memory', 'authorsnote']) {
const targetElement = document.getElementById(target)
if (targetElement) {
targetList.push({element: targetElement, name: target})
}
}
const prefix_main = '@_$\$', prefix_part = '@_$', prefix_current = '@_$$'
const prefix_main_reg = new RegExp(/^@_\$\\\$/), prefix_part_reg = new RegExp(/^@_\$([^\$]+)\$/), prefix_current_reg = new RegExp(/^@_\$\$(.+)\$$/)
class taggedText {
constructor (text = '') {
this.currentId = '-1'
this.currentText = text
this.fullText = text
this.parts = new Map()
this.read(text)
}
read (text) {
if ( ! text) {
// 空っぽなので全て初期値
this.parts.clear()
this.fullText = text
this.currentId = '-1'
this.currentText = text
return
}
let current_id = undefined, current_text = []
const parts = new Map()
for (const line of text.split('\n')) {
let matches
if (matches = line.match(prefix_part_reg)) {
parts.set(matches[1], line.slice(matches.index + matches[0].length))
} else if (matches = line.match(prefix_current_reg)) {
current_id = matches[1]
} else {
current_text.push(line)
}
}
if (current_id) {
parts.set(current_id, current_text.join('\u0001'))
} else if (current_text.length > 0 && parts.size > 0) {
// 新しい番号を割り振る
let max_id = -1
for (const key of parts.keys()) {
const temp_id = Number(key)
if ( ! isNaN(temp_id) && max_id < temp_id) {
max_id = temp_id
}
}
current_id = (max_id + 1).toString()
parts.set((max_id + 1).toString(), current_text.join('\u0001'))
} else {
current_id = '-1'
}
this.parts = parts
this.fullText = text
this.currentId = current_id
if (current_id === '-1') {
this.currentText = text
} else {
this.currentText = current_text.join('\n')
}
}
select (tag) {
const result = []
let text = ''
for (const key of [...this.parts.keys()].sort()) {
if (key === tag) {
text = this.parts.get(key).replace(/\u0001/g, '\n')
result.push(prefix_current + key + '$\n' + text)
} else {
result.push(prefix_part + key + '$' + this.parts.get(key))
}
}
this.currentId = tag
this.fullText = result.join('\n')
if (tag === '-1') {
this.currentText = this.fullText
} else {
this.currentText = text
}
}
updateCurrent (text) {
if (this.currentId === '-1') {
this.read(text)
return
}
const result = []
this.parts.set(this.currentId, text.replace(/\n/g, '\u0001'))
for (const key of [...this.parts.keys()].sort()) {
if (key === this.currentId) {
result.push(prefix_current + key + '$\n' + text)
} else {
result.push(prefix_part + key + '$' + this.parts.get(key))
}
}
this.fullText = result.join('\n')
this.currentText = text
}
new (text, newTag = '') {
let current_id = newTag
if (!newTag || ( ! isNaN(Number(newTag)) && Number(newTag) < 0)) {
// 新しい番号を割り振る
let max_id = -1
for (const key of this.parts.keys()) {
const temp_id = Number(key)
if ( ! isNaN(temp_id) && max_id < temp_id) {
max_id = temp_id
}
}
current_id = (max_id + 1).toString()
} else if (this.parts.has(current_id)) {
for (let i = 0; i < 100000; i++) {
if ( ! this.parts.has(newTag + i)) {
current_id = newTag + i
break
}
}
}
this.currentId = current_id
this.updateCurrent(text)
return current_id
}
deleteCurrent () {
if (this.currentId === '-1') {
return
}
const result = []
this.parts.delete(this.currentId)
this.currentId = '-1'
for (const key of [...this.parts.keys()].sort()) {
result.push(prefix_part + key + '$' + this.parts.get(key))
}
this.fullText = result.join('\n')
this.currentText = this.fullText
}
}
for (const target of targetList) {
const mod_target = target.element.cloneNode(true)
const tagged = new taggedText(localStorage[target.name])
mod_target.id = 'mod_' + target.name
target.element.style.display = 'none'
target.element.insertAdjacentElement('afterend', mod_target)
target.element.insertAdjacentHTML('beforebegin', `<div style="margin:5px;font-size:1rem;"><select id="mod_${target.name}_tab_select" style="padding:5px"><option value="-1">全体</option>${[...tagged.parts.keys()].sort().map(val => `<option value="${val}"${val === tagged.currentId ? 'selected' : ''}>${val.replace(/<[^>]*>/g, '')}</option>`).join('')}</select>&nbsp;
<button style="margin-left: 1.2rem;" id="mod_${target.name}_remove" title="現在のタグを削除">-</button>&nbsp;<button style="margin-left: 1.2rem;" id="mod_${target.name}_add" title="新規タグを追加">+</button><input type="text" id="mod_${target.name}_new_tag" style="margin-left: 1.2rem;padding:5px" placeholder="新規タグ...">
<span id="tooltips">
<span id="help_mod_${target.name}" data-text="テキストを任意のタグ名でグループ化して、プルダウンリストで有効にするテキストを切り替えられます。新規のタグを追加するにはテキストボックスにタグ名を入れて、「+」ボタンを押してください。タグ名を入力しない場合は連番を振ります。&lt;br&gt;` +
`「-」ボタンを押すと、現在のタグを削除します。&lt;br&gt;` +
`プルダウンリストの「全体」はコメントによりタグ分けされたテキストを全て表示します。この状態ではどのタグのテキストも無効になります。なお、実際にデータとして保存されるのはこのコメント付きテキストであるため、個々のタグ分けされたテキストの文字数上限は、本来のテキスト上限より少なくなります。文字数カウンタはコメント付きテキストの文字数を表示します。">
<img src="images/icon_popup.png" width="20" height="20" id="help_mod_${target.name}_icon" class="help_popup" style="margin-left: 10px; margin-top: -5px; vertical-align:middle;" aria-describedby="tooltip_mod_${target.name}" onclick="return false;"></span>
</span></div>`)
window.jQuery("#help_mod_" + target.name).on({
'mouseenter': function () {
const $ = window.jQuery
var text = $(this).attr('data-text');
$(this).append('<div class="tooltips_body">' + text + '</div>');
},
'mouseleave': function () {
window.jQuery(this).find(".tooltips_body").remove();
}
})
mod_target.value = tagged.currentText
const mod_target_tab_select = document.getElementById('mod_' + target.name + '_tab_select')
if ( ! mod_target_tab_select) {
return
}
mod_target_tab_select.addEventListener('change', function () {
tagged.select(mod_target_tab_select.value)
target.element.value = tagged.fullText
mod_target.value = tagged.currentText
target.element.dispatchEvent(new Event('input'))
window.CopyContent()
})
mod_target.addEventListener('input', function () {
tagged.updateCurrent(mod_target.value)
target.element.value = tagged.fullText
if (tagged.currentId === '-1') {
mod_target_tab_select.innerHTML = `<option value="-1">全体</option>${[...tagged.parts.keys()].sort().map(val => `<option value="${val}"${val === tagged.currentId ? 'selected' : ''}>${val.replace(/<[^>]*>/g, '')}</option>`).join('')}`
mod_target_tab_select.value = '-1'
}
target.element.dispatchEvent(new Event('input'))
window.CopyContent()
})
const mod_target_new_tag = document.getElementById('mod_' + target.name + '_new_tag')
const mod_target_remove = document.getElementById('mod_' + target.name + '_remove')
mod_target_remove.addEventListener('click', function () {
const current_id = tagged.currentId
tagged.deleteCurrent()
target.element.value = tagged.fullText
mod_target.value = tagged.currentText
if (current_id !== '-1' && window.confirm('現在のタグ「' + current_id + '」を削除します。\nよろしいですか?')) {
const node = mod_target_tab_select.querySelector('option[value="' + current_id + '"]')
if (node) {
mod_target_tab_select.removeChild(node)
}
mod_target_tab_select.value = '-1'
}
target.element.dispatchEvent(new Event('input'))
window.CopyContent()
})
const mod_target_add = document.getElementById('mod_' + target.name + '_add')
mod_target_add.addEventListener('click', function () {
let initText = ''
if (tagged.currentId === '-1') {
tagged.read(mod_target.value)
initText = tagged.currentText
mod_target_tab_select.innerHTML = `<option value="-1">全体</option>${[...tagged.parts.keys()].sort().map(val => `<option value="${val}"${val === tagged.currentId ? 'selected' : ''}>${val.replace(/<[^>]*>/g, '')}</option>`).join('')}`
mod_target_tab_select.value = '-1'
}
const current_id = tagged.new(initText, mod_target_new_tag.value)
target.element.value = tagged.fullText
mod_target.value = initText
mod_target_tab_select.insertAdjacentHTML('beforeend', `<option value="${current_id}">${current_id}</option>`)
mod_target_tab_select.value = current_id
target.element.dispatchEvent(new Event('input'))
window.CopyContent()
mod_target_new_tag.value = ''
})
}
// 表示の調整
if (targetList.length > 0) {
const VisualChange2 = function () {
const $ = window.jQuery
for (const target of targetList) {
$('#mod_' + target.name).css('font-size', Math.max(39,document.getElementById("vis_fontsize").value) / 39+'rem');
}
}
document.getElementById('vis_fontsize').addEventListener('input', VisualChange2);
document.getElementById('vis_fontkerning').addEventListener('input', VisualChange2);
document.getElementById('vis_fontleading').addEventListener('input', VisualChange2);
document.getElementById('gui_iconsize').addEventListener('input', VisualChange2);
const originalFontFamily = window.FontFamily
window.FontFamily = function (elem) {
originalFontFamily(elem)
VisualChange2()
}
const originalLoadVisConfigFromStorage = window.LoadVisConfigFromStorage
window.LoadVisConfigFromStorage = function (id, dontclose = false) {
originalLoadVisConfigFromStorage()
VisualChange2()
}
VisualChange2()
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment