Greasyfork - 为脚本添加备注(别名/标签)

为脚本添加备注(别名/标签)功能,以帮助识别和搜索,并支持 WebDAV 同步功能

目前为 2023-04-12 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Greasyfork - Add notes to the script
  3. // @name:zh-CN Greasyfork - 为脚本添加备注(别名/标签)
  4. // @name:zh-TW Greasyfork - 為指令碼新增備註(別名/標籤)
  5. // @namespace https://greasyfork.org/zh-CN/users/193133-pana
  6. // @homepage https://greasyfork.org/zh-CN/users/193133-pana
  7. // @icon data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBhcmlhLWxhYmVsbGVkYnk9Im5ld0ljb25UaXRsZSIgc3Ryb2tlPSJyZ2JhKDI5LDE2MSwyNDIsMS4wMCkiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgZmlsbD0ibm9uZSIgY29sb3I9InJnYmEoMjksMTYxLDI0MiwxLjAwKSI+IDx0aXRsZSBpZD0ibmV3SWNvblRpdGxlIj5OZXc8L3RpdGxlPiA8cGF0aCBkPSJNMTkgMTRWMjJIMi45OTk5N1Y0SDEzIi8+IDxwYXRoIGQ9Ik0xNy40NjA4IDQuMDM5MjFDMTguMjQxOCAzLjI1ODE3IDE5LjUwODIgMy4yNTgxNiAyMC4yODkyIDQuMDM5MjFMMjAuOTYwOCA0LjcxMDc5QzIxLjc0MTggNS40OTE4NCAyMS43NDE4IDYuNzU4MTcgMjAuOTYwOCA3LjUzOTIxTDExLjU4NTggMTYuOTE0MkMxMS4yMTA3IDE3LjI4OTMgMTAuNzAyIDE3LjUgMTAuMTcxNiAxNy41TDcuNSAxNy41TDcuNSAxNC44Mjg0QzcuNSAxNC4yOTggNy43MTA3MSAxMy43ODkzIDguMDg1NzkgMTMuNDE0MkwxNy40NjA4IDQuMDM5MjFaIi8+IDxwYXRoIGQ9Ik0xNi4yNSA1LjI1TDE5Ljc1IDguNzUiLz4gPC9zdmc+
  8. // @version 3.1.6
  9. // @description Add notes (aliases/tags) for scripts to help identify and search, and support WebDAV sync
  10. // @description:zh-CN 为脚本添加备注(别名/标签)功能,以帮助识别和搜索,并支持 WebDAV 同步功能
  11. // @description:zh-TW 為指令碼新增備註(別名/標籤)功能,以幫助識別和搜尋,並支援 WebDAV 同步功能
  12. // @author pana
  13. // @license GNU General Public License v3.0 or later
  14. // @compatible chrome
  15. // @compatible firefox
  16. // @match *://*.greasyfork.org/*
  17. // @match *://*.sleazyfork.org/*
  18. // @require https://gcore.jsdelivr.net/gh/LightAPIs/greasy-fork-library@2c441882e206c11de36f3042d5ad7eaa987efe46/Note_Obj.js
  19. // @connect *
  20. // @noframes
  21. // @grant GM_info
  22. // @grant GM_getValue
  23. // @grant GM_setValue
  24. // @grant GM_deleteValue
  25. // @grant GM_listValues
  26. // @grant GM_openInTab
  27. // @grant GM_addStyle
  28. // @grant GM_xmlhttpRequest
  29. // @grant GM_registerMenuCommand
  30. // @grant GM_unregisterMenuCommand
  31. // @grant GM_addValueChangeListener
  32. // @grant GM_removeValueChangeListener
  33. // ==/UserScript==
  34.  
  35. (function () {
  36. 'use strict';
  37. const UPDATED = '2023-04-12';
  38. const GF_ICON = {
  39. NOTE_BLACK: 'url(data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBhcmlhLWxhYmVsbGVkYnk9Im5ld0ljb25UaXRsZSIgc3Ryb2tlPSJyZ2IoMzgsIDM4LCAzOCkiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgZmlsbD0ibm9uZSIgY29sb3I9InJnYigzOCwgMzgsIDM4KSI+IDx0aXRsZSBpZD0ibmV3SWNvblRpdGxlIj5OZXc8L3RpdGxlPiA8cGF0aCBkPSJNMTkgMTRWMjJIMi45OTk5N1Y0SDEzIi8+IDxwYXRoIGQ9Ik0xNy40NjA4IDQuMDM5MjFDMTguMjQxOCAzLjI1ODE3IDE5LjUwODIgMy4yNTgxNiAyMC4yODkyIDQuMDM5MjFMMjAuOTYwOCA0LjcxMDc5QzIxLjc0MTggNS40OTE4NCAyMS43NDE4IDYuNzU4MTcgMjAuOTYwOCA3LjUzOTIxTDExLjU4NTggMTYuOTE0MkMxMS4yMTA3IDE3LjI4OTMgMTAuNzAyIDE3LjUgMTAuMTcxNiAxNy41TDcuNSAxNy41TDcuNSAxNC44Mjg0QzcuNSAxNC4yOTggNy43MTA3MSAxMy43ODkzIDguMDg1NzkgMTMuNDE0MkwxNy40NjA4IDQuMDM5MjFaIi8+IDxwYXRoIGQ9Ik0xNi4yNSA1LjI1TDE5Ljc1IDguNzUiLz4gPC9zdmc+)'
  40. };
  41. const nameSet = {
  42. noteBtn: 'note-obj-gf-note-btn',
  43. infoNoteBtn: 'note-obj-gf-info-note-btn',
  44. libraryNoteBtn: 'note-obj-gf-library-note-btn',
  45. listNoteBtn: 'note-obj-gf-list-note-btn',
  46. tsNoteBtn: 'note-obj-gf-ts-note-btn',
  47. noteTag: 'note-obj-gf-note-tag',
  48. tsNoteTag: 'note-obj-gf-ts-note-tag',
  49. listNoteTag: 'note-obj-gf-list-note-tag'
  50. };
  51. const GF_STYLE = `
  52. .${nameSet.noteBtn} {
  53. background-image: ${GF_ICON.NOTE_BLACK};
  54. background-repeat: no-repeat;
  55. background-position: center;
  56. cursor: pointer;
  57. vertical-align: top;
  58. }
  59. .${nameSet.infoNoteBtn} {
  60. background-size: 32px auto;
  61. width: 32px;
  62. height: 32px;
  63. margin-left: 20px;
  64. display: inline-block;
  65. }
  66. .${nameSet.libraryNoteBtn} {
  67. background-size: 24px auto;
  68. width: 24px;
  69. height: 24px;
  70. margin-left: 20px;
  71. display: inline-block;
  72. }
  73. .${nameSet.listNoteBtn} {
  74. background-size: 24px auto;
  75. width: 24px;
  76. height: 24px;
  77. margin-left: 10px;
  78. display: none;
  79. }
  80. .${nameSet.tsNoteBtn} {
  81. background-size: 16px auto;
  82. width: 16px;
  83. height: 16px;
  84. margin-left: 10px;
  85. display: none;
  86. vertical-align: sub;
  87. }
  88. ol.script-list li:hover .${nameSet.listNoteBtn},
  89. #script-table tbody tr:hover .${nameSet.tsNoteBtn} {
  90. display: inline-block;
  91. }
  92. .${nameSet.noteTag},
  93. .${nameSet.tsNoteTag} {
  94. background-color: #3c81df;
  95. color: #fff;
  96. display: inline-block;
  97. align-items: center;
  98. white-space: nowrap;
  99. border-radius: 50px;
  100. padding: 1px 10px;
  101. line-height: 1em;
  102. }
  103. .${nameSet.listNoteTag} {
  104. text-decoration: none;
  105. }`;
  106. const noteObj = new Note_Obj({
  107. id: 'myGreasyForkNote',
  108. script: {
  109. author: {
  110. name: 'pana',
  111. homepage: 'https://greasyfork.org/zh-CN/users/193133-pana'
  112. },
  113. url: 'https://greasyfork.org/scripts/404275',
  114. updated: UPDATED
  115. },
  116. itemClick: key => `${location.origin}/scripts/${key}`,
  117. language: {
  118. userIdText: {
  119. en: 'Script ID',
  120. zhHans: '脚本 ID',
  121. zhHant: '指令碼 ID'
  122. },
  123. userNameText: {
  124. en: 'Script name',
  125. zhHans: '脚本名',
  126. zhHant: '指令碼名'
  127. }
  128. },
  129. changeEvent,
  130. style: GF_STYLE
  131. });
  132. function changeEvent(id) {
  133. const scriptId = getScriptIdFromPathname(location.pathname);
  134. if (scriptId) {
  135. infoPageNotes(scriptId, undefined, id);
  136. } else {
  137. listPageNotes(id);
  138. initTS(id);
  139. }
  140. }
  141. function initTS(changeId) {
  142. noteObj.fn.docQueryAll('#script-table tbody tr').forEach(item => {
  143. const scriptTitle = noteObj.fn.queryAnchor(item, '.thetitle a');
  144. if (scriptTitle) {
  145. const res = scriptTitle.href.match(/\d+$/);
  146. if (res) {
  147. const scriptId = res[0];
  148. const scriptName = scriptTitle.textContent?.trim();
  149. const thetitle = noteObj.fn.query(item, '.thetitle');
  150. if (thetitle && !noteObj.fn.query(thetitle, '.' + Note_Obj.btnClassName, 'none')) {
  151. thetitle.appendChild(noteObj.createNoteBtn(scriptId, scriptName, [nameSet.noteBtn, nameSet.tsNoteBtn]));
  152. }
  153. if (!changeId || changeId === scriptId) {
  154. noteObj.handler(scriptId, scriptTitle, undefined, {
  155. add: 'span',
  156. className: [nameSet.tsNoteTag]
  157. }, scriptName);
  158. }
  159. }
  160. }
  161. });
  162. }
  163. function getScriptIdFromPathname(pathname) {
  164. const res = pathname.match(/^\/[\w-]+\/scripts\/(\d+)-/);
  165. if (res && res.length === 2) {
  166. return res[1];
  167. }
  168. return null;
  169. }
  170. function infoPageNotes(scriptId, scriptName, changeId) {
  171. const ele = noteObj.fn.docQuery('#script-info h2', 'info');
  172. if (ele) {
  173. if (!changeId || changeId === scriptId) noteObj.handler(scriptId, ele, undefined, {
  174. add: 'sapn',
  175. className: [nameSet.noteTag]
  176. }, scriptName);
  177. }
  178. }
  179. function listPageNotes(changeId) {
  180. const list = noteObj.fn.docQueryAll('ol.script-list li', 'info');
  181. for (const ele of list) {
  182. const scriptId = ele.dataset.scriptId;
  183. if (scriptId) {
  184. const description = noteObj.fn.query(ele, '.description');
  185. const scriptName = noteObj.fn.getText(ele, 'article > h2 > a', 'warn');
  186. if (description) {
  187. const desParent = description.parentElement;
  188. if (desParent && !noteObj.fn.query(desParent, '.' + Note_Obj.btnClassName, 'none')) {
  189. description.before(noteObj.createNoteBtn(scriptId, scriptName, [nameSet.noteBtn, nameSet.listNoteBtn]));
  190. }
  191. }
  192. const header = noteObj.fn.query(ele, 'article > h2 > a');
  193. if (header) {
  194. if (!changeId || changeId === scriptId) noteObj.handler(scriptId, header, undefined, {
  195. add: 'span',
  196. className: [nameSet.noteTag, nameSet.listNoteTag]
  197. }, scriptName);
  198. }
  199. }
  200. }
  201. }
  202. function init() {
  203. const scriptId = getScriptIdFromPathname(location.pathname);
  204. if (scriptId) {
  205. const installHelpLink = noteObj.fn.docQuery('#install-area .install-help-link:last-child', 'info');
  206. const scriptName = noteObj.fn.docGetText('header h2');
  207. if (installHelpLink) {
  208. installHelpLink.after(noteObj.createNoteBtn(scriptId, scriptName, [nameSet.noteBtn, nameSet.infoNoteBtn]));
  209. } else {
  210. const suggestion = noteObj.fn.docQuery('#script-feedback-suggestion');
  211. suggestion?.appendChild(noteObj.createNoteBtn(scriptId, scriptName, [nameSet.noteBtn, nameSet.libraryNoteBtn]));
  212. }
  213. infoPageNotes(scriptId, scriptName);
  214. } else {
  215. listPageNotes();
  216. const scriptList = noteObj.fn.docQuery('#browse-script-list', 'info');
  217. if (scriptList) {
  218. const listObserver = new MutationObserver(() => {
  219. listPageNotes();
  220. });
  221. listObserver.observe(scriptList, {
  222. childList: true
  223. });
  224. }
  225. initTS();
  226. const tsTbody = noteObj.fn.docQuery('#script-table tbody', 'none');
  227. if (tsTbody) {
  228. const tsObserver = new MutationObserver(() => {
  229. initTS();
  230. });
  231. tsObserver.observe(tsTbody, {
  232. childList: true
  233. });
  234. }
  235. }
  236. }
  237. init();
  238. })();