您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Try to take over the world and make it a better place!
// ==UserScript== // @name Case360 script editor // @namespace http://tampermonkey.net/ // @version 0.4 // @author P. Wasilewski // @collaborator B. Bergs // @collaborator J. Elsen // @description Try to take over the world and make it a better place! // @match */sonora/Admin?op=i // @supportURL https://github.com/pwasilewski/Case360-editor // @updateURL https://raw.githubusercontent.com/pwasilewski/Case360-editor/master/Case360%20script%20editor.user.js // @downloadURL https://raw.githubusercontent.com/pwasilewski/Case360-editor/master/Case360%20script%20editor.user.js // @resource functions https://raw.githubusercontent.com/pwasilewski/Case360-editor/master/functions.json // @resource caseMethods https://raw.githubusercontent.com/pwasilewski/Case360-editor/master/methods.json // @resource staticFunctions https://raw.githubusercontent.com/pwasilewski/Case360-editor/master/static_functions.json // @grant GM_getResourceText // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js // @require https://openuserjs.org/src/libs/sizzle/GM_config.js // @grant GM_getValue // @grant GM_setValue // @grant GM_log // @grant GM_registerMenuCommand // ==/UserScript== const ACE_CONFIG = '{ "id":"GM_config", "title":"Case360 script editor config", "fields":{ "repository":{ "label":"Ace repository", "type":"text", "default":"ace/" }, "theme":{ "label":"Theme", "type":"select", "default":"piwi", "options":[ "ambiance", "chaos", "chrome", "clouds", "clouds_midnight", "cobalt", "crimson_editor", "dawn", "dracula", "dreamweaver", "eclipse", "github", "gob", "gruvbox", "idle_fingers", "iplastic", "katzenmilch", "kr_theme", "kuroir", "merbivore", "merbivore_soft", "monokai", "mono_industrial", "notepad", "pastel_on_dark", "piwi", "solarized_dark", "solarized_light", "sqlserver", "terminal", "textmate", "tomorrow", "tomorrow_night", "tomorrow_night_blue", "tomorrow_night_bright", "tomorrow_night_eighties", "twilight", "vibrant_ink", "xcode" ] }, "hline":{ "label":"Show horizontal line", "type":"checkbox", "default":true }, "showSnippets":{ "label":"Show snippets", "type":"checkbox", "default":true }, "showScriptCompleter":{ "label":"Show scripts completer", "type":"checkbox", "default":true }, "showFunctionCompleter":{ "label":"Show functions completer", "type":"checkbox", "default":true }, "showStaticFunctionCompleter":{ "label":"Show static functions completer", "type":"checkbox", "default":true }, "showMethodCompleter":{ "label":"Show methods completer", "type":"checkbox", "default":true } } }'; GM_config.init($.parseJSON (ACE_CONFIG)); GM_registerMenuCommand("Configure Case360 script editor", function(){ GM_config.open(); }); const CASE_EDITOR_ID = "Script"; const CASE_EDITOR_SELECTOR = "#" + CASE_EDITOR_ID; const URL_AJAX_COMPILE_SCRIPT = "CaseAjax?method=ScriptsHelper.compileScript"; const ACE_REPOSITORY = GM_config.get("repository"); const ACE_CDN_URL = ACE_REPOSITORY + "ace.js"; const ACE_LANG_CDN_URL = ACE_REPOSITORY + "ext-language_tools.js"; const ACE_EDITOR_ID = "editor"; const ACE_EDITOR_SELECTOR = "#" + ACE_EDITOR_ID; const ACE_MODE = "ace/mode/case"; const ACE_THEME = "ace/theme/" + GM_config.get("theme"); var scriptName = ""; var editor; var ace_loaded = false; var autocomplete_loaded = false; function GM_compileScript() { var ScriptSource = encodeURIComponent(removeCR(editor.getSession().getValue())); var nodeId = document.getElementById("scriptNodeId"); if (nodeId !== null) { var requestUrl = URL_AJAX_COMPILE_SCRIPT; var params = "nodeID=" + encodeURIComponent(nodeId.value) + "&scriptSource=" + ScriptSource; getASyncAjaxResponse(requestUrl, params, GM_getCompileResults); } } function GM_getCompileResults(objRequestHttp) { if (objRequestHttp.readyState == 4 || objRequestHttp.readyState == "complete") { if (objRequestHttp.status == 200) { var responseXML = objRequestHttp.responseXML; GM_showCompileResults(responseXML); } } } function GM_showCompileResults(responseXML) { if (responseXML !== null) { var errorMsg = responseXML.getElementsByTagName("errormsg"); var errorNode = errorMsg.item(0).firstChild; if (errorNode !== null) { var offsetPos = errorNode.nodeValue.lastIndexOf("near offset "); var offset = 0; var line = 0; var linePos = errorNode.nodeValue.lastIndexOf(" in line "); if (linePos == -1) { if (offsetPos != -1) { offset = parseInt(errorNode.nodeValue.substring(offsetPos + 12)); } line = 1; } else { if (offsetPos != -1) { offset = parseInt(errorNode.nodeValue.substring(offsetPos + 12, linePos)); } line = parseInt(errorNode.nodeValue.substring(linePos + 9)); } editor.getSession().setAnnotations([{ row: line-1, column: offset, text: errorNode.nodeValue, type: "error" }]); } else { editor.getSession().clearAnnotations(); } } } function GM_wait() { if(!ace_loaded) { window.setTimeout(GM_wait,100); } else { if(!autocomplete_loaded) { GM_appendAutoCompleteScript(); if(!autocomplete_loaded) { window.setTimeout(GM_wait,100); } } else { GM_initializeEditor(); } } } function GM_appendAceScript() { if($("script[src$='ace.js']").length === 0) { var ace = document.createElement('script'); ace.src = ACE_CDN_URL; ace.type = 'text/javascript'; ace.onload = function() { ace_loaded = true; }; document.getElementsByTagName('head')[0].appendChild(ace); } } function GM_appendAutoCompleteScript() { if($("script[src$='ext-language_tools.js']").length === 0) { var ace_lang = document.createElement('script'); ace_lang.src = ACE_LANG_CDN_URL; ace_lang.type = 'text/javascript'; ace_lang.onload = function() { autocomplete_loaded = true; }; document.getElementsByTagName('head')[0].appendChild(ace_lang); } } function GM_initializeEditor() { var GM_EDITOR = document.createElement('div'); GM_EDITOR.id = ACE_EDITOR_ID; $(GM_EDITOR).insertBefore(CASE_EDITOR_SELECTOR); var langTools = ace.require("ace/ext/language_tools"); editor = ace.edit(ACE_EDITOR_ID); editor.getSession().setMode(ACE_MODE); editor.setTheme(ACE_THEME); editor.setOptions({ enableBasicAutocompletion: true, enableSnippets : GM_config.get("showSnippets"), showPrintMargin : GM_config.get("hline") }); editor.commands.addCommand({ name: "replace", bindKey: {win: "Ctrl-S", mac: "Command-S"}, exec: function(editor) { updateSource(false); } }); var textarea = $('textarea[id="' + CASE_EDITOR_ID + '"]'); $(GM_EDITOR).width("100%"); $(GM_EDITOR).height($("#rightpane").height()-58); textarea.hide(); editor.getSession().setValue(textarea.val()); editor.getSession().on('change', function(e){ textarea.val(editor.getSession().getValue()); scriptChanges(); }); editor.getSession().selection.on('changeCursor', function(e, o) { compileScript(); GM_compileScript(); }); if(GM_config.get("showScriptCompleter")){ langTools.addCompleter(scriptsCompleter); } if(GM_config.get("showFunctionCompleter")){ langTools.addCompleter(functionsCompleter); } if(GM_config.get("showMethodCompleter")){ langTools.addCompleter(methodsCompleter); } if(GM_config.get("showStaticFunctionCompleter")){ langTools.addCompleter(staticFunctionsCompleter); } } $('#rightpane').bind('DOMSubtreeModified', function() { var script = $('#scriptlocation').val(); if($(CASE_EDITOR_SELECTOR).length !== 0 && scriptName != script) { scriptName = script; GM_appendAceScript(); GM_wait(); } }); // ### UTIL FUNCTIONS ### /** * Function to format the list of params and remove useless extension. * * E.g.: TST.updateActor(Object.PropertyManager.RepositoryObject.FormData actorFormdata, Object.PropertyManager.FmsRow updatedRow) * * @param {String} methodName The method name (e.g.: TST.updateActor) * @param {String} argumentsString The list of arguments (e.g.: Object.PropertyManager.RepositoryObject.FormData actorFormdata, Object.PropertyManager.FmsRow updatedRow) * * @return {String} The format string. (e.g.: TST.updateActor(FormData actorFormdata, FmsRow updatedRow)) */ function getSimplifiedMethodFormat(methodName, argumentsString) { var result = []; var simplified = []; var args = argumentsString.split(","); $.each( args, function( index, value ) { var splitedArg = value.match(/(.+)\s+(.+)/); if(splitedArg !== null) { simplified.push(splitedArg[1].split('.').pop() + ' ' + splitedArg[2]); } else { simplified.push(value.trim()); } }); result.push(methodName, "(", simplified.join(", ") ,")"); return result.join(""); } /** * Function to format the list of params allowing snippets and remove useless extension. * * E.g.: TST.updateActor(Object.PropertyManager.RepositoryObject.FormData actorFormdata, Object.PropertyManager.FmsRow updatedRow) * * @param {String} methodName The method name (e.g.: TST.updateActor) * @param {String} argumentsString The list of arguments (e.g.: Object.PropertyManager.RepositoryObject.FormData actorFormdata, Object.PropertyManager.FmsRow updatedRow) * * @return {String} The format string. (e.g.: TST.updateActor(${1:actorFormdata}, ${2:updatedRow})) */ function getSnippetMethodFormat(methodName, argumentsString) { var result = []; var snippet = []; var args = argumentsString.split(","); $.each( args, function( index, value ) { var splitedArg = value.match(/(.+)\s+(.+)/); if(splitedArg !== null) { snippet.push('${' + (index+1) + ':' + splitedArg[2] + '}'); } else { snippet.push('${' + (index+1) + ':' + value.trim() + '}'); } }); result.push(methodName, "(", snippet.join(", ") ,")"); return result.join(""); } function buildMethod(prefix, signature) { if( ! prefix.endsWith(".") ) { prefix += "."; } return prefix + signature; } function getMethodName(fullsignature) { return fullsignature.split('(')[0]; } function getMethodArgs(fullsignature) { return fullsignature.match(/\((.*?)\)/)[1]; } function countNumberDots(text) { return (text.match(/\./g)||[]).length; } function hasDots(text) { return countNumberDots(text) > 0; } function getPrefix(text) { return text.substring(0, text.lastIndexOf(".")); } // ### COMPLETERS ### /** * Fill in the completer with scripts like : * * DAMO.Log.log(...) or DAMO.Util.getEnvironment(...) * * Firstly, the function will be executed if and only if the prefix has at least 1 dot. * Secondly, it will make an ajax call to retrieve a list of script based on the prefix. * * If both conditions are met, the function will format the output and will add it to the completers. * If not, nothing will happen. */ var scriptsCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { if(! hasDots(prefix) ) return; prefix = getPrefix(prefix); $.ajax({ type: "GET", url: "CaseAjax?getmethods=" + prefix, dataType: "xml", success: function(xml){ var completers = []; if($(xml).find("responseXML").length == 1) { $(xml).find("class").each(function(){ $(this).children().each(function(){ var method = buildMethod(prefix, $(this).text()); var methodName = getMethodName(method); var args = getMethodArgs(method); simplifiedMethod = getSimplifiedMethodFormat(methodName, args); snippetMethod = getSnippetMethodFormat(methodName, args); completers.push({definition : simplifiedMethod, value : simplifiedMethod, snippet: snippetMethod, score : 1000, meta : 'script', type: 'script'}); }); }); } callback(null, completers); return ; } }); }, getDocTooltip: function(item) { if (item.type == 'script' && !item.docHTML) { var scriptIcon = "<span style=\"background: red;display: inline-block;border-radius: 50%;height: 13px;width: 13px;font-size: 11px;line-height: 14px;text-align: center;color: white;\"><i>s</i></span>"; item.docHTML = "<h5 style=\"margin:0px;\">" + scriptIcon + " <i>Scripts</i> - " + item.definition + "</h5>"; } } }; /** * Fill in the completer with functions like : * * optional(...) or doQuery(...) * * Firstly, the function will be executed if and only if the prefix doesn't contain any dots. * * If the condition is met, the function will format the output and will add it to the completers based on the ressource file. * If not, nothing will happen. */ var functionsCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { if( hasDots(prefix) ) return; var ressourceFile = GM_getResourceText("functions", "json"); callback(null, JSON.parse(ressourceFile).functions.map(function(func) { var method = func.signature; var methodName = getMethodName(method); var args = getMethodArgs(method); simplifiedMethod = getSimplifiedMethodFormat(methodName, args); snippetMethod = getSnippetMethodFormat(methodName, args); return {definition: func.definition, value: simplifiedMethod, snippet: snippetMethod, score: 1000, meta : 'function', type: 'function'}; })); }, getDocTooltip: function(item) { if (item.type == 'function' && !item.docHTML) { var functionIcon = "<span style=\"background: #7c7;display: inline-block;border-radius: 50%;height: 13px;width: 13px;font-size: 11px;line-height: 14px;text-align: center;color: white;\"><i>f</i></span>"; item.docHTML = "<h5 style=\"margin:0px;\">" + functionIcon + " <i>Function</i> - " + item.value + "</h5> <p style=\"margin:0px;word-wrap:break-word;\">" + item.definition + "</p>"; } } }; /** * Fill in the completer with Case360's built-in methods like : * * prefix.saveChanges(...) or prefix.add(...) * * Firstly, the function will be executed if and only if the prefix has at least 1 dot. * Secondly, it checks if the user is not looking for a case scripts. (e.g.: DAMO.Log.) * * If both conditions are met, the function will format the output and will add it to the completers based on the ressource file. * If not, nothing will happen. */ var methodsCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { if(! hasDots(prefix) ) return; prefix = getPrefix(prefix); var isScriptSearch = false; $.ajax({ type: "GET", url: "CaseAjax?getmethods=" + prefix, dataType: "xml", async: false, success: function(xml){ isScriptSearch = $(xml).find("responseXML").length == 1; } }); if(isScriptSearch) return; var ressourceFile = GM_getResourceText("caseMethods", "json"); callback(null, JSON.parse(ressourceFile).methods.map(function(met) { var method = buildMethod(prefix, met.signature); var methodName = getMethodName(method); var args = getMethodArgs(method); simplifiedMethod = getSimplifiedMethodFormat(methodName, args); snippetMethod = getSnippetMethodFormat(methodName, args); return {definition: met.definition, value: simplifiedMethod, snippet: snippetMethod, score: 1000, meta : met.meta, type: 'method'}; })); }, getDocTooltip: function(item) { if (item.type == 'method' && !item.docHTML) { var methodIcon = "<span style=\"background: #CC00CC;display: inline-block;border-radius: 50%;height: 13px;width: 13px;font-size: 11px;line-height: 14px;text-align: center;color: white;\"><i>m</i></span>"; item.docHTML = "<h5 style=\"margin:0px;\">" + methodIcon + " <i>" + item.meta + "</i> - " + item.value + "</h5> <p style=\"margin:0px;word-wrap:break-word;\">" + item.definition + "</p>"; } } }; /** * Fill in the completer with static functions/methods like : * * WorkItem.find(...) or User.find(...) * * Firstly, the function will be executed if and only if the prefix has 1 dot. * Secondly, it checks if the prefix exists as key in the ressource file. * * If both conditions are met, the function will format the output and will add it to the completers. * If not, nothing will happen. */ var staticFunctionsCompleter = { getCompletions: function(editor, session, pos, prefix, callback) { if( countNumberDots(prefix) !== 1 ) return; prefix = getPrefix(prefix); var ressourceFile = GM_getResourceText("staticFunctions", "json"); var jsonString = JSON.parse(ressourceFile); if(jsonString.hasOwnProperty(prefix.toLowerCase())) { callback(null, jsonString[prefix.toLowerCase()].map(function(func) { var method = buildMethod(prefix, func.signature); var methodName = getMethodName(method); var args = getMethodArgs(method); simplifiedMethod = getSimplifiedMethodFormat(methodName, args); snippetMethod = getSnippetMethodFormat(methodName, args); return {definition: func.definition, value: simplifiedMethod, snippet: snippetMethod, score: 2000, meta : func.meta, type: 'staticFunction'}; })); } }, getDocTooltip: function(item) { if (item.type == 'staticFunction' && !item.docHTML) { var functionIcon = "<span style=\"background: #ffae19;display: inline-block;border-radius: 50%;height: 13px;width: 13px;font-size: 11px;line-height: 14px;text-align: center;color: white;\"><i>f</i></span>"; item.docHTML = "<h5 style=\"margin:0px;\">" + functionIcon + " <i>" + item.meta + "</i> - " + item.value + "</h5> <p style=\"margin:0px;word-wrap:break-word;\">" + item.definition + "</p>"; } } };