Last active
January 1, 2023 05:58
-
-
Save whiteball/20013e480b3cba97f829fcb9b1c5e458 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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> | |
<button style="margin-left: 1.2rem;" id="mod_${target.name}_remove" title="現在のタグを削除">-</button> <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="テキストを任意のタグ名でグループ化して、プルダウンリストで有効にするテキストを切り替えられます。新規のタグを追加するにはテキストボックスにタグ名を入れて、「+」ボタンを押してください。タグ名を入力しない場合は連番を振ります。<br>` + | |
`「-」ボタンを押すと、現在のタグを削除します。<br>` + | |
`プルダウンリストの「全体」はコメントによりタグ分けされたテキストを全て表示します。この状態ではどのタグのテキストも無効になります。なお、実際にデータとして保存されるのはこのコメント付きテキストであるため、個々のタグ分けされたテキストの文字数上限は、本来のテキスト上限より少なくなります。文字数カウンタはコメント付きテキストの文字数を表示します。"> | |
<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