hipda-ID笔记

来自地板带着爱,记录上网冲浪的美好瞬间

目前为 2021-12-08 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name hipda-ID笔记
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.3
  5. // @description 来自地板带着爱,记录上网冲浪的美好瞬间
  6. // @author 屋大维
  7. // @license MIT
  8. // @match https://www.hi-pda.com/forum/viewthread.php?tid*
  9. // @resource IMPORTED_CSS https://code.jquery.com/ui/1.13.0/themes/base/jquery-ui.css
  10. // @require https://code.jquery.com/jquery-3.4.1.min.js
  11. // @require https://code.jquery.com/ui/1.13.0/jquery-ui.js
  12. // @icon https://icons.iconarchive.com/icons/iconshock/real-vista-project-managment/64/task-notes-icon.png
  13. // @grant GM.setValue
  14. // @grant GM.getValue
  15. // @grant GM.deleteValue
  16. // @grant GM_getResourceText
  17. // @grant GM_addStyle
  18. // ==/UserScript==
  19.  
  20. (async function() {
  21. 'use strict';
  22. // CSS
  23. const my_css = GM_getResourceText("IMPORTED_CSS");
  24. GM_addStyle(my_css);
  25. 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}");
  26.  
  27. // Your code here...
  28. // helpers
  29. function htmlToElement(html) {
  30. var template = document.createElement('template');
  31. html = html.trim(); // Never return a text node of whitespace as the result
  32. template.innerHTML = html;
  33. return template.content.firstChild;
  34. }
  35.  
  36. // classes
  37. class HpPost {
  38. constructor() {
  39. }
  40.  
  41. getPostTid() {
  42. return location.href.match(/tid=(\d+)/) ? parseInt(location.href.match(/tid=(\d+)/)[1]) : -999;
  43. }
  44.  
  45. getUserUid() {
  46. return parseInt($("cite > a").attr("href").split("uid=")[1]);
  47. }
  48.  
  49. getHpThreads() {
  50. let postTid = this.getPostTid();
  51. let divs = $('#postlist > div').get();
  52. return divs.map(d => new Hpthread(postTid, d));
  53. }
  54.  
  55. addNoteManagementUI(_notebook) {
  56. var that = this;
  57. var button = htmlToElement(`
  58. <button id="noteButton_management">
  59. <span><img src="https://icons.iconarchive.com/icons/iconshock/real-vista-project-managment/32/task-notes-icon.png"></img></span>
  60. </button>
  61. `);
  62.  
  63. // create dialog
  64. let dialog = htmlToElement(`
  65. <div id="noteDialog_management" style="display: none;">
  66. <h3>hipda-ID笔记 v${GM_info.script.version}</h3>
  67. <p style="margin: 10px auto 10px auto;">来自地板带着爱</p>
  68. <p id="noteStat" style="margin: 10px auto 10px auto;"></p>
  69. <div>
  70. <button id="noteButton_import">导入</button>
  71. <button id="noteButton_export">导出</button>
  72. <button id="noteButton_reset">重置</button>
  73. </div>
  74. </div>
  75. `);
  76. $("body").append(dialog);
  77.  
  78. function updateNoteStat() {
  79. let note_stat = _notebook.getNotebookStat();
  80. $(`#noteStat`).text(`共${note_stat.note_number}条ID笔记,大小为${(note_stat.size_kb).toFixed(2)}KB`);
  81. }
  82.  
  83. $(document).ready( function () {
  84. $(document).on ("click", "#noteButton_import", function() {
  85. let r = confirm("确定要导入ID笔记吗?现有笔记将会被覆盖!");
  86. if (!r) {
  87. return;
  88. }
  89.  
  90. // prompt cannot handle large file, extend it in the future
  91. let data = prompt("请将 id笔记.json 中的文本复制粘贴入文本框:");
  92. if (data !== null) {
  93. // try to load
  94. try {
  95. let j = JSON.parse(data);
  96. _notebook.importNotebook(j);
  97. } catch(err) {
  98. alert("格式错误!" + err);
  99. return;
  100. }
  101. alert("导入成功!");
  102. }
  103. });
  104. $(document).on ("click", "#noteButton_export", function() {
  105. let r = confirm("确定要导出ID笔记吗?");
  106. if (!r) {
  107. return;
  108. }
  109. let a = document.createElement("a");
  110. a.href = "data:text," + _notebook.exportNotebook();
  111. a.download = "id笔记.json";
  112. a.click();
  113. });
  114. $(document).on ("click", "#noteButton_reset", function() {
  115. let r = confirm("确定要清空ID笔记吗?");
  116. if (!r) {
  117. return;
  118. }
  119. _notebook.resetNotebook();
  120. alert("ID笔记已经清空!");
  121. updateNoteStat();
  122. });
  123. $(document).on ("click", `#noteButton_management`, function () {
  124. console.log("open notebook management dialog");
  125. // update statistics
  126. updateNoteStat();
  127.  
  128. $(`#noteDialog_management`).dialog({
  129. title: "ID笔记:管理面板",
  130. height: 200,
  131. width: 300,
  132. closeOnEscape: true,
  133. });
  134. });
  135. });
  136.  
  137. // add UI
  138. let d = $("td.modaction").last();
  139. d.append(button);
  140.  
  141. }
  142.  
  143. }
  144.  
  145. class Hpthread {
  146. constructor(postTid, threadDiv) {
  147. this.postTid = postTid;
  148. this._thread_div = threadDiv;
  149. }
  150.  
  151. getThreadAuthorName() {
  152. return $(this._thread_div).find("div.postinfo > a").first().text();
  153. }
  154.  
  155. getThreadAuthorUid() {
  156. return parseInt($(this._thread_div).find("div.postinfo > a").first().attr("href").split("uid=")[1]);
  157. }
  158.  
  159. getThreadPid() {
  160. return parseInt($(this._thread_div).attr("id").split("_")[1]);
  161. }
  162.  
  163. getGotoUrl() {
  164. return `https://www.hi-pda.com/forum/redirect.php?goto=findpost&ptid=${this.postTid}&pid=${this.getThreadPid()}`;
  165. }
  166.  
  167. getThreadContent() {
  168. // get text without quotes
  169. let t = $(this._thread_div).find("td.t_msgfont").first().clone();
  170. t.find('.quote').replaceWith( "<p>【引用内容】</p>" );
  171. t.find('.t_attach').replaceWith( "<p>【附件】</p>" );
  172. t.find('img').remove();
  173. let text = t.text().replace(/\n+/g, "\n").trim();
  174. return text;
  175. }
  176.  
  177. getThreadBrief(n) {
  178. let content = this.getThreadContent();
  179. if (content.length <= n) {
  180. return content;
  181. }
  182. return content.slice(0, n) + "\n\n【以上为截取片段】" ;
  183. }
  184.  
  185. addNoteUI(_notebook) {
  186. let uid = this.getThreadAuthorUid();
  187. let index = $(this._thread_div).index();
  188. let userName = this.getThreadAuthorName();
  189.  
  190. var that = this;
  191. // create an UI element which contains data and hooks
  192. // button
  193. let button = htmlToElement(`
  194. <button id="noteButton_${index}" style="color:grey; margin-left:20px;">
  195. ID笔记
  196. </button>
  197. `);
  198. // note dialog
  199. let dialog = htmlToElement(`
  200. <div id="noteDialog_${index}" style="display: none;">
  201. <textarea rows="10" wrap="hard" placeholder="暂时没有笔记">
  202. </div>
  203. `);
  204. $("body").append(dialog);
  205.  
  206. // add event to button
  207. $(document).ready( function () {
  208. $(document).on ("click", `#noteButton_${index}`, function () {
  209. console.log("open note for", userName);
  210. // freshly fetched from DB
  211. $(`#noteDialog_${index}`).find('textarea').first().val(_notebook.get(uid));
  212. $(`#noteDialog_${index}`).dialog({
  213. title: `ID笔记:${userName}`,
  214. dialogClass: "no-close",
  215. closeText: "hide",
  216. closeOnEscape: true,
  217. height: 350,
  218. width: 600,
  219. buttons: [
  220. {
  221. text: "插入当前楼层",
  222. click: function() {
  223. let txt = $(`#noteDialog_${index}`).find('textarea').first();
  224. var caretPos = txt[0].selectionStart;
  225. var textAreaTxt = txt.val();
  226. var txtToAdd = `====\n引用: ${that.getGotoUrl()}\n${that.getThreadAuthorName()} 说:${that.getThreadBrief(200)}\n====`;
  227. txt.val(textAreaTxt.substring(0, caretPos) + txtToAdd + textAreaTxt.substring(caretPos) );
  228. }
  229. },
  230. {
  231. text: "确认",
  232. click: function() {
  233. // save the new note before close
  234. let newNote = $(`#noteDialog_${index}`).find('textarea').first().val();
  235. _notebook.put(uid, userName, newNote);
  236. $(this).dialog( "close" );
  237. }
  238. },
  239. {
  240. text: "取消",
  241. click: function() {
  242. // close without saving
  243. $(this).dialog( "close" );
  244. }
  245. }
  246. ]
  247. });
  248. });
  249. });
  250.  
  251. // add UI
  252. let d = $(this._thread_div).find("td[rowspan='2'].postauthor").first();
  253. d.append(button);
  254. }
  255.  
  256. }
  257.  
  258. class Notebook {
  259. constructor(user_uid) {
  260. // initialization
  261. this._name = "hipda-notebook";
  262. this._user_uid = user_uid;
  263. this._notebook = {};
  264. return (async () => {
  265. this.loadFromLocalStorage();
  266. return this;
  267. })();
  268. }
  269.  
  270. async loadFromLocalStorage() {
  271. console.log("load ID Notebook from Local Storage");
  272. let data = await GM.getValue(this._name, null);
  273. if (data !== null) {
  274. this._notebook = JSON.parse(data);
  275. }
  276. }
  277.  
  278. async saveToLocalStorage() {
  279. console.log("save ID Notebook to Local Storage");
  280. await GM.setValue(this._name, JSON.stringify(this._notebook));
  281. }
  282.  
  283. put(uid, userName, note) {
  284. // we need userName here, so user can analyze notes even after export
  285. this._notebook[uid] = {uid, userName, note};
  286. this.saveToLocalStorage();
  287. }
  288.  
  289. get(uid) {
  290. if (uid in this._notebook) {
  291. return this._notebook[uid].note;
  292. }
  293. return "";
  294. }
  295.  
  296. delete(uid) {
  297. if (uid in this._notebook) {
  298. delete this._notebook[uid];
  299. this.saveToLocalStorage();
  300. }
  301. }
  302.  
  303. exportNotebook() {
  304. // can add meta data here
  305. let output = {
  306. notebook: this._notebook,
  307. version: GM_info.script.version,
  308. timestamp: + new Date()
  309. };
  310. return JSON.stringify(output);
  311. }
  312.  
  313. importNotebook(input) {
  314. let attrs = ['notebook', 'version', 'timestamp'];
  315. for (let i=0; i<attrs.length; i++) {
  316. if (!input.hasOwnProperty(attrs[i])) {
  317. throw(`bad format: ${attrs[i]} does not exist`);
  318. }
  319. }
  320. this._notebook = {...input.notebook};
  321. this.saveToLocalStorage();
  322. }
  323.  
  324. resetNotebook() {
  325. this._notebook = {};
  326. this.saveToLocalStorage();
  327. }
  328.  
  329. getNotebookStat() {
  330. return {
  331. 'note_number': Object.keys(this._notebook).length,
  332. 'size_kb': (new TextEncoder().encode(this.exportNotebook())).length / 1024
  333. };
  334. }
  335. }
  336.  
  337.  
  338.  
  339.  
  340. // get a post object
  341.  
  342. var THIS_POST = new HpPost();
  343.  
  344. // get tid and uid; uid for future extension
  345. var tid = THIS_POST.getPostTid();
  346. var uid = THIS_POST.getUserUid();
  347.  
  348.  
  349. var notebook = await new Notebook(uid);
  350.  
  351. // render UI below
  352. // ID notes
  353. var hp_threads = THIS_POST.getHpThreads();
  354. for (let i=0; i<hp_threads.length; i++) {
  355. let hp_thread = hp_threads[i];
  356. hp_thread.addNoteUI(notebook);
  357. }
  358. // management panel
  359. THIS_POST.addNoteManagementUI(notebook);
  360.  
  361.  
  362. })();