// ==UserScript==
// @name hipda-ID笔记
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @description 来自地板带着爱,记录上网冲浪的美好瞬间
// @author 屋大维
// @license MIT
// @match https://www.hi-pda.com/forum/*
// @match https://www.4d4y.com/forum/*
// @resource IMPORTED_CSS https://code.jquery.com/ui/1.13.0/themes/base/jquery-ui.css
// @require https://code.jquery.com/jquery-3.4.1.min.js
// @require https://code.jquery.com/ui/1.13.0/jquery-ui.js
// @icon https://icons.iconarchive.com/icons/iconshock/real-vista-project-managment/64/task-notes-icon.png
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @grant GM_getResourceText
// @grant GM_addStyle
// @grant GM.xmlHttpRequest
// ==/UserScript==
(function() {
'use strict';
// CONST
const BROWSER_KEY = 'alt+I';
const MANAGEMENT_KEY = "alt+U";
// CSS
const my_css = GM_getResourceText("IMPORTED_CSS");
GM_addStyle(my_css);
GM_addStyle(".no-close .ui-dialog-titlebar-close{display:none} textarea{height:100%;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} .card{box-shadow:0 4px 8px 0 rgba(0,0,0,.2);transition:.3s;width:100%;overflow-y: scroll;}.card:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,.2)}.container{padding:2px 16px}");
GM_addStyle(".flex-container{display:flex;flex-wrap: wrap;}.flex-container>div{background-color:#f1f1f1;width:500px;max-height:500px;margin:15px; padding:5px;text-align:left;}");
GM_addStyle(`.lds-roller{display:inline-block;position:fixed;top:50vh;left:50vh;width:80px;height:80px}.lds-roller div{animation:1.2s cubic-bezier(.5,0,.5,1) infinite lds-roller;transform-origin:40px 40px}.lds-roller div:after{content:" ";display:block;position:absolute;width:7px;height:7px;border-radius:50%;background:#bfa1cf;margin:-4px 0 0 -4px}.lds-roller div:first-child{animation-delay:-36ms}.lds-roller div:first-child:after{top:63px;left:63px}.lds-roller div:nth-child(2){animation-delay:-72ms}.lds-roller div:nth-child(2):after{top:68px;left:56px}.lds-roller div:nth-child(3){animation-delay:-108ms}.lds-roller div:nth-child(3):after{top:71px;left:48px}.lds-roller div:nth-child(4){animation-delay:-144ms}.lds-roller div:nth-child(4):after{top:72px;left:40px}.lds-roller div:nth-child(5){animation-delay:-.18s}.lds-roller div:nth-child(5):after{top:71px;left:32px}.lds-roller div:nth-child(6){animation-delay:-216ms}.lds-roller div:nth-child(6):after{top:68px;left:24px}.lds-roller div:nth-child(7){animation-delay:-252ms}.lds-roller div:nth-child(7):after{top:63px;left:17px}.lds-roller div:nth-child(8){animation-delay:-288ms}.lds-roller div:nth-child(8):after{top:56px;left:12px}@keyframes lds-roller{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}`);
// Your code here...
// helpers
function showLoader() {
let loader = $(`<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>`);
$("body").append(loader);
}
function hideLoader() {
$(".lds-roller").remove();
}
function getKeys(e) { // keycode 转换
var codetable = {
'96': 'Numpad 0',
'97': 'Numpad 1',
'98': 'Numpad 2',
'99': 'Numpad 3',
'100': 'Numpad 4',
'101': 'Numpad 5',
'102': 'Numpad 6',
'103': 'Numpad 7',
'104': 'Numpad 8',
'105': 'Numpad 9',
'106': 'Numpad *',
'107': 'Numpad +',
'108': 'Numpad Enter',
'109': 'Numpad -',
'110': 'Numpad .',
'111': 'Numpad /',
'112': 'F1',
'113': 'F2',
'114': 'F3',
'115': 'F4',
'116': 'F5',
'117': 'F6',
'118': 'F7',
'119': 'F8',
'120': 'F9',
'121': 'F10',
'122': 'F11',
'123': 'F12',
'8': 'BackSpace',
'9': 'Tab',
'12': 'Clear',
'13': 'Enter',
'16': 'Shift',
'17': 'Ctrl',
'18': 'Alt',
'20': 'Cape Lock',
'27': 'Esc',
'32': 'Spacebar',
'33': 'Page Up',
'34': 'Page Down',
'35': 'End',
'36': 'Home',
'37': '←',
'38': '↑',
'39': '→',
'40': '↓',
'45': 'Insert',
'46': 'Delete',
'144': 'Num Lock',
'186': ';:',
'187': '=+',
'188': ',<',
'189': '-_',
'190': '.>',
'191': '/?',
'192': '`~',
'219': '[{',
'220': '\|',
'221': ']}',
'222': '"'
};
var Keys = '';
e.shiftKey && (e.keyCode != 16) && (Keys += 'shift+');
e.ctrlKey && (e.keyCode != 17) && (Keys += 'ctrl+');
e.altKey && (e.keyCode != 18) && (Keys += 'alt+');
return Keys + (codetable[e.keyCode] || String.fromCharCode(e.keyCode) || '');
};
function addHotKey(codes, func) { // 监视并执行快捷键对应的函数
document.addEventListener('keydown', function(e) {
if ((e.target.tagName != 'INPUT') && (e.target.tagName != 'TEXTAREA') && getKeys(e) == codes) {
func();
e.preventDefault();
e.stopPropagation();
}
}, false);
};
function htmlToElement(html) {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
function getEpoch(date_str, time_str) {
let [y, m, d] = date_str.split("-").map(x => parseInt(x));
let [H, M] = time_str.split(":").map(x => parseInt(x));
return new Date(y, m - 1, d, H, M, 0).getTime() / 1000;
}
// classes
class HpThread {
constructor() {}
getThreadTid() {
return location.href.match(/tid=(\d+)/) ? parseInt(location.href.match(/tid=(\d+)/)[1]) : -999;
}
getUserUid() {
return parseInt($("cite > a").attr("href").split("uid=")[1]);
}
getThreadTitle() {
let l = $('#nav').text().split(" » ");
return l[l.length - 1];
}
getHpPosts() {
let threadTid = this.getThreadTid();
let threadTitle = this.getThreadTitle();
let divs = $('#postlist > div').get();
return divs.map(d => new HpPost(threadTid, threadTitle, d));
}
addNoteBrowserUI(_notebook) {
$('#menu>ul').append($(`<li class="menu_2"><a href="javascript:void(0)" hidefocus="true" id="noteButton_browser">搜索笔记</a></li>`));
var that = this;
// create dialog
let dialog = htmlToElement(`
<div id="noteDialog_browser" style="display: none;">
<div id="noteDialog_browser_search_bar" style="width: 80%; margin: 20px auto 20px auto;">
<select style="display: inline-block;" name="searchMethod" id="noteDialog_browser_search_method">
<option value="content">笔记内容</option>
<option value="userName">用户名</option>
</select>
<input type="text" autofocus="true" style="display: inline-block; width: 300px;" id="noteDialog_browser_search_input">
</div>
<div id="noteDialog_browser_note_list" style="width: 95%; margin: 10px auto 10px auto;" class="flex-container">
</div>
</div>
`);
$("body").append(dialog);
function updateNoteList() {
$('#noteDialog_browser_note_list').empty(); // remove all notes from the list
var notes;
var searchMethod = $('#noteDialog_browser_search_method').val();
var searchInput = $('#noteDialog_browser_search_input').val();
if (searchMethod === "userName") {
notes = _notebook.getNotesByUsername(searchInput);
} else if (searchMethod === "content") {
notes = _notebook.getNotesByKeyword(searchInput);
} else {
return;
}
console.log(notes.length)
for (let i = 0; i < notes.length; i++) {
let element = noteToHtmlElement(notes[i]);
$('#noteDialog_browser_note_list').append(element);
}
}
function noteToHtmlElement(note) {
var searchMethod = $('#noteDialog_browser_search_method').val();
var searchInput = $('#noteDialog_browser_search_input').val();
var userName = note.userName;
var uid = note.uid;
var content = note.note;
if (searchMethod === 'userName') {
userName = userName.replaceAll(searchInput, '<mark class="highlight">$&</mark>');
}
if (searchMethod === 'content') {
content = content.replaceAll(searchInput, '<mark class="highlight">$&</mark>');
}
// highlight all URLs
var expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi;
var regex = new RegExp(expression);
content = content.replace(regex, '<a style="color:blue;" href="$&" target="_blank">$&</a>')
var html = `
<div class="card">
<div style="font-size: 2em; float: left; margin: 10px;">${userName}</div>
<div style="float: right;">
<button class="noteEditButton">编辑</button>
<button class="noteDeleteButton" style="margin-right: 2px;">删除</button>
</div>
<div class="container" style="word-break: break-all; white-space: pre-wrap;">
${content}
</div>
</div>
`;
var element = $(html);
// delete
element.find("button.noteDeleteButton").click(function() {
let r = confirm(`确定要删除 ${note.userName} 的ID笔记吗?`);
if (!r) {
return;
}
_notebook.delete(uid);
updateNoteList();
});
// edit
element.find("button.noteEditButton").click(function() {
// note dialog (this will be different from the one opened in posts)
let dialog = htmlToElement(`
<div id="noteDialog_${uid}" style="display: none;">
<textarea rows="10" wrap="hard" placeholder="暂时没有笔记">
</div>
`);
$("body").append(dialog);
// bind event listener
console.log("open note for", userName);
// freshly fetched from DB
$(`#noteDialog_${uid}`).find('textarea').first().val(_notebook.get(uid));
$(`#noteDialog_${uid}`).dialog({
title: `ID笔记:${userName}`,
dialogClass: "no-close",
closeText: "hide",
closeOnEscape: true,
height: Math.max(parseInt($(window).height() * 0.4), 350),
width: Math.max(parseInt($(window).width() * 0.4), 600),
buttons: [{
text: "确认",
click: function() {
// save the new note before close
let newNote = $(`#noteDialog_${uid}`).find('textarea').first().val();
if (newNote.length === 0) {
_notebook.delete(uid);
} else {
_notebook.put(uid, userName, newNote);
}
$(this).dialog("close");
// update the Note List
updateNoteList();
}
},
{
text: "取消",
click: function() {
// close without saving
$(this).dialog("close");
}
}
]
});
});
return element;
}
function openBrowser() {
$('#menu>ul>li').first().removeClass("current");
$('#menu>ul>li').first().addClass("menu_2");
$('#noteButton_browser').parent().removeClass("menu_2");
$('#noteButton_browser').parent().addClass("current");
console.log("open notebook browser dialog");
$(`#noteDialog_browser`).dialog({
title: "ID笔记:浏览器",
modal: true,
height: parseInt($(window).height() * 0.8),
width: parseInt($(window).width() * 0.8),
closeOnEscape: true,
open: function(event, ui) {
$('.ui-widget-overlay').css("background-color", "black");
$('.ui-widget-overlay').css("opacity", "0.6");
},
close: function(event, ui) {
$('#menu>ul>li').first().removeClass("menu_2");
$('#menu>ul>li').first().addClass("current");
$('#noteButton_browser').parent().removeClass("current");
$('#noteButton_browser').parent().addClass("menu_2");
}
});
}
$(document).ready(function() {
$('#noteDialog_browser_search_input').on("input", () => {
updateNoteList();
});
$('#noteDialog_browser_search_method').change(() => {
updateNoteList();
});
$(document).on("click", `#noteButton_browser`, function() {
openBrowser();
});
// HOTKEY
addHotKey(BROWSER_KEY, openBrowser);
});
}
addNoteManagementUI(_notebook) {
var that = this;
var button = htmlToElement(`
<button id="noteButton_management">
<span><img src="https://icons.iconarchive.com/icons/iconshock/real-vista-project-managment/32/task-notes-icon.png"></img></span>
</button>
`);
// create dialog
let dialog = htmlToElement(`
<div id="noteDialog_management" style="display: none;">
<h3>hipda-ID笔记 v${GM_info.script.version}</h3>
<p style="margin: 10px auto 10px auto;">来自地板带着爱</p>
<p id="noteStat" style="margin: 10px auto 10px auto;"></p>
<div>
<button id="noteButton_import">导入</button>
<button id="noteButton_export">导出</button>
<button id="noteButton_reset">重置</button>
<button id="noteButton_migrate">4d4y</button>
<button id="noteButton_server">服务器</button>
<input type="hidden" autofocus="true" />
</div>
</div>
`);
$("body").append(dialog);
function updateNoteStat() {
let note_stat = _notebook.getNotebookStat();
let synced = _notebook._synced;
$(`#noteStat`).text(`共${note_stat.note_number}条ID笔记,大小为${(note_stat.size_kb).toFixed(2)}KB${synced ? " (已同步)" : ""}`);
}
function openManagement() {
console.log("open notebook management dialog");
// update statistics
updateNoteStat();
$(`#noteDialog_management`).dialog({
title: "ID笔记:管理面板",
height: 200,
width: 300,
closeOnEscape: true,
});
}
$(document).ready(function() {
$(document).on("click", "#noteButton_server", async function() {
let apiKey = await _notebook.getApiKey();
let data = prompt("请将 API链接 输入文本框:", apiKey ? apiKey : "");
if (data !== null) {
// try to load
try {
_notebook.setApiKey(data);
} catch (err) {
alert("格式错误!" + err);
return;
}
alert("导入成功!");
}
});
$(document).on("click", "#noteButton_migrate", function() {
let r = confirm("确定要从hi-pda迁移到4d4y吗?");
if (!r) {
return;
}
_notebook.migrate();
alert("迁移成功!");
});
$(document).on("click", "#noteButton_import", function() {
let r = confirm("确定要导入ID笔记吗?现有笔记将会被覆盖!");
if (!r) {
return;
}
// prompt cannot handle large file, extend it in the future
let data = prompt("请将 id笔记.json 中的文本复制粘贴入文本框:");
if (data !== null) {
// try to load
try {
let j = JSON.parse(data);
_notebook.importNotebook(j);
} catch (err) {
alert("格式错误!" + err);
return;
}
alert("导入成功!");
updateNoteStat();
}
});
$(document).on("click", "#noteButton_export", async function() {
let r = confirm("确定要导出ID笔记吗?");
if (!r) {
return;
}
let a = document.createElement("a");
let data = await _notebook.exportNotebook();
a.href = "data:text," + encodeURIComponent(data);
a.download = "id笔记.json";
a.click();
});
$(document).on("click", "#noteButton_reset", function() {
let r = confirm("确定要清空ID笔记吗?");
if (!r) {
return;
}
_notebook.resetNotebook();
alert("ID笔记已经清空!");
updateNoteStat();
});
$(document).on("click", `#noteButton_management`, function() {
openManagement();
});
// HOTKEY
addHotKey(MANAGEMENT_KEY, openManagement);
});
// add UI
let d = $("td.modaction").last();
d.append(button);
}
}
class HpPost {
constructor(threadTid, threadTitle, postDiv) {
this.threadTid = threadTid;
this.threadTitle = threadTitle;
this._post_div = postDiv;
}
getPostAuthorName() {
return $(this._post_div).find("div.postinfo > a").first().text();
}
getPostAuthorUid() {
return parseInt($(this._post_div).find("div.postinfo > a").first().attr("href").split("uid=")[1]);
}
getPostPid() {
return parseInt($(this._post_div).attr("id").split("_")[1]);
}
getGotoUrl() {
// return `https://www.hi-pda.com/forum/redirect.php?goto=findpost&ptid=${this.threadTid}&pid=${this.getPostPid()}`;
return `https://www.4d4y.com/forum/redirect.php?goto=findpost&ptid=${this.threadTid}&pid=${this.getPostPid()}`;
}
getPostContent() {
// get text without quotes
let t = $(this._post_div).find("td.t_msgfont").first().clone();
t.find('.quote').replaceWith("<p>【引用内容】</p>");
t.find('.t_attach').replaceWith("<p>【附件】</p>");
t.find('img').remove();
let text = t.text().replace(/\n+/g, "\n").trim();
return text;
}
getPostBrief(n) {
let content = this.getPostContent();
if (content.length <= n) {
return content;
}
return content.slice(0, n) + "\n\n【以上为截取片段】";
}
getOriginalTimestamp(use_string = false) {
let dt = $(this._post_div).find("div.authorinfo > em").text().trim().split(" ").slice(1, 3);
if (use_string) {
return dt.join(" ");
}
return getEpoch(dt[0], dt[1]);
}
getLastTimestamp(use_string = false) {
let ele = $(this._post_div).find("i.pstatus").get();
if (ele.length !== 0) {
let dt = $(this._post_div).find("i.pstatus").text().trim().split(" ").slice(3, 5);
if (use_string) {
return dt.join(" ");
}
return getEpoch(dt[0], dt[1]);
}
return null;
}
getTimestamp(use_string = false) {
// get last edit time
let lastTimestamp = this.getLastTimestamp(use_string);
return lastTimestamp ? lastTimestamp : this.getOriginalTimestamp(use_string);
}
addNoteUI(_notebook) {
let uid = this.getPostAuthorUid();
let index = $(this._post_div).index();
let userName = this.getPostAuthorName();
var that = this;
// create an UI element which contains data and hooks
// button
let button = htmlToElement(`
<button id="noteButton_${index}" style="color:grey; margin-left:20px;">
ID笔记
</button>
`);
// note dialog
let dialog = htmlToElement(`
<div id="noteDialog_${index}" style="display: none;">
<textarea rows="10" wrap="hard" placeholder="暂时没有笔记">
</div>
`);
$("body").append(dialog);
// add event to button
$(document).ready(function() {
$(document).on("click", `#noteButton_${index}`, async function() {
// try to sync DB
if (!_notebook._synced) {
try {
await _notebook.sync_server(uid);
} catch (err) {
console.log(err);
}
}
console.log("open note for", userName);
// freshly fetched from DB
$(`#noteDialog_${index}`).find('textarea').first().val(_notebook.get(uid));
$(`#noteDialog_${index}`).dialog({
title: `ID笔记:${userName}`,
dialogClass: "no-close",
closeText: "hide",
closeOnEscape: true,
height: Math.max(parseInt($(window).height() * 0.4), 350),
width: Math.max(parseInt($(window).width() * 0.4), 600),
buttons: [{
text: "插入当前楼层",
click: function() {
let txt = $(`#noteDialog_${index}`).find('textarea').first();
var caretPos = txt[0].selectionStart;
var textAreaTxt = txt.val();
var txtToAdd = `\n====\n引用: ${that.getGotoUrl()}\n【${that.getTimestamp(true)}】\n${that.getPostAuthorName()} 在《${that.threadTitle}》中说:\n ${that.getPostBrief(200)}\n====\n`;
txt.val(textAreaTxt.substring(0, caretPos) + txtToAdd + textAreaTxt.substring(caretPos));
}
},
{
text: "确认",
click: function() {
// save the new note before close
let newNote = $(`#noteDialog_${index}`).find('textarea').first().val();
if (newNote.length === 0) {
_notebook.delete(uid);
} else {
_notebook.put(uid, userName, newNote);
}
$(this).dialog("close");
}
},
{
text: "取消",
click: function() {
// close without saving
$(this).dialog("close");
}
}
]
});
});
});
// add UI
let d = $(this._post_div).find("td[rowspan='2'].postauthor").first();
d.append(button);
}
}
class NotebookClient {
// used to connect to the server
constructor(UID, apiKey) {
this.UID = String(UID);
this.apiKey = apiKey;
}
get() {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url: `${this.apiKey}`,
onload: function(response) {
let data = response.responseText;
if (response.status === 200) {
resolve(data);
} else {
reject(data);
}
}
});
});
}
put(payload) {
return new Promise((resolve, reject) => {
let d = {
note: payload
};
GM.xmlHttpRequest({
method: "POST",
url: `${this.apiKey}`,
data: JSON.stringify(d),
headers: {
"Content-Type": "application/json"
},
onload: function(response) {
let data = response.responseText;
if (response.status === 200) {
resolve(data);
} else {
reject(data);
}
}
});
});
}
}
class Notebook {
// notebook data structure:
// this._notebook[uid] = {uid, userName, note};
constructor(UID) {
// initialization
this._name = "hipda-notebook";
this._keyname = "hipda-notebook-key";
this._timestamp_name = "hipda-notebook-timestamp";
this._uid = UID;
this._key = null;
this._client = null;
this._notebook = {};
this._synced = false;
return (async () => {
this.loadFromLocalStorage();
this._key = await this.getApiKey();
return this;
})();
}
async sync_server() {
showLoader();
await this._sync_server();
hideLoader();
}
async _sync_server() {
if (GM.xmlHttpRequest === undefined) {
console.log("浏览器不支持连接服务器");
return;
}
if (this._key === null) {
return;
}
let client = new NotebookClient(this._uid, this._key);
let data;
try {
data = await client.get();
} catch (err) {
console.log(err);
this._synced = true;
}
function isServerDataValid(data) {
if (data === undefined || data === '') {
return false;
}
try {
let serverVal = JSON.parse(JSON.parse(data).note)
if (serverVal.timestamp === undefined) {
return false
}
} catch {
return false;
}
return true;
}
if (!isServerDataValid(data)) {
// initialize in server
let payload = await this.exportNotebook();
let data = await client.put(payload);
console.log("initialize record in server");
console.log("server:", data);
} else {
// check timestamp
let serverVal = JSON.parse(JSON.parse(data).note)
let serverTimestamp = serverVal.timestamp;
let localTimestamp = await this.getTimestamp();
if (localTimestamp === null || localTimestamp < serverTimestamp) {
// import from server
this.importNotebook(serverVal);
console.log("import record from server");
} else if (localTimestamp > serverTimestamp) {
// push to server
let payload = await this.exportNotebook();
let data = await client.put(payload);
console.log("update record in server");
console.log("server:", data);
} else {
console.log("already up-to-date");
}
}
this._synced = true;
}
async getTimestamp() {
let data = await GM.getValue(this._timestamp_name, null);
return data;
}
async setTimestamp() {
await GM.setValue(this._timestamp_name, +new Date());
}
async getApiKey() {
console.log("load ID Notebook API key from Local Storage");
let data = await GM.getValue(this._keyname, null);
return data;
}
async setApiKey(apiKey) {
console.log("save ID Notebook API key to Local Storage");
if (apiKey === "") {
await GM.deleteValue(this._keyname);
this._key = null;
} else {
await GM.setValue(this._keyname, apiKey);
this._key = apiKey;
}
}
async loadFromLocalStorage() {
console.log("load ID Notebook from Local Storage");
let data = await GM.getValue(this._name, null);
if (data !== null) {
this._notebook = JSON.parse(data);
}
}
async saveToLocalStorage() {
console.log("save ID Notebook to Local Storage");
await GM.setValue(this._name, JSON.stringify(this._notebook));
await this.setTimestamp();
await this.sync_server();
}
put(uid, userName, note) {
// we need userName here, so user can analyze notes even after export
this._notebook[uid] = {
uid,
userName,
note
};
this.saveToLocalStorage();
}
get(uid) {
if (uid in this._notebook) {
return this._notebook[uid].note;
}
return "";
}
delete(uid) {
if (uid in this._notebook) {
delete this._notebook[uid];
this.saveToLocalStorage();
}
}
getNotesByUsername(userName) {
if (userName.length === 0) {
return [];
}
function compareFn(a, b) {
if (a.userName < b.userName) {
return -1;
}
if (a.userName > b.userName) {
return 1;
}
return 0;
}
return Object.values(this._notebook).filter(x => x.userName.toLocaleLowerCase().indexOf(userName.toLocaleLowerCase()) !== -1).sort(compareFn);
}
getNotesByKeyword(keyword) {
if (keyword.length === 0) {
return [];
}
function compareFn(a, b) {
if (a.note < b.userName) {
return -1;
}
if (a.userName > b.userName) {
return 1;
}
return 0;
}
return Object.values(this._notebook).filter(x => x.note.toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) !== -1).sort(compareFn);
}
async exportNotebook() {
// can add meta data here
let timestamp = await this.getTimestamp()
let output = {
notebook: this._notebook,
version: GM_info.script.version,
timestamp: timestamp
};
return JSON.stringify(output);
}
importNotebook(input) {
let attrs = ['notebook', 'version', 'timestamp'];
for (let i = 0; i < attrs.length; i++) {
if (!input.hasOwnProperty(attrs[i])) {
throw (`bad format: ${attrs[i]} does not exist`);
}
}
this._notebook = {
...input.notebook
};
this.saveToLocalStorage();
}
resetNotebook() {
this._notebook = {};
this.saveToLocalStorage();
}
getNotebookStat() {
return {
'note_number': Object.keys(this._notebook).length,
'size_kb': (new TextEncoder().encode(this.exportNotebook())).length / 1024
};
}
migrate() {
// update all hi-pda urls to 4d4y urls
Object.keys(this._notebook).forEach(uid => {
let oldVal = this._notebook[uid].note;
let newVal = oldVal.replace('www.hi-pda.com/forum/', 'www.4d4y.com/forum/');
this._notebook[uid].note = newVal;
});
}
}
async function main() {
// get a thread object
var THIS_THREAD = new HpThread();
var notebook = await new Notebook(THIS_THREAD.getUserUid());
// notebook browser
THIS_THREAD.addNoteBrowserUI(notebook);
// management panel
THIS_THREAD.addNoteManagementUI(notebook);
// render UI below
// ID notes
var hp_posts = THIS_THREAD.getHpPosts();
for (let i = 0; i < hp_posts.length; i++) {
let hp_post = hp_posts[i];
try {
hp_post.addNoteUI(notebook);
} catch (e) {
// deleted post, simply pass it
console.log("unable to parse the post, pass");
}
}
}
main();
})();