- /* eslint-disable no-multi-spaces */
-
- // ==UserScript==
- // @name Greasyfork 快捷编辑收藏
- // @name:zh-CN Greasyfork 快捷编辑收藏
- // @name:zh-TW Greasyfork 快捷編輯收藏
- // @name:en Greasyfork script-set-edit button
- // @name:en-US Greasyfork script-set-edit button
- // @namespace Greasyfork-Favorite
- // @version 0.1.6
- // @description 在GF脚本页添加快速打开收藏集编辑页面功能
- // @description:zh-CN 在GF脚本页添加快速打开收藏集编辑页面功能
- // @description:zh-TW 在GF腳本頁添加快速打開收藏集編輯頁面功能
- // @description:en Add open script-set-edit-page button in GF script page
- // @description:en-US Add open script-set-edit-page button in GF script page
- // @author PY-DNG
- // @license GPL-3
- // @match http*://greasyfork.org/*
- // @match http*://sleazyfork.org/*
- // @include http*://greasyfork.org/*
- // @include http*://sleazyfork.org/*
- // @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==
- // @grant GM_xmlhttpRequest
- // @grant GM_setValue
- // @grant GM_getValue
- // ==/UserScript==
-
- (function __MAIN__() {
- 'use strict';
-
- // function DoLog() {}
- // Arguments: level=LogLevel.Info, logContent, trace=false
- const [LogLevel, DoLog] = (function() {
- const LogLevel = {
- None: 0,
- Error: 1,
- Success: 2,
- Warning: 3,
- Info: 4,
- };
-
- return [LogLevel, DoLog];
- function DoLog() {
- // Get window
- const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
-
- const LogLevelMap = {};
- LogLevelMap[LogLevel.None] = {
- prefix: '',
- color: 'color:#ffffff'
- }
- LogLevelMap[LogLevel.Error] = {
- prefix: '[Error]',
- color: 'color:#ff0000'
- }
- LogLevelMap[LogLevel.Success] = {
- prefix: '[Success]',
- color: 'color:#00aa00'
- }
- LogLevelMap[LogLevel.Warning] = {
- prefix: '[Warning]',
- color: 'color:#ffa500'
- }
- LogLevelMap[LogLevel.Info] = {
- prefix: '[Info]',
- color: 'color:#888888'
- }
- LogLevelMap[LogLevel.Elements] = {
- prefix: '[Elements]',
- color: 'color:#000000'
- }
-
- // Current log level
- DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
-
- // Log counter
- DoLog.logCount === undefined && (DoLog.logCount = 0);
-
- // Get args
- let [level, logContent, trace] = parseArgs([...arguments], [
- [2],
- [1,2],
- [1,2,3]
- ], [LogLevel.Info, 'DoLog initialized.', false]);
-
- // Log when log level permits
- if (level <= DoLog.logLevel) {
- let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
- let subst = LogLevelMap[level].color;
-
- switch (typeof(logContent)) {
- case 'string':
- msg += '%s';
- break;
- case 'number':
- msg += '%d';
- break;
- default:
- msg += '%o';
- break;
- }
-
- if (++DoLog.logCount > 512) {
- console.clear();
- DoLog.logCount = 0;
- }
- console[trace ? 'trace' : 'log'](msg, subst, logContent);
- }
- }
- }) ();
-
- const CONST = {
- Text: {
- 'zh-CN': {
- FavEdit: '收藏集:',
- Add: '加入此集',
- Edit: '手动编辑',
- CopySID: '复制脚本ID',
- Working: ['正在添加...', '就快好了...'],
- Error: {
- Unknown: '未知错误'
- }
- },
- 'zh-TW': {
- FavEdit: '收藏集:',
- Add: '加入此集',
- Edit: '手動編輯',
- CopySID: '複製腳本ID',
- Working: ['正在添加...', '就快好了...'],
- Error: {
- Unknown: '未知錯誤'
- }
- },
- 'en': {
- FavEdit: 'Add to/Remove from favorite list: ',
- Add: 'Add',
- Edit: 'Edit Manually',
- CopySID: 'Copy-Script-ID',
- Working: ['Working...', 'Just a moment...'],
- Error: {
- Unknown: 'Unknown Error'
- }
- },
- 'default': {
- FavEdit: 'Add to/Remove from favorite list: ',
- Add: 'Add',
- Edit: 'Edit Manually',
- CopySID: 'Copy-Script-ID',
- Working: ['Working...', 'Just a moment...'],
- Error: {
- Unknown: 'Unknown Error'
- }
- },
- }
- }
-
- // Get i18n code
- let i18n = navigator.language;
- if (!Object.keys(CONST.Text).includes(i18n)) {i18n = 'default';}
-
- main()
- function main() {
- const HOST = getHost();
- const API = getAPI();
-
- // Common actions
- commons();
-
- // API-based actions
- switch(API[1]) {
- case "scripts":
- API[2] && centerScript(API);
- break;
- default:
- DoLog('API is {}'.replace('{}', API));
- }
- }
-
- function centerScript(API) {
- switch(API[3]) {
- case undefined:
- pageScript();
- break;
- case 'code':
- pageCode();
- break;
- case 'feedback':
- pageFeedback();
- break;
- }
- }
-
- function commons() {
- // Your common actions here...
- }
-
- function pageScript() {
- addFavPanel();
- }
-
- function pageCode() {
- addFavPanel();
- }
-
- function pageFeedback() {
- addFavPanel();
- }
-
- function addFavPanel() {
- if (!getUserpage()) {return false;}
- GUI();
-
- function GUI() {
- // Get elements
- const script_after = $('#script-feedback-suggestion+*') || $('#new-script-discussion');
- const script_parent = script_after.parentElement;
-
- // My elements
- const script_favorite = $CrE('div');
- script_favorite.id = 'script-favorite';
- script_favorite.style.margin = '0.75em 0';
- script_favorite.innerHTML = CONST.Text[i18n].FavEdit;
-
- const favorite_groups = $CrE('select');
- favorite_groups.id = 'favorite-groups';
-
- const stored_sets = GM_getValue('script-sets', {sets: []}).sets;
- for (const set of stored_sets) {
- // Make <option>
- const option = $CrE('option');
- option.innerText = set.name;
- option.value = set.linkedit;
- $APD(favorite_groups, option);
- }
- adjustWidth();
-
- getScriptSets(function(sets) {
- clearChildnodes(favorite_groups);
- for (const set of sets) {
- // Make <option>
- const option = $CrE('option');
- option.innerText = set.name;
- option.value = set.linkedit;
- $APD(favorite_groups, option);
- }
- adjustWidth();
-
- // Set edit-button.href
- favorite_edit.href = favorite_groups.value;
- })
- favorite_groups.addEventListener('change', function(e) {
- favorite_edit.href = favorite_groups.value;
- });
-
- const favorite_add = $CrE('a');
- favorite_add.id = 'favorite-add';
- favorite_add.innerHTML = CONST.Text[i18n].Add;
- favorite_add.style.margin = favorite_add.style.margin = '0px 0.5em';
- favorite_add.href = 'javascript:void(0);'
- favorite_add.addEventListener('click', function(e) {
- addFav();
- });
-
- const favorite_edit = $CrE('a');
- favorite_edit.id = 'favorite-edit';
- favorite_edit.innerHTML = CONST.Text[i18n].Edit;
- favorite_edit.style.margin = favorite_edit.style.margin = '0px 0.5em';
- favorite_edit.target = '_blank';
-
- const favorite_copy = $CrE('a');
- favorite_copy.id = 'favorite-copy';
- favorite_copy.href = 'javascript: void(0);';
- favorite_copy.innerHTML = CONST.Text[i18n].CopySID;
- favorite_copy.addEventListener('click', function() {
- copyText(getStrSID());
- });
-
- // Append to document
- $APD(script_favorite, favorite_groups);
- script_parent.insertBefore(script_favorite, script_after);
- $APD(script_favorite, favorite_add);
- $APD(script_favorite, favorite_edit);
- $APD(script_favorite, favorite_copy);
-
- function adjustWidth() {
- favorite_groups.style.width = Math.max.apply(null, Array.from(favorite_groups.children).map((o) => (o.innerText.length))).toString() + 'em';
- favorite_groups.style.maxWidth = '40vw';
- }
-
- function addFav() {
- const iframe = $CrE('iframe');
- iframe.style.width = iframe.style.height = iframe.style.border = '0';
- iframe.addEventListener('load', edit_onload, {once: true});
- iframe.src = favorite_groups.value;
- $APD(document.body, iframe);
- displayNotice(CONST.Text[i18n].Working[0]);
-
- function edit_onload() {
- const oDom = iframe.contentDocument;
- const input = $CrE('input');
- input.value = getStrSID();
- input.name = 'scripts-included[]';
- input.type = 'hidden';
- $APD($(oDom, '#script-set-scripts'), input);
- $(oDom, 'button[name="save"]').click();
- iframe.addEventListener('load', finish_onload, {once: true});
- displayNotice(CONST.Text[i18n].Working[1]);
- }
-
- function finish_onload() {
- const status = $(iframe.contentDocument, 'p.notice');
- const status_text = status ? status.innerText : CONST.Text[i18n].Error.Unknown;
- displayNotice(status_text);
- iframe.parentElement.removeChild(iframe);
- }
-
- function displayNotice(text) {
- const notice = $CrE('p');
- notice.classList.add('notice');
- notice.id = 'fav-notice';
- notice.innerText = text;
- const old_notice = $('#fav-notice');
- old_notice && old_notice.parentElement.removeChild(old_notice);
- $('#script-content').insertAdjacentElement('afterbegin', notice);
- }
- }
- }
- }
-
- function getScriptSets(callback, args=[]) {
- const userpage = getUserpage();
- getDocument(userpage, function(oDom) {
- const user_script_sets = oDom.querySelector('#user-script-sets');
- const script_sets = [];
-
- for (const li of user_script_sets.querySelectorAll('li')) {
- // Get fav info
- const name = li.childNodes[0].nodeValue.trimRight();
- const link = li.children[0].href;
- const linkedit = li.children[1] ? li.children[1].href : 'https://greasyfork.org/' + $('#language-selector-locale').value + '/users/' + $('#nav-user-info>.user-profile-link>a').href.match(/zh-CN\/users\/([^\/]*)/)[1] + '/sets/' + li.children[0].href.match(/[\?&]set=(\d+)/)[1] + '/edit';
-
- // Append to script_sets
- script_sets.push({
- name: name,
- link: link,
- linkedit: linkedit
- });
- }
-
- // Save to GM_storage
- GM_setValue('script-sets', {
- sets: script_sets,
- time: (new Date()).getTime(),
- version: '0.1'
- });
-
- // callback
- callback.apply(null, [script_sets].concat(args));
- });
- }
-
- function getUserpage() {
- const a = $('#nav-user-info>.user-profile-link>a');
- return a ? a.href : null;
- }
-
- function getStrSID(url=location.href) {
- const API = getAPI(url);
- const strSID = API[2].match(/\d+/);
- return strSID;
- }
-
- function getSID(url=location.href) {
- return Number(getStrSID(url));
- }
-
- // Basic functions
- // querySelector
- function $() {
- switch(arguments.length) {
- case 2:
- return arguments[0].querySelector(arguments[1]);
- break;
- default:
- return document.querySelector(arguments[0]);
- }
- }
- // querySelectorAll
- function $All() {
- switch(arguments.length) {
- case 2:
- return arguments[0].querySelectorAll(arguments[1]);
- break;
- default:
- return document.querySelectorAll(arguments[0]);
- }
- }
- // createElement
- function $CrE() {
- switch(arguments.length) {
- case 2:
- return arguments[0].createElement(arguments[1]);
- break;
- default:
- return document.createElement(arguments[0]);
- }
- }
- function $APD(a,b) {return a.appendChild(b);}
- // Object1[prop] ==> Object2[prop]
- function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);}
- function copyProps(obj1, obj2, props) {props.forEach((prop) => (copyProp(obj1, obj2, prop)));}
-
- // Just stopPropagation and preventDefault
- function destroyEvent(e) {
- if (!e) {return false;};
- if (!e instanceof Event) {return false;};
- e.stopPropagation();
- e.preventDefault();
- }
-
- // Remove all childnodes from an element
- function clearChildnodes(element) {
- const cns = []
- for (const cn of element.childNodes) {
- cns.push(cn);
- }
- for (const cn of cns) {
- element.removeChild(cn);
- }
- }
-
- // Download and parse a url page into a html document(dom).
- // when xhr onload: callback.apply([dom, args])
- function getDocument(url, callback, args=[]) {
- GM_xmlhttpRequest({
- method : 'GET',
- url : url,
- responseType : 'blob',
- onloadstart : function() {
- DoLog(LogLevel.Info, 'getting document, url=\'' + url + '\'');
- },
- onload : function(response) {
- const htmlblob = response.response;
- parseDocument(htmlblob, callback, args);
- }
- })
- }
-
- function parseDocument(htmlblob, callback, args=[]) {
- const reader = new FileReader();
- reader.onload = function(e) {
- const htmlText = reader.result;
- const dom = new DOMParser().parseFromString(htmlText, 'text/html');
- args = [dom].concat(args);
- callback.apply(null, args);
- //callback(dom, htmlText);
- }
- reader.readAsText(htmlblob, document.characterSet);
- }
-
- // Get a url argument from lacation.href
- // also recieve a function to deal the matched string
- // returns defaultValue if name not found
- // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name'
- function getUrlArgv(details) {
- typeof(details) === 'string' && (details = {name: details});
- typeof(details) === 'undefined' && (details = {});
- if (!details.name) {return null;};
-
- const url = details.url ? details.url : location.href;
- const name = details.name ? details.name : '';
- const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;});
- const defaultValue = details.defaultValue ? details.defaultValue : null;
- const matcher = new RegExp('[\\?&]' + name + '=([^&#]+)');
- const result = url.match(matcher);
- const argv = result ? dealFunc(result[1]) : defaultValue;
-
- return argv;
- }
-
- // Copy text to clipboard (needs to be called in an user event)
- function copyText(text) {
- // Create a new textarea for copying
- const newInput = document.createElement('textarea');
- document.body.appendChild(newInput);
- newInput.value = text;
- newInput.select();
- document.execCommand('copy');
- document.body.removeChild(newInput);
- }
-
- // Append a style text to document(<head>) with a <style> element
- function addStyle(css, id) {
- const style = document.createElement("style");
- id && (style.id = id);
- style.textContent = css;
- for (const elm of document.querySelectorAll('#'+id)) {
- elm.parentElement && elm.parentElement.removeChild(elm);
- }
- document.head.appendChild(style);
- }
-
- // get '/' splited API array from a url
- function getAPI(url=location.href) {
- return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);
- }
-
- // get host part from a url(includes '^https://', '/$')
- function getHost(url=location.href) {
- const match = location.href.match(/https?:\/\/[^\/]+\//);
- return match ? match[0] : match;
- }
-
- function AsyncManager() {
- const AM = this;
-
- // Ongoing xhr count
- this.taskCount = 0;
-
- // Whether generate finish events
- let finishEvent = false;
- Object.defineProperty(this, 'finishEvent', {
- configurable: true,
- enumerable: true,
- get: () => (finishEvent),
- set: (b) => {
- finishEvent = b;
- b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
- }
- });
-
- // Add one task
- this.add = () => (++AM.taskCount);
-
- // Finish one task
- this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
- }
-
- function randint(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
-
- function parseArgs(args, rules, defaultValues=[]) {
- // args and rules should be array, but not just iterable (string is also iterable)
- if (!Array.isArray(args) || !Array.isArray(rules)) {
- throw new TypeError('parseArgs: args and rules should be array')
- }
-
- // fill rules[0]
- (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
-
- // max arguments length
- const count = rules.length - 1;
-
- // args.length must <= count
- if (args.length > count) {
- throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
- }
-
- // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
- for (let i = 1; i <= count; i++) {
- const rule = rules[i];
- if (Array.isArray(rule)) {
- if (rule.length !== i) {
- throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
- }
- if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
- throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
- }
- } else if (typeof rule !== 'function') {
- throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
- }
- }
-
- // Parse
- const rule = rules[args.length];
- let parsed;
- if (Array.isArray(rule)) {
- parsed = [...defaultValues];
- for (let i = 0; i < rule.length; i++) {
- parsed[rule[i]-1] = args[i];
- }
- } else {
- parsed = rule(args, defaultValues);
- }
- return parsed;
- }
- })();