Greasyfork 快捷编辑收藏

在GF脚本页添加快速打开收藏集编辑页面功能

Fra 03.11.2023. Se den seneste versjonen.

  1. /* eslint-disable no-multi-spaces */
  2.  
  3. // ==UserScript==
  4. // @name Greasyfork 快捷编辑收藏
  5. // @name:zh-CN Greasyfork 快捷编辑收藏
  6. // @name:zh-TW Greasyfork 快捷編輯收藏
  7. // @name:en Greasyfork script-set-edit button
  8. // @name:en-US Greasyfork script-set-edit button
  9. // @namespace Greasyfork-Favorite
  10. // @version 0.1.8.1
  11. // @description 在GF脚本页添加快速打开收藏集编辑页面功能
  12. // @description:zh-CN 在GF脚本页添加快速打开收藏集编辑页面功能
  13. // @description:zh-TW 在GF腳本頁添加快速打開收藏集編輯頁面功能
  14. // @description:en Add open script-set-edit-page button in GF script page
  15. // @description:en-US Add open script-set-edit-page button in GF script page
  16. // @author PY-DNG
  17. // @license GPL-3
  18. // @match http*://*.greatest.deepsurf.us/*
  19. // @match http*://*.sleazyfork.org/*
  20. // @require https://greatest.deepsurf.us/scripts/456034-basic-functions-for-userscripts/code/script.js?version=1226884
  21. // @require https://greatest.deepsurf.us/scripts/460385-gm-web-hooks/code/script.js?version=1221394
  22. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAbBJREFUOE+Vk7GKGlEUhr8pAiKKDlqpCDpLUCzWBxCENBa+hBsL9wHsLWxXG4tNtcGH0MIiWopY7JSGEUWsbESwUDMw4Z7siLsZDbnlPff/7n/+e67G38sA6sAXIPVWXgA/gCdgfinRPuhfCoXCw3Q65XA4eLBl6zvw1S2eAZqmvTqOc5/NZhkMBqRSKWzbvgYxgbwquoAX4MGyLHK5HIlEgtFo9C+IOFEAo1gsWsvlUmyPx2MymYxAhsMh6XT6lpM7BXjWdf1xNpuRz+fl8GQywTAMGo0G1WpVnJxOJ692vinADPgcDAaZz+cCOR6PmKZJPB4XUb/fp1wuewF+KoBCf1JVBVE5dDodms3mWdDtdqlUKl6AX+8ALmS9XgtM0/5kvNlspKX9fv8RIgBp4bISCoXo9XqsVitKpRK6rrPb7STQ7XZ7eVRaeAYerz14OBxGOfL7/eIgmUwKzHEcJZEQ1eha1wBqPxqNihufzyeQWCzmtiPPqJYM0jWIyiISibBYLAgEAtTrdVqt1nmQXN0rcH/LicqmVqvRbrdN27bfjbKru+nk7ZD3Z7q4+b++82/YPKIrXsKZ3AAAAABJRU5ErkJggg==
  23. // @grant GM_xmlhttpRequest
  24. // @grant GM_setValue
  25. // @grant GM_getValue
  26. // ==/UserScript==
  27.  
  28. /* global LogLevel DoLog Err $ $All $CrE $AEL $$CrE addStyle detectDom destroyEvent copyProp copyProps parseArgs escJsStr replaceText getUrlArgv dl_browser dl_GM AsyncManager */
  29. /* global GMXHRHook GMDLHook */
  30.  
  31. (function __MAIN__() {
  32. 'use strict';
  33.  
  34. const CONST = {
  35. Text: {
  36. 'zh-CN': {
  37. FavEdit: '收藏集:',
  38. Add: '加入此集',
  39. Edit: '手动编辑',
  40. CopySID: '复制脚本ID',
  41. Working: ['正在添加...', '就快好了...'],
  42. InSetStatus: ['[ ]', '[✔]'],
  43. Error: {
  44. Unknown: '未知错误'
  45. }
  46. },
  47. 'zh-TW': {
  48. FavEdit: '收藏集:',
  49. Add: '加入此集',
  50. Edit: '手動編輯',
  51. CopySID: '複製腳本ID',
  52. Working: ['正在添加...', '就快好了...'],
  53. InSetStatus: ['[ ]', '[✔]'],
  54. Error: {
  55. Unknown: '未知錯誤'
  56. }
  57. },
  58. 'en': {
  59. FavEdit: 'Add to/Remove from favorite list: ',
  60. Add: 'Add',
  61. Edit: 'Edit Manually',
  62. CopySID: 'Copy-Script-ID',
  63. Working: ['Working...', 'Just a moment...'],
  64. InSetStatus: ['[ ]', '[✔]'],
  65. Error: {
  66. Unknown: 'Unknown Error'
  67. }
  68. },
  69. 'default': {
  70. FavEdit: 'Add to/Remove from favorite list: ',
  71. Add: 'Add',
  72. Edit: 'Edit Manually',
  73. CopySID: 'Copy-Script-ID',
  74. Working: ['Working...', 'Just a moment...'],
  75. InSetStatus: ['[ ]', '[✔]'],
  76. Error: {
  77. Unknown: 'Unknown Error'
  78. }
  79. },
  80. }
  81. }
  82.  
  83. // Get i18n code
  84. let i18n = navigator.language;
  85. if (!Object.keys(CONST.Text).includes(i18n)) {i18n = 'default';}
  86.  
  87. main()
  88. function main() {
  89. const HOST = getHost();
  90. const API = getAPI();
  91.  
  92. // Common actions
  93. commons();
  94.  
  95. // API-based actions
  96. switch(API[1]) {
  97. case "scripts":
  98. API[2] && centerScript(API);
  99. break;
  100. default:
  101. DoLog('API is {}'.replace('{}', API));
  102. }
  103. }
  104.  
  105. function centerScript(API) {
  106. switch(API[3]) {
  107. case undefined:
  108. pageScript();
  109. break;
  110. case 'code':
  111. pageCode();
  112. break;
  113. case 'feedback':
  114. pageFeedback();
  115. break;
  116. }
  117. }
  118.  
  119. function commons() {
  120. // Your common actions here...
  121. GMXHRHook(5);
  122. }
  123.  
  124. function pageScript() {
  125. addFavPanel();
  126. }
  127.  
  128. function pageCode() {
  129. addFavPanel();
  130. }
  131.  
  132. function pageFeedback() {
  133. addFavPanel();
  134. }
  135.  
  136. function addFavPanel() {
  137. if (!getUserpage()) {return false;}
  138. GUI();
  139.  
  140. function GUI() {
  141. // Get elements
  142. const script_after = $('#script-feedback-suggestion+*') || $('#new-script-discussion');
  143. const script_parent = script_after.parentElement;
  144.  
  145. // My elements
  146. const script_favorite = $CrE('div');
  147. script_favorite.id = 'script-favorite';
  148. script_favorite.style.margin = '0.75em 0';
  149. script_favorite.innerHTML = CONST.Text[i18n].FavEdit;
  150.  
  151. const favorite_groups = $CrE('select');
  152. favorite_groups.id = 'favorite-groups';
  153.  
  154. const stored_sets = GM_getValue('script-sets', {sets: []}).sets;
  155. for (const set of stored_sets) {
  156. // Make <option>
  157. const option = $CrE('option');
  158. option.innerText = set.name;
  159. option.value = set.linkedit;
  160. $APD(favorite_groups, option);
  161. }
  162. adjustWidth();
  163.  
  164. getScriptSets(function(sets) {
  165. clearChildnodes(favorite_groups);
  166. for (const set of sets) {
  167. // Make <option>
  168. const option = set.elmOption = $CrE('option');
  169. option.innerText = set.name;
  170. option.value = set.linkedit;
  171. $APD(favorite_groups, option);
  172. }
  173. adjustWidth();
  174.  
  175. // Set edit-button.href
  176. favorite_edit.href = favorite_groups.value;
  177.  
  178. // Check script in-set status
  179. getInSets(sets, getStrSID(), inSets => {
  180. sets.forEach(set => {
  181. const inSet = inSets.includes(set);
  182. set.elmOption.innerText = `${CONST.Text[i18n].InSetStatus[inSet+0]} ${set.name}`;
  183. });
  184. adjustWidth();
  185. });
  186. })
  187. favorite_groups.addEventListener('change', function(e) {
  188. favorite_edit.href = favorite_groups.value;
  189. });
  190.  
  191. const favorite_add = $CrE('a');
  192. favorite_add.id = 'favorite-add';
  193. favorite_add.innerHTML = CONST.Text[i18n].Add;
  194. favorite_add.style.margin = favorite_add.style.margin = '0px 0.5em';
  195. favorite_add.href = 'javascript:void(0);'
  196. favorite_add.addEventListener('click', function(e) {
  197. addFav();
  198. });
  199.  
  200. const favorite_edit = $CrE('a');
  201. favorite_edit.id = 'favorite-edit';
  202. favorite_edit.innerHTML = CONST.Text[i18n].Edit;
  203. favorite_edit.style.margin = favorite_edit.style.margin = '0px 0.5em';
  204. favorite_edit.target = '_blank';
  205.  
  206. const favorite_copy = $CrE('a');
  207. favorite_copy.id = 'favorite-copy';
  208. favorite_copy.href = 'javascript: void(0);';
  209. favorite_copy.innerHTML = CONST.Text[i18n].CopySID;
  210. favorite_copy.addEventListener('click', function() {
  211. copyText(getStrSID());
  212. });
  213.  
  214. // Append to document
  215. $APD(script_favorite, favorite_groups);
  216. script_parent.insertBefore(script_favorite, script_after);
  217. $APD(script_favorite, favorite_add);
  218. $APD(script_favorite, favorite_edit);
  219. $APD(script_favorite, favorite_copy);
  220.  
  221. function adjustWidth() {
  222. favorite_groups.style.width = Math.max.apply(null, Array.from(favorite_groups.children).map((o) => (o.innerText.length))).toString() + 'em';
  223. favorite_groups.style.maxWidth = '40vw';
  224. }
  225.  
  226. function addFav() {
  227. const option = favorite_groups.selectedOptions[0];
  228. const set = GM_getValue('script-sets').sets.find(set => set.linkedit === option.value);
  229. const iframe = $CrE('iframe');
  230. iframe.style.width = iframe.style.height = iframe.style.border = '0';
  231. $AEL(iframe, 'load', edit_onload, {once: true});
  232. $AEL(iframe, 'error', iframe_onerror);
  233. iframe.src = favorite_groups.value;
  234. $APD(document.body, iframe);
  235. displayNotice(CONST.Text[i18n].Working[0]);
  236.  
  237. function edit_onload() {
  238. const oDom = iframe.contentDocument;
  239. const input = $CrE('input');
  240. input.value = getStrSID();
  241. input.name = 'scripts-included[]';
  242. input.type = 'hidden';
  243. $APD($(oDom, '#script-set-scripts'), input);
  244. $(oDom, 'button[name="save"]').click();
  245. iframe.addEventListener('load', finish_onload, {once: true});
  246. displayNotice(CONST.Text[i18n].Working[1]);
  247. }
  248.  
  249. function finish_onload() {
  250. const status = $(iframe.contentDocument, 'p.notice');
  251. const status_text = status ? status.innerText : CONST.Text[i18n].Error.Unknown;
  252. displayNotice(status_text);
  253. iframe.parentElement.removeChild(iframe);
  254. option.innerText = `${CONST.Text[i18n].InSetStatus[1]} ${set.name}`;
  255. }
  256.  
  257. function iframe_onerror() {
  258. iframe.src = iframe.src;
  259. }
  260.  
  261. function displayNotice(text) {
  262. const notice = $CrE('p');
  263. notice.classList.add('notice');
  264. notice.id = 'fav-notice';
  265. notice.innerText = text;
  266. const old_notice = $('#fav-notice');
  267. old_notice && old_notice.parentElement.removeChild(old_notice);
  268. $('#script-content').insertAdjacentElement('afterbegin', notice);
  269. }
  270. }
  271. }
  272. }
  273.  
  274. function getScriptSets(callback, args=[]) {
  275. const userpage = getUserpage();
  276. getDocument(userpage, function(oDom) {
  277. /*
  278. const user_script_sets = oDom.querySelector('#user-script-sets');
  279. const script_sets = [];
  280.  
  281. for (const li of user_script_sets.querySelectorAll('li')) {
  282. // Get fav info
  283. const name = li.childNodes[0].nodeValue.trimRight();
  284. const link = li.children[0].href;
  285. const linkedit = li.children[1] ? li.children[1].href : 'https://greatest.deepsurf.us/' + $('#language-selector-locale').value + '/users/' + $('#nav-user-info>.user-profile-link>a').href.match(/[a-zA-Z\-]+\/users\/([^\/]*)/)[1] + '/sets/' + li.children[0].href.match(/[\?&]set=(\d+)/)[1] + '/edit';
  286.  
  287. // Append to script_sets
  288. script_sets.push({
  289. name: name,
  290. link: link,
  291. linkedit: linkedit
  292. });
  293. }
  294. */
  295. const script_sets = Array.from($(oDom, 'ul#user-script-sets').children).map(li => ({
  296. name: li.children[0].innerText,
  297. link: li.children[0].href,
  298. linkedit: li.children[1].href
  299. }));
  300.  
  301. // Save to GM_storage
  302. GM_setValue('script-sets', {
  303. sets: script_sets,
  304. time: (new Date()).getTime(),
  305. version: '0.2'
  306. });
  307.  
  308. // callback
  309. callback.apply(null, [script_sets].concat(args));
  310. });
  311. }
  312.  
  313. function getUserpage() {
  314. const a = $('#nav-user-info>.user-profile-link>a');
  315. return a ? a.href : null;
  316. }
  317.  
  318. function getInSet(set, sid, callback) {
  319. sid = sid.toString();
  320. getDocument(set.linkedit, oDom => {
  321. const inSet = [...$(oDom, '#script-set-scripts').children].some(input => input.value === sid);
  322. callback(inSet);
  323. });
  324. }
  325.  
  326. function getInSets(sets, sid, callback) {
  327. const AM = new AsyncManager();
  328. const inSets = [];
  329. for (const set of sets) {
  330. AM.add();
  331. getInSet(set, sid, inSet => {
  332. inSet && inSets.push(set);
  333. AM.finish();
  334. });
  335. }
  336. AM.onfinish = e => {
  337. callback(inSets);
  338. };
  339. AM.finishEvent = true;
  340. }
  341.  
  342. function getStrSID(url=location.href) {
  343. const API = getAPI(url);
  344. const strSID = API[2].match(/\d+/);
  345. return strSID;
  346. }
  347.  
  348. function getSID(url=location.href) {
  349. return Number(getStrSID(url));
  350. }
  351. // Basic functions
  352. function $APD(a,b) {return a.appendChild(b);}
  353.  
  354. // Remove all childnodes from an element
  355. function clearChildnodes(element) {
  356. const cns = []
  357. for (const cn of element.childNodes) {
  358. cns.push(cn);
  359. }
  360. for (const cn of cns) {
  361. element.removeChild(cn);
  362. }
  363. }
  364.  
  365. // Download and parse a url page into a html document(dom).
  366. // when xhr onload: callback.apply([dom, args])
  367. function getDocument(url, callback, args=[]) {
  368. GM_xmlhttpRequest({
  369. method : 'GET',
  370. url : url,
  371. responseType : 'blob',
  372. onloadstart : function() {
  373. DoLog(LogLevel.Info, 'getting document, url=\'' + url + '\'');
  374. },
  375. onload : function(response) {
  376. const htmlblob = response.response;
  377. parseDocument(htmlblob, callback, args);
  378. }
  379. })
  380. }
  381.  
  382. function parseDocument(htmlblob, callback, args=[]) {
  383. const reader = new FileReader();
  384. reader.onload = function(e) {
  385. const htmlText = reader.result;
  386. const dom = new DOMParser().parseFromString(htmlText, 'text/html');
  387. args = [dom].concat(args);
  388. callback.apply(null, args);
  389. //callback(dom, htmlText);
  390. }
  391. reader.readAsText(htmlblob, document.characterSet);
  392. }
  393.  
  394. // Copy text to clipboard (needs to be called in an user event)
  395. function copyText(text) {
  396. // Create a new textarea for copying
  397. const newInput = document.createElement('textarea');
  398. document.body.appendChild(newInput);
  399. newInput.value = text;
  400. newInput.select();
  401. document.execCommand('copy');
  402. document.body.removeChild(newInput);
  403. }
  404.  
  405. // get '/' splited API array from a url
  406. function getAPI(url=location.href) {
  407. return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);
  408. }
  409.  
  410. // get host part from a url(includes '^https://', '/$')
  411. function getHost(url=location.href) {
  412. const match = location.href.match(/https?:\/\/[^\/]+\//);
  413. return match ? match[0] : match;
  414. }
  415.  
  416. function randint(min, max) {
  417. return Math.floor(Math.random() * (max - min + 1)) + min;
  418. }
  419. })();