diff --git a/pom.xml b/pom.xml index a66042a..67590ff 100644 --- a/pom.xml +++ b/pom.xml @@ -22,8 +22,9 @@ 1.1.16 2.11.2 3.1.0 - 1.6 - 1.8 + 1.6 + 1.8 + 1.3 2.6 28.2-jre 1.11.3 @@ -82,12 +83,17 @@ org.apache.commons commons-text - ${apache.commons.text.verion} + ${apache.commons.text.version} org.apache.commons commons-csv - ${apache.commons.csv.verion} + ${apache.commons.csv.version} + + + org.apache.commons + commons-exec + ${apache.commons.exec.version} diff --git a/spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/ScriptExecutor.java b/spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/ScriptExecutor.java new file mode 100644 index 0000000..ed6016f --- /dev/null +++ b/spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/ScriptExecutor.java @@ -0,0 +1,81 @@ +package org.spiderflow.core.executor.shape; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spiderflow.context.SpiderContext; +import org.spiderflow.core.io.ScriptResponse; +import org.spiderflow.core.service.ScriptService; +import org.spiderflow.core.utils.ExpressionUtils; +import org.spiderflow.executor.ShapeExecutor; +import org.spiderflow.model.SpiderNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.util.Map; + +@Component +public class ScriptExecutor implements ShapeExecutor { + + public static final String RESULT_VARIABLE = "scriptVariable"; + + public static final String SCRIPT_TIMEOUT = "scriptTimeout"; + + public static final String SCRIPT_NAME = "scriptName"; + + public static final String SCRIPT_FILE = "scriptFile"; + + public static final String SCRIPT_CHARSET = "scriptCharset"; + + public static final String SCRIPT_PARAMETER = "scriptParameter"; + + private static Logger logger = LoggerFactory.getLogger(ScriptExecutor.class); + + @Autowired + private ScriptService scriptService; + + @Override + public String supportShape() { + return "script"; + } + + @Override + public void execute(SpiderNode node, SpiderContext context, Map variables) { + String scriptName = node.getStringJsonValue(SCRIPT_NAME); + String scriptFile = node.getStringJsonValue(SCRIPT_FILE); + String parameter = node.getStringJsonValue(SCRIPT_PARAMETER); + int timeout = NumberUtils.toInt(node.getStringJsonValue(SCRIPT_TIMEOUT,"-1"),-1); + if(StringUtils.isBlank(scriptName) || StringUtils.isBlank(scriptFile)){ + logger.info("脚本名和脚本文件名不能为空!"); + return; + } + File file = scriptService.getScriptFile(scriptName, scriptFile); + if(!file.exists()){ + logger.info("在脚本{}中找不到脚本文件{}",scriptName,scriptFile); + return; + } + String charset = node.getStringJsonValue(SCRIPT_CHARSET); + String variable = node.getStringJsonValue(RESULT_VARIABLE); + if(parameter != null){ + try { + Object value = ExpressionUtils.execute(parameter,variables); + logger.info("脚本参数:{}",value); + if(value != null){ + parameter = value.toString(); + } + } catch (Exception e) { + logger.error("获取脚本参数出错",e); + } + } + ScriptResponse response = scriptService.execute(scriptName, scriptFile, parameter, timeout); + if(StringUtils.isNotBlank(charset)){ + response.charset(charset); + } + if(StringUtils.isNotBlank(variable)){ + variables.put(variable,response); + } + + } +} diff --git a/spider-flow-core/src/main/java/org/spiderflow/core/io/ScriptResponse.java b/spider-flow-core/src/main/java/org/spiderflow/core/io/ScriptResponse.java new file mode 100644 index 0000000..9f16ff2 --- /dev/null +++ b/spider-flow-core/src/main/java/org/spiderflow/core/io/ScriptResponse.java @@ -0,0 +1,57 @@ +package org.spiderflow.core.io; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; + +public class ScriptResponse { + + private int exitValue; + + private ByteArrayOutputStream outputStream; + + private ByteArrayOutputStream errorStream; + + private String defaultCharset; + + private Throwable throwable; + + public ScriptResponse(int exitValue, ByteArrayOutputStream outputStream, ByteArrayOutputStream errorStream) { + this.exitValue = exitValue; + this.outputStream = outputStream; + this.errorStream = errorStream; + } + + public ScriptResponse(int exitValue, ByteArrayOutputStream outputStream, ByteArrayOutputStream errorStream,Throwable throwable) { + this(exitValue,outputStream,errorStream); + this.throwable = throwable; + } + + public int getExitValue() { + return exitValue; + } + + public ScriptResponse charset(String charset){ + this.defaultCharset = charset; + return this; + } + + public String getValue() throws UnsupportedEncodingException { + if(errorStream == null){ + return null; + } + return defaultCharset == null ? outputStream.toString() : outputStream.toString(defaultCharset); + } + + public String getError() throws UnsupportedEncodingException { + if(errorStream == null){ + return null; + } + return defaultCharset == null ? errorStream.toString() : errorStream.toString(defaultCharset); + } + + public String getStack(){ + return this.throwable == null ? null : ExceptionUtils.getStackTrace(this.throwable); + } +} diff --git a/spider-flow-core/src/main/java/org/spiderflow/core/model/ScriptFile.java b/spider-flow-core/src/main/java/org/spiderflow/core/model/ScriptFile.java new file mode 100644 index 0000000..e0f31d4 --- /dev/null +++ b/spider-flow-core/src/main/java/org/spiderflow/core/model/ScriptFile.java @@ -0,0 +1,49 @@ +package org.spiderflow.core.model; + +public class ScriptFile { + + private String name; + + private String path; + + private boolean directory; + + public ScriptFile(String name, String path, boolean directory) { + this.name = name; + this.path = path; + this.directory = directory; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isDirectory() { + return directory; + } + + public void setDirectory(boolean directory) { + this.directory = directory; + } + + @Override + public String toString() { + return "ScriptFile{" + + "name='" + name + '\'' + + ", path='" + path + '\'' + + ", directory=" + directory + + '}'; + } +} diff --git a/spider-flow-core/src/main/java/org/spiderflow/core/model/TreeNode.java b/spider-flow-core/src/main/java/org/spiderflow/core/model/TreeNode.java new file mode 100644 index 0000000..0c4a176 --- /dev/null +++ b/spider-flow-core/src/main/java/org/spiderflow/core/model/TreeNode.java @@ -0,0 +1,38 @@ +package org.spiderflow.core.model; + +import java.util.ArrayList; +import java.util.List; + +public class TreeNode { + + private T node; + + private List> children; + + public TreeNode(T node) { + this.node = node; + } + + public T getNode() { + return node; + } + + public void setNode(T node) { + this.node = node; + } + + public List> getChildren() { + return children; + } + + public void setChildren(List> children) { + this.children = children; + } + + public void addNode(TreeNode node){ + if(this.children == null){ + this.children = new ArrayList<>(); + } + this.children.add(node); + } +} diff --git a/spider-flow-core/src/main/java/org/spiderflow/core/service/ScriptService.java b/spider-flow-core/src/main/java/org/spiderflow/core/service/ScriptService.java new file mode 100644 index 0000000..d8fddf6 --- /dev/null +++ b/spider-flow-core/src/main/java/org/spiderflow/core/service/ScriptService.java @@ -0,0 +1,194 @@ +package org.spiderflow.core.service; + +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.PumpStreamHandler; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spiderflow.core.io.ScriptResponse; +import org.spiderflow.core.model.ScriptFile; +import org.spiderflow.core.model.TreeNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Service +public class ScriptService { + + @Value("${spider.workspace}") + private String workspace; + + @Autowired + private static Logger logger = LoggerFactory.getLogger(ScriptService.class); + + /** + * 保存脚本 + * @param scriptName 脚本名称 + * @param filename 文件名 + * @param content 脚本内容 + * @throws IOException + */ + public boolean saveScript(String scriptName,String filename,String content){ + try { + FileUtils.write(getScriptFile(scriptName,filename),content,"UTF-8"); + return true; + } catch (Exception e) { + return false; + } + } + + public ScriptResponse execute(String scriptName, String filename, String parameters, int timeout) { + String command = ""; + if(filename.endsWith(".py")){ + command = "python "; + }else if(filename.endsWith(".js")){ + command = "node "; + }else{ + return new ScriptResponse(-2,null,null,null); + } + command+= filename; + if(parameters != null){ + command += " " + parameters; + } + logger.info("准备执行命令:{}",command); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); + try{ + File directory = getScriptDirectory(scriptName); + CommandLine commandLine = CommandLine.parse(command); + DefaultExecutor executor = new DefaultExecutor(); + ExecuteWatchdog watchdog = new ExecuteWatchdog(timeout); + executor.setWatchdog(watchdog); + executor.setWorkingDirectory(directory); + PumpStreamHandler handler = new PumpStreamHandler(outputStream, errorStream); + executor.setStreamHandler(handler); + int value = executor.execute(commandLine); + logger.info("命令{}执行完毕,exitValue={}",command,value); + return new ScriptResponse(value,outputStream,errorStream); + } catch (Exception e) { + logger.error("执行脚本失败",e); + return new ScriptResponse(-1,outputStream,errorStream,e); + } + } + + public File getScriptFile(String scriptName,String filename){ + File file = new File(getScriptDirectory(scriptName),filename); + File dir = file.getParentFile(); + if(!dir.exists()){ + dir.mkdirs(); + } + return file; + } + + private File getScriptDirectory(String scriptName){ + File directory = new File(new File(workspace,"scripts"),scriptName); + if(!directory.exists()){ + directory.mkdirs(); + } + return directory; + } + + public String read(String scriptName,String filename) throws IOException { + File file = getScriptFile(scriptName, filename); + if(!file.exists()){ + return null; + } + return FileUtils.readFileToString(file,"UTF-8"); + } + + public boolean rename(String scriptName,String oldName,String newName){ + File directory = getScriptDirectory(scriptName); + return new File(directory, oldName).renameTo(new File(directory,newName)); + } + + /** + * 删除脚本 + * @param scriptName 脚本名称 + * @param filename 文件名 + */ + public boolean removeScript(String scriptName,String filename) { + try { + File scriptDirectory = getScriptDirectory(scriptName); + if(StringUtils.isBlank(filename)){ + FileUtils.deleteDirectory(scriptDirectory); + }else{ + File file = getScriptFile(scriptName, filename); + if(!file.exists()){ + return false; + } + if(file.isDirectory()){ + FileUtils.deleteDirectory(file); + return true; + } + return file.delete(); + } + } catch (IOException e) { + return false; + } + return true; + } + + public List listScript(){ + String[] files = new File(workspace, "scripts").list(); + if(files != null){ + Arrays.sort(files); + return Arrays.asList(files); + }else{ + return Collections.emptyList(); + } + } + + public boolean createScript(String scriptName){ + File file = new File(new File(workspace,"scripts"),scriptName); + if(file.exists()){ + return false; + } + return file.mkdirs(); + } + + public boolean createScriptFile(String scriptName,String file,boolean directory){ + File dir = getScriptDirectory(scriptName); + File f = new File(dir, file); + File parentFile = f.getParentFile(); + parentFile.mkdirs(); + try { + return directory ? f.mkdirs() : f.createNewFile(); + } catch (IOException e) { + return false; + } + } + + /** + * 循环遍历 + */ + public TreeNode list(String scriptName){ + return list(getScriptDirectory(scriptName),null,""); + } + + private TreeNode list(File file,TreeNode node, String parentName){ + ScriptFile scriptFile = new ScriptFile(file.getName(),parentName,file.isDirectory()); + TreeNode current = new TreeNode<>(scriptFile); + if(node != null){ + node.addNode(current); + } + if(file.isDirectory()){ + File[] files = file.listFiles(); + if(files != null){ + for (File child : files) { + list(child,current,(parentName.isEmpty() ? "" : parentName + File.separator) + child.getName()); + } + } + } + return current; + } +} diff --git a/spider-flow-web/src/main/java/org/spiderflow/controller/ScriptController.java b/spider-flow-web/src/main/java/org/spiderflow/controller/ScriptController.java new file mode 100644 index 0000000..ce186b9 --- /dev/null +++ b/spider-flow-web/src/main/java/org/spiderflow/controller/ScriptController.java @@ -0,0 +1,65 @@ +package org.spiderflow.controller; + +import org.spiderflow.core.io.ScriptResponse; +import org.spiderflow.core.model.ScriptFile; +import org.spiderflow.core.model.TreeNode; +import org.spiderflow.core.service.ScriptService; +import org.spiderflow.model.JsonBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; + +@RestController +@RequestMapping("/script") +public class ScriptController { + + @Autowired + private ScriptService scriptService; + + @RequestMapping("/list") + public JsonBean> scripts(){ + return new JsonBean<>(scriptService.listScript()); + } + + @RequestMapping("/files") + public JsonBean> files(String script){ + return new JsonBean<>(scriptService.list(script)); + } + @RequestMapping("/create") + public JsonBean create(String scriptName){ + return new JsonBean<>(scriptService.createScript(scriptName)); + } + + @RequestMapping("/create/file") + public JsonBean createFile(String scriptName,String file,String dir){ + return new JsonBean<>(scriptService.createScriptFile(scriptName,file,"1".equalsIgnoreCase(dir))); + } + + @RequestMapping("/remove/file") + public JsonBean remove(String scriptName,String file){ + return new JsonBean<>(scriptService.removeScript(scriptName,file)); + } + + @RequestMapping("/rename/file") + public JsonBean rename(String scriptName,String file,String newFile){ + return new JsonBean<>(scriptService.rename(scriptName,file,newFile)); + } + + @RequestMapping("/read") + public JsonBean read(String scriptName,String file) throws IOException { + return new JsonBean<>(scriptService.read(scriptName,file)); + } + + @RequestMapping("/save") + public JsonBean save(String scriptName,String file,String content){ + return new JsonBean<>(scriptService.saveScript(scriptName,file,content)); + } + + @RequestMapping("/test") + public JsonBean test(String scriptName, String file, String parameter, Integer timeout) { + return new JsonBean<>(scriptService.execute(scriptName,file,parameter,timeout == null ? -1 : timeout)); + } +} diff --git a/spider-flow-web/src/main/resources/static/index.html b/spider-flow-web/src/main/resources/static/index.html index c6464d3..c4e6de1 100644 --- a/spider-flow-web/src/main/resources/static/index.html +++ b/spider-flow-web/src/main/resources/static/index.html @@ -38,6 +38,7 @@
  • 全局变量
  • 自定义函数
  • 数据源管理
  • +
  • 脚本管理
  • diff --git a/spider-flow-web/src/main/resources/static/js/codemirror/python.js b/spider-flow-web/src/main/resources/static/js/codemirror/python.js new file mode 100644 index 0000000..6a5cd0d --- /dev/null +++ b/spider-flow-web/src/main/resources/static/js/codemirror/python.js @@ -0,0 +1,399 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } + + var wordOperators = wordRegexp(["and", "or", "not", "is"]); + var commonKeywords = ["as", "assert", "break", "class", "continue", + "def", "del", "elif", "else", "except", "finally", + "for", "from", "global", "if", "import", + "lambda", "pass", "raise", "return", + "try", "while", "with", "yield", "in"]; + var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr", + "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", + "enumerate", "eval", "filter", "float", "format", "frozenset", + "getattr", "globals", "hasattr", "hash", "help", "hex", "id", + "input", "int", "isinstance", "issubclass", "iter", "len", + "list", "locals", "map", "max", "memoryview", "min", "next", + "object", "oct", "open", "ord", "pow", "property", "range", + "repr", "reversed", "round", "set", "setattr", "slice", + "sorted", "staticmethod", "str", "sum", "super", "tuple", + "type", "vars", "zip", "__import__", "NotImplemented", + "Ellipsis", "__debug__"]; + CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins)); + + function top(state) { + return state.scopes[state.scopes.length - 1]; + } + + CodeMirror.defineMode("python", function(conf, parserConf) { + var ERRORCLASS = "error"; + + var delimiters = parserConf.delimiters || parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.\\]/; + // (Backwards-compatibility with old, cumbersome config system) + var operators = [parserConf.singleOperators, parserConf.doubleOperators, parserConf.doubleDelimiters, parserConf.tripleDelimiters, + parserConf.operators || /^([-+*/%\/&|^]=?|[<>=]+|\/\/=?|\*\*=?|!=|[~!@]|\.\.\.)/] + for (var i = 0; i < operators.length; i++) if (!operators[i]) operators.splice(i--, 1) + + var hangingIndent = parserConf.hangingIndent || conf.indentUnit; + + var myKeywords = commonKeywords, myBuiltins = commonBuiltins; + if (parserConf.extra_keywords != undefined) + myKeywords = myKeywords.concat(parserConf.extra_keywords); + + if (parserConf.extra_builtins != undefined) + myBuiltins = myBuiltins.concat(parserConf.extra_builtins); + + var py3 = !(parserConf.version && Number(parserConf.version) < 3) + if (py3) { + // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator + var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/; + myKeywords = myKeywords.concat(["nonlocal", "False", "True", "None", "async", "await"]); + myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]); + var stringPrefixes = new RegExp("^(([rbuf]|(br)|(fr))?('{3}|\"{3}|['\"]))", "i"); + } else { + var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/; + myKeywords = myKeywords.concat(["exec", "print"]); + myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile", + "file", "intern", "long", "raw_input", "reduce", "reload", + "unichr", "unicode", "xrange", "False", "True", "None"]); + var stringPrefixes = new RegExp("^(([rubf]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); + } + var keywords = wordRegexp(myKeywords); + var builtins = wordRegexp(myBuiltins); + + // tokenizers + function tokenBase(stream, state) { + var sol = stream.sol() && state.lastToken != "\\" + if (sol) state.indent = stream.indentation() + // Handle scope changes + if (sol && top(state).type == "py") { + var scopeOffset = top(state).offset; + if (stream.eatSpace()) { + var lineOffset = stream.indentation(); + if (lineOffset > scopeOffset) + pushPyScope(state); + else if (lineOffset < scopeOffset && dedent(stream, state) && stream.peek() != "#") + state.errorToken = true; + return null; + } else { + var style = tokenBaseInner(stream, state); + if (scopeOffset > 0 && dedent(stream, state)) + style += " " + ERRORCLASS; + return style; + } + } + return tokenBaseInner(stream, state); + } + + function tokenBaseInner(stream, state) { + if (stream.eatSpace()) return null; + + // Handle Comments + if (stream.match(/^#.*/)) return "comment"; + + // Handle Number Literals + if (stream.match(/^[0-9\.]/, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^[\d_]*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } + if (stream.match(/^[\d_]+\.\d*/)) { floatLiteral = true; } + if (stream.match(/^\.\d+/)) { floatLiteral = true; } + if (floatLiteral) { + // Float literals may be "imaginary" + stream.eat(/J/i); + return "number"; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^0x[0-9a-f_]+/i)) intLiteral = true; + // Binary + if (stream.match(/^0b[01_]+/i)) intLiteral = true; + // Octal + if (stream.match(/^0o[0-7_]+/i)) intLiteral = true; + // Decimal + if (stream.match(/^[1-9][\d_]*(e[\+\-]?[\d_]+)?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + if (stream.match(/^0(?![\dx])/i)) intLiteral = true; + if (intLiteral) { + // Integer literals may be "long" + stream.eat(/L/i); + return "number"; + } + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + var isFmtString = stream.current().toLowerCase().indexOf('f') !== -1; + if (!isFmtString) { + state.tokenize = tokenStringFactory(stream.current(), state.tokenize); + return state.tokenize(stream, state); + } else { + state.tokenize = formatStringFactory(stream.current(), state.tokenize); + return state.tokenize(stream, state); + } + } + + for (var i = 0; i < operators.length; i++) + if (stream.match(operators[i])) return "operator" + + if (stream.match(delimiters)) return "punctuation"; + + if (state.lastToken == "." && stream.match(identifiers)) + return "property"; + + if (stream.match(keywords) || stream.match(wordOperators)) + return "keyword"; + + if (stream.match(builtins)) + return "builtin"; + + if (stream.match(/^(self|cls)\b/)) + return "variable-2"; + + if (stream.match(identifiers)) { + if (state.lastToken == "def" || state.lastToken == "class") + return "def"; + return "variable"; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function formatStringFactory(delimiter, tokenOuter) { + while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) + delimiter = delimiter.substr(1); + + var singleline = delimiter.length == 1; + var OUTCLASS = "string"; + + function tokenNestedExpr(depth) { + return function(stream, state) { + var inner = tokenBaseInner(stream, state) + if (inner == "punctuation") { + if (stream.current() == "{") { + state.tokenize = tokenNestedExpr(depth + 1) + } else if (stream.current() == "}") { + if (depth > 1) state.tokenize = tokenNestedExpr(depth - 1) + else state.tokenize = tokenString + } + } + return inner + } + } + + function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"\{\}\\]/); + if (stream.eat("\\")) { + stream.next(); + if (singleline && stream.eol()) + return OUTCLASS; + } else if (stream.match(delimiter)) { + state.tokenize = tokenOuter; + return OUTCLASS; + } else if (stream.match('{{')) { + // ignore {{ in f-str + return OUTCLASS; + } else if (stream.match('{', false)) { + // switch to nested mode + state.tokenize = tokenNestedExpr(0) + if (stream.current()) return OUTCLASS; + else return state.tokenize(stream, state) + } else if (stream.match('}}')) { + return OUTCLASS; + } else if (stream.match('}')) { + // single } in f-string is an error + return ERRORCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) + return ERRORCLASS; + else + state.tokenize = tokenOuter; + } + return OUTCLASS; + } + tokenString.isString = true; + return tokenString; + } + + function tokenStringFactory(delimiter, tokenOuter) { + while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) + delimiter = delimiter.substr(1); + + var singleline = delimiter.length == 1; + var OUTCLASS = "string"; + + function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"\\]/); + if (stream.eat("\\")) { + stream.next(); + if (singleline && stream.eol()) + return OUTCLASS; + } else if (stream.match(delimiter)) { + state.tokenize = tokenOuter; + return OUTCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) + return ERRORCLASS; + else + state.tokenize = tokenOuter; + } + return OUTCLASS; + } + tokenString.isString = true; + return tokenString; + } + + function pushPyScope(state) { + while (top(state).type != "py") state.scopes.pop() + state.scopes.push({offset: top(state).offset + conf.indentUnit, + type: "py", + align: null}) + } + + function pushBracketScope(stream, state, type) { + var align = stream.match(/^([\s\[\{\(]|#.*)*$/, false) ? null : stream.column() + 1 + state.scopes.push({offset: state.indent + hangingIndent, + type: type, + align: align}) + } + + function dedent(stream, state) { + var indented = stream.indentation(); + while (state.scopes.length > 1 && top(state).offset > indented) { + if (top(state).type != "py") return true; + state.scopes.pop(); + } + return top(state).offset != indented; + } + + function tokenLexer(stream, state) { + if (stream.sol()) state.beginningOfLine = true; + + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle decorators + if (state.beginningOfLine && current == "@") + return stream.match(identifiers, false) ? "meta" : py3 ? "operator" : ERRORCLASS; + + if (/\S/.test(current)) state.beginningOfLine = false; + + if ((style == "variable" || style == "builtin") + && state.lastToken == "meta") + style = "meta"; + + // Handle scope changes. + if (current == "pass" || current == "return") + state.dedent += 1; + + if (current == "lambda") state.lambda = true; + if (current == ":" && !state.lambda && top(state).type == "py") + pushPyScope(state); + + if (current.length == 1 && !/string|comment/.test(style)) { + var delimiter_index = "[({".indexOf(current); + if (delimiter_index != -1) + pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); + + delimiter_index = "])}".indexOf(current); + if (delimiter_index != -1) { + if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent + else return ERRORCLASS; + } + } + if (state.dedent > 0 && stream.eol() && top(state).type == "py") { + if (state.scopes.length > 1) state.scopes.pop(); + state.dedent -= 1; + } + + return style; + } + + var external = { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + scopes: [{offset: basecolumn || 0, type: "py", align: null}], + indent: basecolumn || 0, + lastToken: null, + lambda: false, + dedent: 0 + }; + }, + + token: function(stream, state) { + var addErr = state.errorToken; + if (addErr) state.errorToken = false; + var style = tokenLexer(stream, state); + + if (style && style != "comment") + state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style; + if (style == "punctuation") style = null; + + if (stream.eol() && state.lambda) + state.lambda = false; + return addErr ? style + " " + ERRORCLASS : style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) + return state.tokenize.isString ? CodeMirror.Pass : 0; + + var scope = top(state), closing = scope.type == textAfter.charAt(0) + if (scope.align != null) + return scope.align - (closing ? 1 : 0) + else + return scope.offset - (closing ? hangingIndent : 0) + }, + + electricInput: /^\s*[\}\]\)]$/, + closeBrackets: {triples: "'\""}, + lineComment: "#", + fold: "indent" + }; + return external; + }); + + CodeMirror.defineMIME("text/x-python", "python"); + + var words = function(str) { return str.split(" "); }; + + CodeMirror.defineMIME("text/x-cython", { + name: "python", + extra_keywords: words("by cdef cimport cpdef ctypedef enum except "+ + "extern gil include nogil property public "+ + "readonly struct union DEF IF ELIF ELSE") + }); + +}); diff --git a/spider-flow-web/src/main/resources/static/js/editor.js b/spider-flow-web/src/main/resources/static/js/editor.js index 408a53a..d3f168c 100644 --- a/spider-flow-web/src/main/resources/static/js/editor.js +++ b/spider-flow-web/src/main/resources/static/js/editor.js @@ -587,6 +587,11 @@ $(function(){ image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD1ElEQVRYR7WXUXIaRxCGv4YqR2+WH2zxZukEUU5gdAKjExhfILAnMD7BohMYn8D4BEYnsHQCobfd5CHkDVIFnerZmc0sLAJjMlVUSbszPX93//13r7DnOk319JcGb0TpAOfRzyxMw0+F8WLF7SyR2T6mZdemVqrnCB+A7sZe5dE9E17X2BmhfMwSMXBb11YAzmPhg0DfnVYeFcYKkz8SGddZfJVqR6At0ClBCYP5ipttEakF4C//JnCpyt/AIE9kuCta8fuzVA34QITnCncL5aoOxAaAl6leNgS7/FSVrwvo7pvPdYDOERiJ8FZhtlKu/kzkLt5XAeA9f/CX3+SJFOH/yXWW6lCEnoFYKBexQyWAtbAf7fKAPQJRSUcJoNygfM0TsVI7+jpLdWzp8NUxKAoI8KX2YIRbwPmhOd+F2HNiasREubASDQBGCO9USdbZ3kq1TRPNfpfb9Qsc8CavWfIY17sRudnk+XzJ/bozrVQHTleUz1kiXTFUJ8JfVudZIqZwbjkjwhevePZoulSuA4tLQ36/wjDvS+IjOkF4g3KVJTKpAT41nZgrL8TEoyF8UaVCvFaqbpOVooEQ4Ver57wvv7moCN8sZVKIU8eH9X2WyKiV6i4ALuIr5VpaqZb/BIWLOHGfJ3LpvXKAsr7YGRdGM2Bn1p3YBSDstzSYMYfWDIdQBQ9RbrNE2h6A9YLzLJFBlEcX4vX9uwA4e0NVs28ASs/iXLkN1uWKPFYayrEAWErFI6kQ0IAEXbC/rQmJchMIdRQA3nEHIJCrhq0mFt2ysxXt9TgpKAFsSUElHakaiE+m5XlfXhwlAua4cl9LwjpFi8l6LACBhBtl6EUo9Wo1isUlLkMj6BJmTaHnUuWrZlcVVMqwTog8gO+BG14tv7syjHTApNuGFhMVF7X/OLK/EJVSDNOsLxdR+7wz9aukw+u3CZUrIWsq0QqTzwmMnRRXX8aaUpb+1mbkgMHQZNa3zfEc+qG5mPiY/juJLuR6YvPjEjoN6LrIrIGzAWejGfn82uT7UDex1BHy0GfxxFVpxx6E03cTnbwv14de8tS5iJxOT3xkiyN+WJj4kP5/I5lyv4B2SOXmUApuYon7+89GIxr3Niau+rHcCFWAGC+U94eOaD7nn+xDxWaHFbSfHMuDp5V0wAxhkPfk5kciYWxXoedH/ErYYztPfpqd2GdZ8V1oa2oRQZjkPbGy21he1N4CNkMU453ycQ7DH/o0iy276QisQgq1q64wJ5SzZPla+WznDv44Xb/J0vIM2o1CmEwJT4NSuq5mqYLpCsb/wGRf3vwLgODoY+vqQ1gAAAAASUVORK5CYII=', title : '执行SQL', desc : '执行sql,需配置数据源,sql执行结果存于变量rs中。
    语句类型为:select,返回:List<Map<String,Object>>
    语句类型为:selectOne,返回:Map<String,Object>
    语句类型为:selectInt,返回:Integer
    语句类型为:insert、update、delete,返回:int,批量操作返回int数组
    sql中变量必须用 # # 包裹,如:#${title}#' + },{ + name : 'script', + image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADD0lEQVRYR92XTUhUURTH//8xyzYRJM2MuZCoFrk0iD4WFQiJRSsnF4GmIFHkvEdmUIEKRZjVm5EoDErcmS4rSgpzIX1ArcIWYSZt5vkBSUWM0zgn3nPe9NSx+XBeQRcGZuaej98999xzzyX+8aDl3xuUMonhcE54iBFdYX86thIAHk1aQbSko5SmzGld4c1UsksBBG2plFLOxxdC4krIz/N/kk8GsF9XOZTSyTICHk32gXiemCZ6NnjRMOJjJJmK8wDzXgchqNdVji+G+FsAgGA05kLNpJ8v7BA5BzCMewIiy+zQLIEjIYUD1rwzAEFpgaAKQOkSEEGbrrLVUYBkq08k5/8PUCV56Ofc4iisOAKeoLRTMBZS2GU37g3InZigd0LlYFFAymOCE9EIaqfP8ZtdLmuALZ2y5rvgFgR1AEbggk9v5HvDuDcoB0Xw2HTkQili6Isn3sBcBLVTzdQtiKwAim/Lpp+z6CZQDmA4KvBNqwzZjD4EUSnE5Qk/Lxa2S1HeavSR2APgVSyCY5PN/GgeTatCZpKEnoAYRWMXiAcF6+AbP86w5dytSR2JuyA+hGPYOaNyxpgrviFroy4zEocAvNQV7l4JQC+AoySe5efD9/kkvxjGSjRZH3bhNQTbRFA/ofKeBVbSLQXhr+iDmFf7fV1hddYA8ap2FcBZAGMkfCE/37qDcomCCxA80lUaKzVHoSbeVTRXvxdAh66weUU5kFAOyBkA18zfRKXh2PxKVIT8fGKCdsp2WxI26Qqv5+QUJCCCUgPBD6PTcWtywEVUhxQ2LHJSARc26n725LwOJCuvmfyX1SnIxEE6smbbBwzZGx5HbsN0YCwZRwE8AXljONIV7lgOyjEAtyabSZhVUFeY8LMYxBEAY69dwNMYMRx3+LvJFbzTVTY6tgUeTUpAfAJgNKAlSUNvuw+ciUBQOiBoWjYZUwBk/DCJAl32WzJewo0ELMs8ApmcIUtWsOQx4w1KgwgWNC+W+Fw+tk6d4uh8ZY+PlTxOk0XAjILRAwDGJzHsHfECgGwWngudX+XU9zD0knfOAAAAAElFTkSuQmCC', + title : '执行脚本', + desc : '单独执行脚本方法' },{ name : 'function', image : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAAgCAYAAACPb1E+AAAC9UlEQVRYR+2YP0zUUBjAf9+RKJPgQK6THptxERPiZOTUxEUj/tlMjCS62xoT3YARl5bJTXByBGcHISQmxhjQ6A6TPRg8JrnE62de6Z0Htne9XhMw4W1N3/u+X7//rwJgzeokygRQMs+HYSksBsr0liNrYrk6gTB3GMBiGNZ9W4al6OqiCOOqfFGYKkD1oIEVRkRwQw5l2FhyCWEMZdp3ZOqgARv6LU/1CDIPbxxZMg8rhmUxj5gcdHWw6khiJSh6+lVgAGXMd2S90/79H9czpOXpd+CsKk7FEe8fBab2whwCdeF6n/ISOFUPuLT1RFbSWLsnSMvTOVUmREJVM74tz1uVGov1C6tR9/rs2zJqefoL6FfYDpSy6SKdQDNDGkAI2yeqvKk4ci/GilMIk40aZ1w95OpIQVgWOKFQDZTLnUAzQbYCorz2HQlhW5flakmFVYFBVWYrjtiN9yEoLIkwkAa0a0jL0x8m4SLrxAKGGenqPMIDVbZrUNqfWE1Qk1AFqAc83HLkVZzrs0AGgKhQDwJG41xluVpGeB99SGKLtTx9i3LTJFUAC5u23MkFcsjTZwWYFjie5CrLUwNYRtnwHYkd+fbENHyr/eZG9als5AJphLSLqdZxL1BubzqyGFP3mkmXFNN74jtrMW8FRdgB7u4EfGiWHGXZd6QcA7iCcBEz1yQkXa7F3ID2wUeEY0HATJ9wRoVxo6SunI+N18gqCu8qtlzrVCNzaYtFVx8BVwJ4UYB5Ec4pfKrYciEOoOiqjXC1ptxv10pzcXccQBQC5VqdhaQkSGO5XN2dRWGWM13XySxKej1zBNmrBRvnYy25fyDIS1kWOdG49zNqseG92xPhcSRsqWuhgoXSn3jOFH3F71Kuaatha91RTpp7d0lhzYxQXQra3W66yO4AHL86vW+nNOpOofgI9JaZAzOAmt80p9tAmgFivlu5Aaw1ZoB2Nkglt/kHJNmSsf08lfBoU2pIy9W/14JuNCTs9e3olpRC1n8B+Qd0Dhp9ddQMugAAAABJRU5ErkJggg==', diff --git a/spider-flow-web/src/main/resources/static/js/ztree/jquery.ztree.all.min.js b/spider-flow-web/src/main/resources/static/js/ztree/jquery.ztree.all.min.js new file mode 100644 index 0000000..feabc33 --- /dev/null +++ b/spider-flow-web/src/main/resources/static/js/ztree/jquery.ztree.all.min.js @@ -0,0 +1,3 @@ +!function($){var settings={},roots={},caches={},_consts={className:{BUTTON:"button",LEVEL:"level",ICO_LOADING:"ico_loading",SWITCH:"switch",NAME:"node_name"},event:{NODECREATED:"ztree_nodeCreated",CLICK:"ztree_click",EXPAND:"ztree_expand",COLLAPSE:"ztree_collapse",ASYNC_SUCCESS:"ztree_async_success",ASYNC_ERROR:"ztree_async_error",REMOVE:"ztree_remove",SELECTED:"ztree_selected",UNSELECTED:"ztree_unselected"},id:{A:"_a",ICON:"_ico",SPAN:"_span",SWITCH:"_switch",UL:"_ul"},line:{ROOT:"root",ROOTS:"roots",CENTER:"center",BOTTOM:"bottom",NOLINE:"noline",LINE:"line"},folder:{OPEN:"open",CLOSE:"close",DOCU:"docu"},node:{CURSELECTED:"curSelectedNode"}},_setting={treeId:"",treeObj:null,view:{addDiyDom:null,autoCancelSelected:!0,dblClickExpand:!0,expandSpeed:"fast",fontCss:{},nodeClasses:{},nameIsHTML:!1,selectedMulti:!0,showIcon:!0,showLine:!0,showTitle:!0,txtSelectedEnable:!1},data:{key:{isParent:"isParent",children:"children",name:"name",title:"",url:"url",icon:"icon"},simpleData:{enable:!1,idKey:"id",pIdKey:"pId",rootPId:null},keep:{parent:!1,leaf:!1}},async:{enable:!1,contentType:"application/x-www-form-urlencoded",type:"post",dataType:"text",headers:{},xhrFields:{},url:"",autoParam:[],otherParam:[],dataFilter:null},callback:{beforeAsync:null,beforeClick:null,beforeDblClick:null,beforeRightClick:null,beforeMouseDown:null,beforeMouseUp:null,beforeExpand:null,beforeCollapse:null,beforeRemove:null,onAsyncError:null,onAsyncSuccess:null,onNodeCreated:null,onClick:null,onDblClick:null,onRightClick:null,onMouseDown:null,onMouseUp:null,onExpand:null,onCollapse:null,onRemove:null}},_initRoot=function(e){var t=data.getRoot(e);t||(t={},data.setRoot(e,t)),data.nodeChildren(e,t,[]),t.expandTriggerFlag=!1,t.curSelectedList=[],t.noSelection=!0,t.createdNodes=[],t.zId=0,t._ver=(new Date).getTime()},_initCache=function(e){var t=data.getCache(e);t||(t={},data.setCache(e,t)),t.nodes=[],t.doms=[]},_bindEvent=function(d){var e=d.treeObj,t=consts.event;e.bind(t.NODECREATED,function(e,t,n){tools.apply(d.callback.onNodeCreated,[e,t,n])}),e.bind(t.CLICK,function(e,t,n,o,a){tools.apply(d.callback.onClick,[t,n,o,a])}),e.bind(t.EXPAND,function(e,t,n){tools.apply(d.callback.onExpand,[e,t,n])}),e.bind(t.COLLAPSE,function(e,t,n){tools.apply(d.callback.onCollapse,[e,t,n])}),e.bind(t.ASYNC_SUCCESS,function(e,t,n,o){tools.apply(d.callback.onAsyncSuccess,[e,t,n,o])}),e.bind(t.ASYNC_ERROR,function(e,t,n,o,a,r){tools.apply(d.callback.onAsyncError,[e,t,n,o,a,r])}),e.bind(t.REMOVE,function(e,t,n){tools.apply(d.callback.onRemove,[e,t,n])}),e.bind(t.SELECTED,function(e,t,n){tools.apply(d.callback.onSelected,[t,n])}),e.bind(t.UNSELECTED,function(e,t,n){tools.apply(d.callback.onUnSelected,[t,n])})},_unbindEvent=function(e){var t=e.treeObj,n=consts.event;t.unbind(n.NODECREATED).unbind(n.CLICK).unbind(n.EXPAND).unbind(n.COLLAPSE).unbind(n.ASYNC_SUCCESS).unbind(n.ASYNC_ERROR).unbind(n.REMOVE).unbind(n.SELECTED).unbind(n.UNSELECTED)},_eventProxy=function(e){var t=e.target,n=data.getSetting(e.data.treeId),o="",a=null,r="",d="",i=null,s=null,l=null;if(tools.eqs(e.type,"mousedown")?d="mousedown":tools.eqs(e.type,"mouseup")?d="mouseup":tools.eqs(e.type,"contextmenu")?d="contextmenu":tools.eqs(e.type,"click")?tools.eqs(t.tagName,"span")&&null!==t.getAttribute("treeNode"+consts.id.SWITCH)?(o=tools.getNodeMainDom(t).id,r="switchNode"):(l=tools.getMDom(n,t,[{tagName:"a",attrName:"treeNode"+consts.id.A}]))&&(o=tools.getNodeMainDom(l).id,r="clickNode"):tools.eqs(e.type,"dblclick")&&(d="dblclick",(l=tools.getMDom(n,t,[{tagName:"a",attrName:"treeNode"+consts.id.A}]))&&(o=tools.getNodeMainDom(l).id,r="switchNode")),0=r.length&&(n=-1):(r=data.nodeChildren(e,t,[]),n=-1),0=u.length-n.length)&&(a=-1);for(var p=0,f=n.length;p/g,">");e.push("",a,"")},makeDOMNodeLine:function(e,t,n){e.push("")},makeDOMNodeMainAfter:function(e,t,n){e.push("")},makeDOMNodeMainBefore:function(e,t,n){e.push("
  • ")},makeDOMNodeNameAfter:function(e,t,n){e.push("")},makeDOMNodeNameBefore:function(e,t,n){var o=data.nodeTitle(t,n),a=view.makeNodeUrl(t,n),r=view.makeNodeFontCss(t,n),d=view.makeNodeClasses(t,n),i=[];for(var s in r)i.push(s,":",r[s],";");e.push("/g,">"),"'"),e.push(">")},makeNodeFontCss:function(e,t){var n=tools.apply(e.view.fontCss,[e.treeId,t],e.view.fontCss);return n&&"function"!=typeof n?n:{}},makeNodeClasses:function(e,t){var n=tools.apply(e.view.nodeClasses,[e.treeId,t],e.view.nodeClasses);return n&&"function"!=typeof n?n:{add:[],remove:[]}},makeNodeIcoClass:function(e,t){var n=["ico"];if(!t.isAjaxing){var o=data.nodeIsParent(e,t);n[0]=(t.iconSkin?t.iconSkin+"_":"")+n[0],o?n.push(t.open?consts.folder.OPEN:consts.folder.CLOSE):n.push(consts.folder.DOCU)}return consts.className.BUTTON+" "+n.join("_")},makeNodeIcoStyle:function(e,t){var n=[];if(!t.isAjaxing){var o=data.nodeIsParent(e,t)&&t.iconOpen&&t.iconClose?t.open?t.iconOpen:t.iconClose:t[e.data.key.icon];o&&n.push("background:url(",o,") 0 0 no-repeat;"),0!=e.view.showIcon&&tools.apply(e.view.showIcon,[e.treeId,t],!0)||n.push("display:none;")}return n.join("")},makeNodeLineClass:function(e,t){var n=[];return e.view.showLine?0==t.level&&t.isFirstNode&&t.isLastNode?n.push(consts.line.ROOT):0==t.level&&t.isFirstNode?n.push(consts.line.ROOTS):t.isLastNode?n.push(consts.line.BOTTOM):n.push(consts.line.CENTER):n.push(consts.line.NOLINE),data.nodeIsParent(e,t)?n.push(t.open?consts.folder.OPEN:consts.folder.CLOSE):n.push(consts.folder.DOCU),view.makeNodeLineClassEx(t)+n.join("_")},makeNodeLineClassEx:function(e){return consts.className.BUTTON+" "+consts.className.LEVEL+e.level+" "+consts.className.SWITCH+" "},makeNodeTarget:function(e){return e.target||"_blank"},makeNodeUrl:function(e,t){var n=e.data.key.url;return t[n]?t[n]:null},makeUlHtml:function(e,t,n,o){n.push("
      "),n.push(o),n.push("
    ")},makeUlLineClass:function(e,t){return e.view.showLine&&!t.isLastNode?consts.line.LINE:""},removeChildNodes:function(e,t){if(t){var n=data.nodeChildren(e,t);if(n){for(var o=0,a=n.length;on.bottom||o.right>n.right||o.left
  • ",Z)).append(Pe(d,he.id.A,Z).clone()),r.css("padding","0"),r.children("#"+d.tId+he.id.A).removeClass(he.node.CURSELECTED),te.append(r),t==Z.edit.drag.maxShowNodeNum-1&&(r=Pe("
  • ...
  • ",Z),te.append(r)));te.attr("id",ee[0].tId+he.id.UL+"_tmp"),te.addClass(Z.treeObj.attr("class")),te.appendTo(ie),(oe=Pe("",Z)).attr("id","zTreeMove_arrow_tmp"),oe.appendTo(ie),Z.treeObj.trigger(he.event.DRAG,[e,Z.treeId,ee])}if(1==$.dragFlag){if(de&&oe.attr("id")==e.target.id&&ue&&e.clientX+ae.scrollLeft()+2>fe("#"+ue+he.id.A,de).offset().left){var s=fe("#"+ue+he.id.A,de);e.target=0Z.edit.drag.borderMin,b=fZ.edit.drag.borderMin,R=EZ.edit.drag.borderMin,P=IZ.edit.drag.borderMin,C=T>Z.edit.drag.borderMin&&f>Z.edit.drag.borderMin&&E>Z.edit.drag.borderMin&&I>Z.edit.drag.borderMin,w=h&&se.treeObj.scrollTop()<=0,M=b&&se.treeObj.scrollTop()+se.treeObj.height()+10>=m,_=R&&se.treeObj.scrollLeft()<=0,O=P&&se.treeObj.scrollLeft()+se.treeObj.width()+10>=p;if(e.target&&Ie.isChildOrSelf(e.target,se.treeId)){for(var D=e.target;D&&D.tagName&&!Ie.eqs(D.tagName,"li")&&D.id!=se.treeId;)D=D.parentNode;var y=!0;for(t=0,o=ee.length;tse.edit.drag.autoOpenTime&&Ie.apply(se.callback.beforeDragOpen,[se.treeId,A],!0)&&(be.switchNode(se,A),se.edit.drag.autoExpandTrigger&&se.treeObj.trigger(he.event.EXPAND,[se.treeId,A]))},se.edit.drag.autoOpenTime+50),window.zTreeMoveTargetNodeTId=A.tId)}}else F()}else ge=he.move.TYPE_INNER,de&&Ie.apply(se.edit.drag.inner,[se.treeId,ee,null],!!se.edit.drag.inner)?de.addClass(he.node.TMPTARGET_TREE):de=null,oe.css({display:"none"}),window.zTreeMoveTimer&&(clearTimeout(window.zTreeMoveTimer),window.zTreeMoveTargetNodeTId=null);ce=ue,Ne=ge,Z.treeObj.trigger(he.event.DRAGMOVE,[e,Z.treeId,ee])}return!1}function Te(d){if(window.zTreeMoveTimer&&(clearTimeout(window.zTreeMoveTimer),window.zTreeMoveTargetNodeTId=null),Ne=ce=null,ae.unbind("mousemove",s),ae.unbind("mouseup",Te),ae.unbind("selectstart",c),ie.css("cursor",""),de&&(de.removeClass(he.node.TMPTARGET_TREE),ue&&fe("#"+ue+he.id.A,de).removeClass(he.node.TMPTARGET_NODE+"_"+he.move.TYPE_PREV).removeClass(he.node.TMPTARGET_NODE+"_"+Ee.move.TYPE_NEXT).removeClass(he.node.TMPTARGET_NODE+"_"+Ee.move.TYPE_INNER)),Ie.showIfameMask(Z,!1),J.showHoverDom=!0,0!=$.dragFlag){var e,t,o;for(e=$.dragFlag=0,t=ee.length;e",e);l.appendTo(Pe("body",e)),o.dragMaskList.push(l)}}},view:{addEditBtn:function(e,t){if(!(t.editNameFlag||0