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

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

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

  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.4
  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@19b95c341c3d2dd9771a1bb841590c244bee8e5f/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-09';
  38. const GF_ICON = {
  39. NOTE_BLACK: 'url(data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBhcmlhLWxhYmVsbGVkYnk9Im5ld0ljb25UaXRsZSIgc3Ryb2tlPSJyZ2IoMzgsIDM4LCAzOCkiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgZmlsbD0ibm9uZSIgY29sb3I9InJnYigzOCwgMzgsIDM4KSI+IDx0aXRsZSBpZD0ibmV3SWNvblRpdGxlIj5OZXc8L3RpdGxlPiA8cGF0aCBkPSJNMTkgMTRWMjJIMi45OTk5N1Y0SDEzIi8+IDxwYXRoIGQ9Ik0xNy40NjA4IDQuMDM5MjFDMTguMjQxOCAzLjI1ODE3IDE5LjUwODIgMy4yNTgxNiAyMC4yODkyIDQuMDM5MjFMMjAuOTYwOCA0LjcxMDc5QzIxLjc0MTggNS40OTE4NCAyMS43NDE4IDYuNzU4MTcgMjAuOTYwOCA3LjUzOTIxTDExLjU4NTggMTYuOTE0MkMxMS4yMTA3IDE3LjI4OTMgMTAuNzAyIDE3LjUgMTAuMTcxNiAxNy41TDcuNSAxNy41TDcuNSAxNC44Mjg0QzcuNSAxNC4yOTggNy43MTA3MSAxMy43ODkzIDguMDg1NzkgMTMuNDE0MkwxNy40NjA4IDQuMDM5MjFaIi8+IDxwYXRoIGQ9Ik0xNi4yNSA1LjI1TDE5Ljc1IDguNzUiLz4gPC9zdmc+)'
  40. };
  41. const GF_STYLE = `
  42. .note-obj-gf-note-btn {
  43. background-image: ${GF_ICON.NOTE_BLACK};
  44. background-repeat: no-repeat;
  45. background-position: center;
  46. cursor: pointer;
  47. vertical-align: top;
  48. }
  49. .note-obj-gf-info-note-btn {
  50. background-size: 32px auto;
  51. width: 32px;
  52. height: 32px;
  53. margin-left: 20px;
  54. display: inline-block;
  55. }
  56. .note-obj-gf-library-note-btn {
  57. background-size: 24px auto;
  58. width: 24px;
  59. height: 24px;
  60. margin-left: 20px;
  61. display: inline-block;
  62. }
  63. .note-obj-gf-list-note-btn {
  64. background-size: 24px auto;
  65. width: 24px;
  66. height: 24px;
  67. margin-left: 10px;
  68. display: none;
  69. }
  70. .note-obj-gf-ts-note-btn {
  71. background-size: 16px auto;
  72. width: 16px;
  73. height: 16px;
  74. margin-left: 10px;
  75. display: none;
  76. vertical-align: sub;
  77. }
  78. ol.script-list li:hover .note-obj-gf-list-note-btn,
  79. #script-table tbody tr:hover .note-obj-gf-ts-note-btn {
  80. display: inline-block;
  81. }
  82. .note-obj-gf-note-tag,
  83. .note-obj-gf-ts-note-tag {
  84. background-color: #3c81df;
  85. color: #fff;
  86. display: inline-block;
  87. align-items: center;
  88. white-space: nowrap;
  89. border-radius: 50px;
  90. padding: 1px 10px;
  91. line-height: 1em;
  92. }
  93. .note-obj-gf-list-note-tag {
  94. text-decoration: none;
  95. }
  96. `;
  97. const noteObj = new Note_Obj({
  98. id: 'myGreasyForkNote',
  99. script: {
  100. author: {
  101. name: 'pana',
  102. homepage: 'https://greasyfork.org/zh-CN/users/193133-pana'
  103. },
  104. url: 'https://greasyfork.org/scripts/404275',
  105. updated: UPDATED
  106. },
  107. itemClick: key => `${location.origin}/scripts/${key}`,
  108. language: {
  109. userIdText: {
  110. en: 'Script ID',
  111. zhHans: '脚本 ID',
  112. zhHant: '指令碼 ID'
  113. },
  114. userNameText: {
  115. en: 'Script name',
  116. zhHans: '脚本名',
  117. zhHant: '指令碼名'
  118. }
  119. },
  120. changeEvent,
  121. style: GF_STYLE
  122. });
  123. function changeEvent(id) {
  124. const scriptId = getScriptIdFromPathname(location.pathname);
  125. if (scriptId) {
  126. infoPageNotes(scriptId, undefined, id);
  127. } else {
  128. listPageNotes(id);
  129. initTS(id);
  130. }
  131. }
  132. function initTS(changeId) {
  133. noteObj.fn.docQueryAll('#script-table tbody tr').forEach(item => {
  134. const scriptTitle = noteObj.fn.queryAnchor(item, '.thetitle a');
  135. if (scriptTitle) {
  136. const res = scriptTitle.href.match(/\d+$/);
  137. if (res) {
  138. const scriptId = res[0];
  139. const scriptName = scriptTitle.textContent?.trim();
  140. const thetitle = noteObj.fn.query(item, '.thetitle');
  141. if (thetitle && !noteObj.fn.query(thetitle, '.' + Note_Obj.btnClassName, 'none')) {
  142. thetitle.appendChild(noteObj.createNoteBtn(scriptId, scriptName, ['note-obj-gf-note-btn', 'note-obj-gf-ts-note-btn']));
  143. }
  144. if (!changeId || changeId === scriptId) {
  145. noteObj.handler(scriptId, scriptTitle, undefined, {
  146. add: 'span',
  147. className: ['note-obj-gf-ts-note-tag']
  148. }, scriptName);
  149. }
  150. }
  151. }
  152. });
  153. }
  154. function getScriptIdFromPathname(pathname) {
  155. const res = pathname.match(/^\/[\w-]+\/scripts\/(\d+)-/);
  156. if (res && res.length === 2) {
  157. return res[1];
  158. }
  159. return null;
  160. }
  161. function infoPageNotes(scriptId, scriptName, changeId) {
  162. const ele = noteObj.fn.docQuery('#script-info h2', 'info');
  163. if (ele) {
  164. if (!changeId || changeId === scriptId) noteObj.handler(scriptId, ele, undefined, {
  165. add: 'sapn',
  166. className: ['note-obj-gf-note-tag']
  167. }, scriptName);
  168. }
  169. }
  170. function listPageNotes(changeId) {
  171. const list = noteObj.fn.docQueryAll('ol.script-list li', 'info');
  172. for (const ele of list) {
  173. const scriptId = ele.dataset.scriptId;
  174. if (scriptId) {
  175. const description = noteObj.fn.query(ele, '.description');
  176. const scriptName = noteObj.fn.getText(ele, 'article > h2 > a', 'warn');
  177. if (description) {
  178. const desParent = description.parentElement;
  179. if (desParent && !noteObj.fn.query(desParent, '.' + Note_Obj.btnClassName, 'none')) {
  180. description.before(noteObj.createNoteBtn(scriptId, scriptName, ['note-obj-gf-note-btn', 'note-obj-gf-list-note-btn']));
  181. }
  182. }
  183. const header = noteObj.fn.query(ele, 'article > h2 > a');
  184. if (header) {
  185. if (!changeId || changeId === scriptId) noteObj.handler(scriptId, header, undefined, {
  186. add: 'span',
  187. className: ['note-obj-gf-note-tag', 'note-obj-gf-list-note-tag']
  188. }, scriptName);
  189. }
  190. }
  191. }
  192. }
  193. function init() {
  194. const scriptId = getScriptIdFromPathname(location.pathname);
  195. if (scriptId) {
  196. const installHelpLink = noteObj.fn.docQuery('#install-area .install-help-link:last-child', 'info');
  197. const scriptName = noteObj.fn.docGetText('header h2');
  198. if (installHelpLink) {
  199. installHelpLink.after(noteObj.createNoteBtn(scriptId, scriptName, ['note-obj-gf-note-btn', 'note-obj-gf-info-note-btn']));
  200. } else {
  201. const suggestion = noteObj.fn.docQuery('#script-feedback-suggestion');
  202. suggestion?.appendChild(noteObj.createNoteBtn(scriptId, scriptName, ['note-obj-gf-note-btn', 'note-obj-gf-library-note-btn']));
  203. }
  204. infoPageNotes(scriptId, scriptName);
  205. } else {
  206. listPageNotes();
  207. const scriptList = noteObj.fn.docQuery('#browse-script-list', 'info');
  208. if (scriptList) {
  209. const listObserver = new MutationObserver(() => {
  210. listPageNotes();
  211. });
  212. listObserver.observe(scriptList, {
  213. childList: true
  214. });
  215. }
  216. initTS();
  217. const tsTbody = noteObj.fn.docQuery('#script-table tbody', 'none');
  218. if (tsTbody) {
  219. const tsObserver = new MutationObserver(() => {
  220. initTS();
  221. });
  222. tsObserver.observe(tsTbody, {
  223. childList: true
  224. });
  225. }
  226. }
  227. }
  228. init();
  229. })();