diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index cc6edf8..0000000 --- a/nginx.conf +++ /dev/null @@ -1,5 +0,0 @@ -#注意:如果蓝天采集器安装在子目录中,请将“子目录”替换为相应目录的名称,否则删除文字“/子目录” -if (!-e $request_filename) { - rewrite ^/子目录/app/(\w+)/(.*)$ /子目录/app/$1/index.php?s=/$2 last; - rewrite ^/子目录/(.*)$ /子目录/index.php?s=/$1 last; -} \ No newline at end of file diff --git a/plugin/skycaiji.php b/plugin/skycaiji.php index 85a6692..891ebf8 100644 --- a/plugin/skycaiji.php +++ b/plugin/skycaiji.php @@ -64,18 +64,27 @@ class skycaiji{ * 请求网址 * @param string $url 网址 * @param mixed $post (bool)post模式 或者 (array)post数据 - * @param string $pageCharset 页面编码,不填可自动识别 + * @param string $charset 网页编码,默认自动识别 * @param array $headers 头信息 - * @return array 返回数组:(bool)success是否成功,(string)header头信息,(string)content页面内容 + * @param array $options 选项:timeout超时秒数,curlopts附加curl的选项值 + * @return array 返回数组:(bool)success抓取成功,(int)code页面状态码,(string)header头信息,(string)content页面内容,(array)error错误,(array)info资源信息 */ - public static function curl($url,$post=null,$pageCharset=null,$headers=array()){ - $pageCharset=isset($pageCharset)?$pageCharset:'auto';//默认自动识别 - $data=get_html($url,$headers,array('timeout'=>60),$pageCharset,$post,true); + public static function curl($url,$post=null,$charset=null,$headers=array(),$options=array()){ + $charset=$charset?$charset:'auto';//默认auto自动识别 + $options=is_array($options)?$options:array(); + $options['timeout']=$options['timeout']?:30;//超时时间(秒) + $options['return_info']=$options['return_info']?:1;//返回curl句柄信息 + $options['return_body']=$options['return_body']?:1;//返回非成功状态的页面内容 + $options['curlopts']=is_array($options['curlopts'])?$options['curlopts']:array();//curl的选项值列表,以CURLOPT_XXX为键名 + $data=get_html($url,$headers,$options,$charset,$post,true); $data=is_array($data)?$data:array(); $data=array( 'success'=>$data['ok']?true:false, - 'header'=>$data['header']?$data['header']:'', - 'content'=>$data['html']?$data['html']:'' + 'code'=>intval($data['code']), + 'header'=>$data['header']?:'', + 'content'=>$data['html']?:'', + 'error'=>$data['error']?:array(), + 'info'=>$data['info']?:array(), ); return $data; } diff --git a/public/codemirror/addon/comment/comment.js b/public/codemirror/addon/comment/comment.js new file mode 100644 index 0000000..7f6f3ed --- /dev/null +++ b/public/codemirror/addon/comment/comment.js @@ -0,0 +1,211 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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"; + + var noOptions = {}; + var nonWS = /[^\s\u00a0]/; + var Pos = CodeMirror.Pos, cmp = CodeMirror.cmpPos; + + function firstNonWS(str) { + var found = str.search(nonWS); + return found == -1 ? 0 : found; + } + + CodeMirror.commands.toggleComment = function(cm) { + cm.toggleComment(); + }; + + CodeMirror.defineExtension("toggleComment", function(options) { + if (!options) options = noOptions; + var cm = this; + var minLine = Infinity, ranges = this.listSelections(), mode = null; + for (var i = ranges.length - 1; i >= 0; i--) { + var from = ranges[i].from(), to = ranges[i].to(); + if (from.line >= minLine) continue; + if (to.line >= minLine) to = Pos(minLine, 0); + minLine = from.line; + if (mode == null) { + if (cm.uncomment(from, to, options)) mode = "un"; + else { cm.lineComment(from, to, options); mode = "line"; } + } else if (mode == "un") { + cm.uncomment(from, to, options); + } else { + cm.lineComment(from, to, options); + } + } + }); + + // Rough heuristic to try and detect lines that are part of multi-line string + function probablyInsideString(cm, pos, line) { + return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line) + } + + function getMode(cm, pos) { + var mode = cm.getMode() + return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos) + } + + CodeMirror.defineExtension("lineComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var firstLine = self.getLine(from.line); + if (firstLine == null || probablyInsideString(self, from, firstLine)) return; + + var commentString = options.lineComment || mode.lineComment; + if (!commentString) { + if (options.blockCommentStart || mode.blockCommentStart) { + options.fullLines = true; + self.blockComment(from, to, options); + } + return; + } + + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1); + var pad = options.padding == null ? " " : options.padding; + var blankLines = options.commentBlankLines || from.line == to.line; + + self.operation(function() { + if (options.indent) { + var baseString = null; + for (var i = from.line; i < end; ++i) { + var line = self.getLine(i); + var whitespace = line.search(nonWS) === -1 ? line : line.slice(0, firstNonWS(line)); + if (baseString == null || baseString.length > whitespace.length) { + baseString = whitespace; + } + } + for (var i = from.line; i < end; ++i) { + var line = self.getLine(i), cut = baseString.length; + if (!blankLines && !nonWS.test(line)) continue; + if (line.slice(0, cut) != baseString) cut = firstNonWS(line); + self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut)); + } + } else { + for (var i = from.line; i < end; ++i) { + if (blankLines || nonWS.test(self.getLine(i))) + self.replaceRange(commentString + pad, Pos(i, 0)); + } + } + }); + }); + + CodeMirror.defineExtension("blockComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) { + if ((options.lineComment || mode.lineComment) && options.fullLines != false) + self.lineComment(from, to, options); + return; + } + if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return + + var end = Math.min(to.line, self.lastLine()); + if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end; + + var pad = options.padding == null ? " " : options.padding; + if (from.line > end) return; + + self.operation(function() { + if (options.fullLines != false) { + var lastLineHasText = nonWS.test(self.getLine(end)); + self.replaceRange(pad + endString, Pos(end)); + self.replaceRange(startString + pad, Pos(from.line, 0)); + var lead = options.blockCommentLead || mode.blockCommentLead; + if (lead != null) for (var i = from.line + 1; i <= end; ++i) + if (i != end || lastLineHasText) + self.replaceRange(lead + pad, Pos(i, 0)); + } else { + var atCursor = cmp(self.getCursor("to"), to) == 0, empty = !self.somethingSelected() + self.replaceRange(endString, to); + if (atCursor) self.setSelection(empty ? to : self.getCursor("from"), to) + self.replaceRange(startString, from); + } + }); + }); + + CodeMirror.defineExtension("uncomment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end); + + // Try finding line comments + var lineString = options.lineComment || mode.lineComment, lines = []; + var pad = options.padding == null ? " " : options.padding, didSomething; + lineComment: { + if (!lineString) break lineComment; + for (var i = start; i <= end; ++i) { + var line = self.getLine(i); + var found = line.indexOf(lineString); + if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1; + if (found == -1 && nonWS.test(line)) break lineComment; + if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; + lines.push(line); + } + self.operation(function() { + for (var i = start; i <= end; ++i) { + var line = lines[i - start]; + var pos = line.indexOf(lineString), endPos = pos + lineString.length; + if (pos < 0) continue; + if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length; + didSomething = true; + self.replaceRange("", Pos(i, pos), Pos(i, endPos)); + } + }); + if (didSomething) return true; + } + + // Try block comments + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) return false; + var lead = options.blockCommentLead || mode.blockCommentLead; + var startLine = self.getLine(start), open = startLine.indexOf(startString) + if (open == -1) return false + var endLine = end == start ? startLine : self.getLine(end) + var close = endLine.indexOf(endString, end == start ? open + startString.length : 0); + var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1) + if (close == -1 || + !/comment/.test(self.getTokenTypeAt(insideStart)) || + !/comment/.test(self.getTokenTypeAt(insideEnd)) || + self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1) + return false; + + // Avoid killing block comments completely outside the selection. + // Positions of the last startString before the start of the selection, and the first endString after it. + var lastStart = startLine.lastIndexOf(startString, from.ch); + var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length); + if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false; + // Positions of the first endString after the end of the selection, and the last startString before it. + firstEnd = endLine.indexOf(endString, to.ch); + var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch); + lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart; + if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false; + + self.operation(function() { + self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), + Pos(end, close + endString.length)); + var openEnd = open + startString.length; + if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length; + self.replaceRange("", Pos(start, open), Pos(start, openEnd)); + if (lead) for (var i = start + 1; i <= end; ++i) { + var line = self.getLine(i), found = line.indexOf(lead); + if (found == -1 || nonWS.test(line.slice(0, found))) continue; + var foundEnd = found + lead.length; + if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length; + self.replaceRange("", Pos(i, found), Pos(i, foundEnd)); + } + }); + return true; + }); +}); diff --git a/public/codemirror/addon/comment/continuecomment.js b/public/codemirror/addon/comment/continuecomment.js new file mode 100644 index 0000000..2f16da8 --- /dev/null +++ b/public/codemirror/addon/comment/continuecomment.js @@ -0,0 +1,114 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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) { + var nonspace = /\S/g; + var repeat = String.prototype.repeat || function (n) { return Array(n + 1).join(this); }; + function continueComment(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + var ranges = cm.listSelections(), mode, inserts = []; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].head + if (!/\bcomment\b/.test(cm.getTokenTypeAt(pos))) return CodeMirror.Pass; + var modeHere = cm.getModeAt(pos) + if (!mode) mode = modeHere; + else if (mode != modeHere) return CodeMirror.Pass; + + var insert = null, line, found; + var blockStart = mode.blockCommentStart, lineCmt = mode.lineComment; + if (blockStart && mode.blockCommentContinue) { + line = cm.getLine(pos.line); + var end = line.lastIndexOf(mode.blockCommentEnd, pos.ch - mode.blockCommentEnd.length); + // 1. if this block comment ended + // 2. if this is actually inside a line comment + if (end != -1 && end == pos.ch - mode.blockCommentEnd.length || + lineCmt && (found = line.lastIndexOf(lineCmt, pos.ch - 1)) > -1 && + /\bcomment\b/.test(cm.getTokenTypeAt({line: pos.line, ch: found + 1}))) { + // ...then don't continue it + } else if (pos.ch >= blockStart.length && + (found = line.lastIndexOf(blockStart, pos.ch - blockStart.length)) > -1 && + found > end) { + // reuse the existing leading spaces/tabs/mixed + // or build the correct indent using CM's tab/indent options + if (nonspaceAfter(0, line) >= found) { + insert = line.slice(0, found); + } else { + var tabSize = cm.options.tabSize, numTabs; + found = CodeMirror.countColumn(line, found, tabSize); + insert = !cm.options.indentWithTabs ? repeat.call(" ", found) : + repeat.call("\t", (numTabs = Math.floor(found / tabSize))) + + repeat.call(" ", found - tabSize * numTabs); + } + } else if ((found = line.indexOf(mode.blockCommentContinue)) > -1 && + found <= pos.ch && + found <= nonspaceAfter(0, line)) { + insert = line.slice(0, found); + } + if (insert != null) insert += mode.blockCommentContinue + } + if (insert == null && lineCmt && continueLineCommentEnabled(cm)) { + if (line == null) line = cm.getLine(pos.line); + found = line.indexOf(lineCmt); + // cursor at pos 0, line comment also at pos 0 => shift it down, don't continue + if (!pos.ch && !found) insert = ""; + // continue only if the line starts with an optional space + line comment + else if (found > -1 && nonspaceAfter(0, line) >= found) { + // don't continue if there's only space(s) after cursor or the end of the line + insert = nonspaceAfter(pos.ch, line) > -1; + // but always continue if the next line starts with a line comment too + if (!insert) { + var next = cm.getLine(pos.line + 1) || '', + nextFound = next.indexOf(lineCmt); + insert = nextFound > -1 && nonspaceAfter(0, next) >= nextFound || null; + } + if (insert) { + insert = line.slice(0, found) + lineCmt + + line.slice(found + lineCmt.length).match(/^\s*/)[0]; + } + } + } + if (insert == null) return CodeMirror.Pass; + inserts[i] = "\n" + insert; + } + + cm.operation(function() { + for (var i = ranges.length - 1; i >= 0; i--) + cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert"); + }); + } + + function nonspaceAfter(ch, str) { + nonspace.lastIndex = ch; + var m = nonspace.exec(str); + return m ? m.index : -1; + } + + function continueLineCommentEnabled(cm) { + var opt = cm.getOption("continueComments"); + if (opt && typeof opt == "object") + return opt.continueLineComment !== false; + return true; + } + + CodeMirror.defineOption("continueComments", null, function(cm, val, prev) { + if (prev && prev != CodeMirror.Init) + cm.removeKeyMap("continueComment"); + if (val) { + var key = "Enter"; + if (typeof val == "string") + key = val; + else if (typeof val == "object" && val.key) + key = val.key; + var map = {name: "continueComment"}; + map[key] = continueComment; + cm.addKeyMap(map); + } + }); +}); diff --git a/public/codemirror/addon/dialog/dialog.css b/public/codemirror/addon/dialog/dialog.css new file mode 100644 index 0000000..677c078 --- /dev/null +++ b/public/codemirror/addon/dialog/dialog.css @@ -0,0 +1,32 @@ +.CodeMirror-dialog { + position: absolute; + left: 0; right: 0; + background: inherit; + z-index: 15; + padding: .1em .8em; + overflow: hidden; + color: inherit; +} + +.CodeMirror-dialog-top { + border-bottom: 1px solid #eee; + top: 0; +} + +.CodeMirror-dialog-bottom { + border-top: 1px solid #eee; + bottom: 0; +} + +.CodeMirror-dialog input { + border: none; + outline: none; + background: transparent; + width: 20em; + color: inherit; + font-family: monospace; +} + +.CodeMirror-dialog button { + font-size: 70%; +} diff --git a/public/codemirror/addon/dialog/dialog.js b/public/codemirror/addon/dialog/dialog.js new file mode 100644 index 0000000..0294d23 --- /dev/null +++ b/public/codemirror/addon/dialog/dialog.js @@ -0,0 +1,163 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/LICENSE + +// Open simple dialogs on top of an editor. Relies on dialog.css. + +(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) { + function dialogDiv(cm, template, bottom) { + var wrap = cm.getWrapperElement(); + var dialog; + dialog = wrap.appendChild(document.createElement("div")); + if (bottom) + dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; + else + dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; + + if (typeof template == "string") { + dialog.innerHTML = template; + } else { // Assuming it's a detached DOM element. + dialog.appendChild(template); + } + CodeMirror.addClass(wrap, 'dialog-opened'); + return dialog; + } + + function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; + } + + CodeMirror.defineExtension("openDialog", function(template, callback, options) { + if (!options) options = {}; + + closeNotification(this, null); + + var dialog = dialogDiv(this, template, options.bottom); + var closed = false, me = this; + function close(newVal) { + if (typeof newVal == 'string') { + inp.value = newVal; + } else { + if (closed) return; + closed = true; + CodeMirror.rmClass(dialog.parentNode, 'dialog-opened'); + dialog.parentNode.removeChild(dialog); + me.focus(); + + if (options.onClose) options.onClose(dialog); + } + } + + var inp = dialog.getElementsByTagName("input")[0], button; + if (inp) { + inp.focus(); + + if (options.value) { + inp.value = options.value; + if (options.selectValueOnOpen !== false) { + inp.select(); + } + } + + if (options.onInput) + CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); + + CodeMirror.on(inp, "keydown", function(e) { + if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } + if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { + inp.blur(); + CodeMirror.e_stop(e); + close(); + } + if (e.keyCode == 13) callback(inp.value, e); + }); + + if (options.closeOnBlur !== false) CodeMirror.on(dialog, "focusout", function (evt) { + if (evt.relatedTarget !== null) close(); + }); + } else if (button = dialog.getElementsByTagName("button")[0]) { + CodeMirror.on(button, "click", function() { + close(); + me.focus(); + }); + + if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); + + button.focus(); + } + return close; + }); + + CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { + closeNotification(this, null); + var dialog = dialogDiv(this, template, options && options.bottom); + var buttons = dialog.getElementsByTagName("button"); + var closed = false, me = this, blurring = 1; + function close() { + if (closed) return; + closed = true; + CodeMirror.rmClass(dialog.parentNode, 'dialog-opened'); + dialog.parentNode.removeChild(dialog); + me.focus(); + } + buttons[0].focus(); + for (var i = 0; i < buttons.length; ++i) { + var b = buttons[i]; + (function(callback) { + CodeMirror.on(b, "click", function(e) { + CodeMirror.e_preventDefault(e); + close(); + if (callback) callback(me); + }); + })(callbacks[i]); + CodeMirror.on(b, "blur", function() { + --blurring; + setTimeout(function() { if (blurring <= 0) close(); }, 200); + }); + CodeMirror.on(b, "focus", function() { ++blurring; }); + } + }); + + /* + * openNotification + * Opens a notification, that can be closed with an optional timer + * (default 5000ms timer) and always closes on click. + * + * If a notification is opened while another is opened, it will close the + * currently opened one and open the new one immediately. + */ + CodeMirror.defineExtension("openNotification", function(template, options) { + closeNotification(this, close); + var dialog = dialogDiv(this, template, options && options.bottom); + var closed = false, doneTimer; + var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; + + function close() { + if (closed) return; + closed = true; + clearTimeout(doneTimer); + CodeMirror.rmClass(dialog.parentNode, 'dialog-opened'); + dialog.parentNode.removeChild(dialog); + } + + CodeMirror.on(dialog, 'click', function(e) { + CodeMirror.e_preventDefault(e); + close(); + }); + + if (duration) + doneTimer = setTimeout(close, duration); + + return close; + }); +}); diff --git a/public/codemirror/addon/edit/matchbrackets.js b/public/codemirror/addon/edit/matchbrackets.js new file mode 100644 index 0000000..c342910 --- /dev/null +++ b/public/codemirror/addon/edit/matchbrackets.js @@ -0,0 +1,160 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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) { + var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && + (document.documentMode == null || document.documentMode < 8); + + var Pos = CodeMirror.Pos; + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<"}; + + function bracketRegex(config) { + return config && config.bracketRegex || /[(){}[\]]/ + } + + function findMatchingBracket(cm, where, config) { + var line = cm.getLineHandle(where.line), pos = where.ch - 1; + var afterCursor = config && config.afterCursor + if (afterCursor == null) + afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className) + var re = bracketRegex(config) + + // A cursor is defined as between two characters, but in in vim command mode + // (i.e. not insert mode), the cursor is visually represented as a + // highlighted box on top of the 2nd character. Otherwise, we allow matches + // from before or after the cursor. + var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) || + re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)]; + if (!match) return null; + var dir = match.charAt(1) == ">" ? 1 : -1; + if (config && config.strict && (dir > 0) != (pos == where.ch)) return null; + var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); + + var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style, config); + if (found == null) return null; + return {from: Pos(where.line, pos), to: found && found.pos, + match: found && found.ch == match.charAt(0), forward: dir > 0}; + } + + // bracketRegex is used to specify which type of bracket to scan + // should be a regexp, e.g. /[[\]]/ + // + // Note: If "where" is on an open bracket, then this bracket is ignored. + // + // Returns false when no bracket was found, null when it reached + // maxScanLines and gave up + function scanForBracket(cm, where, dir, style, config) { + var maxScanLen = (config && config.maxScanLineLength) || 10000; + var maxScanLines = (config && config.maxScanLines) || 1000; + + var stack = []; + var re = bracketRegex(config) + var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) + : Math.max(cm.firstLine() - 1, where.line - maxScanLines); + for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { + var line = cm.getLine(lineNo); + if (!line) continue; + var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; + if (line.length > maxScanLen) continue; + if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); + for (; pos != end; pos += dir) { + var ch = line.charAt(pos); + if (re.test(ch) && (style === undefined || + (cm.getTokenTypeAt(Pos(lineNo, pos + 1)) || "") == (style || ""))) { + var match = matching[ch]; + if (match && (match.charAt(1) == ">") == (dir > 0)) stack.push(ch); + else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; + else stack.pop(); + } + } + } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; + } + + function matchBrackets(cm, autoclear, config) { + // Disable brace matching in long lines, since it'll cause hugely slow updates + var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000, + highlightNonMatching = config && config.highlightNonMatching; + var marks = [], ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config); + if (match && (match.match || highlightNonMatching !== false) && cm.getLine(match.from.line).length <= maxHighlightLen) { + var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); + if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) + marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); + } + } + + if (marks.length) { + // Kludge to work around the IE bug from issue #1193, where text + // input stops going to the textarea whenever this fires. + if (ie_lt8 && cm.state.focused) cm.focus(); + + var clear = function() { + cm.operation(function() { + for (var i = 0; i < marks.length; i++) marks[i].clear(); + }); + }; + if (autoclear) setTimeout(clear, 800); + else return clear; + } + } + + function doMatchBrackets(cm) { + cm.operation(function() { + if (cm.state.matchBrackets.currentlyHighlighted) { + cm.state.matchBrackets.currentlyHighlighted(); + cm.state.matchBrackets.currentlyHighlighted = null; + } + cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); + }); + } + + function clearHighlighted(cm) { + if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) { + cm.state.matchBrackets.currentlyHighlighted(); + cm.state.matchBrackets.currentlyHighlighted = null; + } + } + + CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("cursorActivity", doMatchBrackets); + cm.off("focus", doMatchBrackets) + cm.off("blur", clearHighlighted) + clearHighlighted(cm); + } + if (val) { + cm.state.matchBrackets = typeof val == "object" ? val : {}; + cm.on("cursorActivity", doMatchBrackets); + cm.on("focus", doMatchBrackets) + cm.on("blur", clearHighlighted) + } + }); + + CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); + CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){ + // Backwards-compatibility kludge + if (oldConfig || typeof config == "boolean") { + if (!oldConfig) { + config = config ? {strict: true} : null + } else { + oldConfig.strict = config + config = oldConfig + } + } + return findMatchingBracket(this, pos, config) + }); + CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ + return scanForBracket(this, pos, dir, style, config); + }); +}); diff --git a/public/codemirror/addon/fold/brace-fold.js b/public/codemirror/addon/fold/brace-fold.js new file mode 100644 index 0000000..2124768 --- /dev/null +++ b/public/codemirror/addon/fold/brace-fold.js @@ -0,0 +1,119 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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 bracketFolding(pairs) { + return function(cm, start) { + var line = start.line, lineText = cm.getLine(line); + + function findOpening(pair) { + var tokenType; + for (var at = start.ch, pass = 0;;) { + var found = at <= 0 ? -1 : lineText.lastIndexOf(pair[0], at - 1); + if (found == -1) { + if (pass == 1) break; + pass = 1; + at = lineText.length; + continue; + } + if (pass == 1 && found < start.ch) break; + tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)); + if (!/^(comment|string)/.test(tokenType)) return {ch: found + 1, tokenType: tokenType, pair: pair}; + at = found - 1; + } + } + + function findRange(found) { + var count = 1, lastLine = cm.lastLine(), end, startCh = found.ch, endCh + outer: for (var i = line; i <= lastLine; ++i) { + var text = cm.getLine(i), pos = i == line ? startCh : 0; + for (;;) { + var nextOpen = text.indexOf(found.pair[0], pos), nextClose = text.indexOf(found.pair[1], pos); + if (nextOpen < 0) nextOpen = text.length; + if (nextClose < 0) nextClose = text.length; + pos = Math.min(nextOpen, nextClose); + if (pos == text.length) break; + if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == found.tokenType) { + if (pos == nextOpen) ++count; + else if (!--count) { end = i; endCh = pos; break outer; } + } + ++pos; + } + } + + if (end == null || line == end) return null + return {from: CodeMirror.Pos(line, startCh), + to: CodeMirror.Pos(end, endCh)}; + } + + var found = [] + for (var i = 0; i < pairs.length; i++) { + var open = findOpening(pairs[i]) + if (open) found.push(open) + } + found.sort(function(a, b) { return a.ch - b.ch }) + for (var i = 0; i < found.length; i++) { + var range = findRange(found[i]) + if (range) return range + } + return null + } +} + +CodeMirror.registerHelper("fold", "brace", bracketFolding([["{", "}"], ["[", "]"]])); + +CodeMirror.registerHelper("fold", "brace-paren", bracketFolding([["{", "}"], ["[", "]"], ["(", ")"]])); + +CodeMirror.registerHelper("fold", "import", function(cm, start) { + function hasImport(line) { + if (line < cm.firstLine() || line > cm.lastLine()) return null; + var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + if (start.type != "keyword" || start.string != "import") return null; + // Now find closing semicolon, return its position + for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) { + var text = cm.getLine(i), semi = text.indexOf(";"); + if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)}; + } + } + + var startLine = start.line, has = hasImport(startLine), prev; + if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1)) + return null; + for (var end = has.end;;) { + var next = hasImport(end.line + 1); + if (next == null) break; + end = next.end; + } + return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end}; +}); + +CodeMirror.registerHelper("fold", "include", function(cm, start) { + function hasInclude(line) { + if (line < cm.firstLine() || line > cm.lastLine()) return null; + var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8; + } + + var startLine = start.line, has = hasInclude(startLine); + if (has == null || hasInclude(startLine - 1) != null) return null; + for (var end = startLine;;) { + var next = hasInclude(end + 1); + if (next == null) break; + ++end; + } + return {from: CodeMirror.Pos(startLine, has + 1), + to: cm.clipPos(CodeMirror.Pos(end))}; +}); + +}); diff --git a/public/codemirror/addon/fold/comment-fold.js b/public/codemirror/addon/fold/comment-fold.js new file mode 100644 index 0000000..adaa79d --- /dev/null +++ b/public/codemirror/addon/fold/comment-fold.js @@ -0,0 +1,59 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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"; + +CodeMirror.registerGlobalHelper("fold", "comment", function(mode) { + return mode.blockCommentStart && mode.blockCommentEnd; +}, function(cm, start) { + var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd; + if (!startToken || !endToken) return; + var line = start.line, lineText = cm.getLine(line); + + var startCh; + for (var at = start.ch, pass = 0;;) { + var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1); + if (found == -1) { + if (pass == 1) return; + pass = 1; + at = lineText.length; + continue; + } + if (pass == 1 && found < start.ch) return; + if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) && + (found == 0 || lineText.slice(found - endToken.length, found) == endToken || + !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) { + startCh = found + startToken.length; + break; + } + at = found - 1; + } + + var depth = 1, lastLine = cm.lastLine(), end, endCh; + outer: for (var i = line; i <= lastLine; ++i) { + var text = cm.getLine(i), pos = i == line ? startCh : 0; + for (;;) { + var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos); + if (nextOpen < 0) nextOpen = text.length; + if (nextClose < 0) nextClose = text.length; + pos = Math.min(nextOpen, nextClose); + if (pos == text.length) break; + if (pos == nextOpen) ++depth; + else if (!--depth) { end = i; endCh = pos; break outer; } + ++pos; + } + } + if (end == null || line == end && endCh == startCh) return; + return {from: CodeMirror.Pos(line, startCh), + to: CodeMirror.Pos(end, endCh)}; +}); + +}); diff --git a/public/codemirror/addon/fold/foldcode.js b/public/codemirror/addon/fold/foldcode.js new file mode 100644 index 0000000..a6a51af --- /dev/null +++ b/public/codemirror/addon/fold/foldcode.js @@ -0,0 +1,159 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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 doFold(cm, pos, options, force) { + if (options && options.call) { + var finder = options; + options = null; + } else { + var finder = getOption(cm, options, "rangeFinder"); + } + if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0); + var minSize = getOption(cm, options, "minFoldSize"); + + function getRange(allowFolded) { + var range = finder(cm, pos); + if (!range || range.to.line - range.from.line < minSize) return null; + if (force === "fold") return range; + + var marks = cm.findMarksAt(range.from); + for (var i = 0; i < marks.length; ++i) { + if (marks[i].__isFold) { + if (!allowFolded) return null; + range.cleared = true; + marks[i].clear(); + } + } + return range; + } + + var range = getRange(true); + if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) { + pos = CodeMirror.Pos(pos.line - 1, 0); + range = getRange(false); + } + if (!range || range.cleared || force === "unfold") return; + + var myWidget = makeWidget(cm, options, range); + CodeMirror.on(myWidget, "mousedown", function(e) { + myRange.clear(); + CodeMirror.e_preventDefault(e); + }); + var myRange = cm.markText(range.from, range.to, { + replacedWith: myWidget, + clearOnEnter: getOption(cm, options, "clearOnEnter"), + __isFold: true + }); + myRange.on("clear", function(from, to) { + CodeMirror.signal(cm, "unfold", cm, from, to); + }); + CodeMirror.signal(cm, "fold", cm, range.from, range.to); + } + + function makeWidget(cm, options, range) { + var widget = getOption(cm, options, "widget"); + + if (typeof widget == "function") { + widget = widget(range.from, range.to); + } + + if (typeof widget == "string") { + var text = document.createTextNode(widget); + widget = document.createElement("span"); + widget.appendChild(text); + widget.className = "CodeMirror-foldmarker"; + } else if (widget) { + widget = widget.cloneNode(true) + } + return widget; + } + + // Clumsy backwards-compatible interface + CodeMirror.newFoldFunction = function(rangeFinder, widget) { + return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); }; + }; + + // New-style interface + CodeMirror.defineExtension("foldCode", function(pos, options, force) { + doFold(this, pos, options, force); + }); + + CodeMirror.defineExtension("isFolded", function(pos) { + var marks = this.findMarksAt(pos); + for (var i = 0; i < marks.length; ++i) + if (marks[i].__isFold) return true; + }); + + CodeMirror.commands.toggleFold = function(cm) { + cm.foldCode(cm.getCursor()); + }; + CodeMirror.commands.fold = function(cm) { + cm.foldCode(cm.getCursor(), null, "fold"); + }; + CodeMirror.commands.unfold = function(cm) { + cm.foldCode(cm.getCursor(), { scanUp: false }, "unfold"); + }; + CodeMirror.commands.foldAll = function(cm) { + cm.operation(function() { + for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) + cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "fold"); + }); + }; + CodeMirror.commands.unfoldAll = function(cm) { + cm.operation(function() { + for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++) + cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "unfold"); + }); + }; + + CodeMirror.registerHelper("fold", "combine", function() { + var funcs = Array.prototype.slice.call(arguments, 0); + return function(cm, start) { + for (var i = 0; i < funcs.length; ++i) { + var found = funcs[i](cm, start); + if (found) return found; + } + }; + }); + + CodeMirror.registerHelper("fold", "auto", function(cm, start) { + var helpers = cm.getHelpers(start, "fold"); + for (var i = 0; i < helpers.length; i++) { + var cur = helpers[i](cm, start); + if (cur) return cur; + } + }); + + var defaultOptions = { + rangeFinder: CodeMirror.fold.auto, + widget: "\u2194", + minFoldSize: 0, + scanUp: false, + clearOnEnter: true + }; + + CodeMirror.defineOption("foldOptions", null); + + function getOption(cm, options, name) { + if (options && options[name] !== undefined) + return options[name]; + var editorOptions = cm.options.foldOptions; + if (editorOptions && editorOptions[name] !== undefined) + return editorOptions[name]; + return defaultOptions[name]; + } + + CodeMirror.defineExtension("foldOption", function(options, name) { + return getOption(this, options, name); + }); +}); diff --git a/public/codemirror/addon/fold/foldgutter.css b/public/codemirror/addon/fold/foldgutter.css new file mode 100644 index 0000000..ad19ae2 --- /dev/null +++ b/public/codemirror/addon/fold/foldgutter.css @@ -0,0 +1,20 @@ +.CodeMirror-foldmarker { + color: blue; + text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; + font-family: arial; + line-height: .3; + cursor: pointer; +} +.CodeMirror-foldgutter { + width: .7em; +} +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + cursor: pointer; +} +.CodeMirror-foldgutter-open:after { + content: "\25BE"; +} +.CodeMirror-foldgutter-folded:after { + content: "\25B8"; +} diff --git a/public/codemirror/addon/fold/foldgutter.js b/public/codemirror/addon/fold/foldgutter.js new file mode 100644 index 0000000..8fc0e99 --- /dev/null +++ b/public/codemirror/addon/fold/foldgutter.js @@ -0,0 +1,169 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("./foldcode")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "./foldcode"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineOption("foldGutter", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.clearGutter(cm.state.foldGutter.options.gutter); + cm.state.foldGutter = null; + cm.off("gutterClick", onGutterClick); + cm.off("changes", onChange); + cm.off("viewportChange", onViewportChange); + cm.off("fold", onFold); + cm.off("unfold", onFold); + cm.off("swapDoc", onChange); + cm.off("optionChange", optionChange); + } + if (val) { + cm.state.foldGutter = new State(parseOptions(val)); + updateInViewport(cm); + cm.on("gutterClick", onGutterClick); + cm.on("changes", onChange); + cm.on("viewportChange", onViewportChange); + cm.on("fold", onFold); + cm.on("unfold", onFold); + cm.on("swapDoc", onChange); + cm.on("optionChange", optionChange); + } + }); + + var Pos = CodeMirror.Pos; + + function State(options) { + this.options = options; + this.from = this.to = 0; + } + + function parseOptions(opts) { + if (opts === true) opts = {}; + if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter"; + if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open"; + if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded"; + return opts; + } + + function isFolded(cm, line) { + var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0)); + for (var i = 0; i < marks.length; ++i) { + if (marks[i].__isFold) { + var fromPos = marks[i].find(-1); + if (fromPos && fromPos.line === line) + return marks[i]; + } + } + } + + function marker(spec) { + if (typeof spec == "string") { + var elt = document.createElement("div"); + elt.className = spec + " CodeMirror-guttermarker-subtle"; + return elt; + } else { + return spec.cloneNode(true); + } + } + + function updateFoldInfo(cm, from, to) { + var opts = cm.state.foldGutter.options, cur = from - 1; + var minSize = cm.foldOption(opts, "minFoldSize"); + var func = cm.foldOption(opts, "rangeFinder"); + // we can reuse the built-in indicator element if its className matches the new state + var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded); + var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen); + cm.eachLine(from, to, function(line) { + ++cur; + var mark = null; + var old = line.gutterMarkers; + if (old) old = old[opts.gutter]; + if (isFolded(cm, cur)) { + if (clsFolded && old && clsFolded.test(old.className)) return; + mark = marker(opts.indicatorFolded); + } else { + var pos = Pos(cur, 0); + var range = func && func(cm, pos); + if (range && range.to.line - range.from.line >= minSize) { + if (clsOpen && old && clsOpen.test(old.className)) return; + mark = marker(opts.indicatorOpen); + } + } + if (!mark && !old) return; + cm.setGutterMarker(line, opts.gutter, mark); + }); + } + + // copied from CodeMirror/src/util/dom.js + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } + + function updateInViewport(cm) { + var vp = cm.getViewport(), state = cm.state.foldGutter; + if (!state) return; + cm.operation(function() { + updateFoldInfo(cm, vp.from, vp.to); + }); + state.from = vp.from; state.to = vp.to; + } + + function onGutterClick(cm, line, gutter) { + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; + if (gutter != opts.gutter) return; + var folded = isFolded(cm, line); + if (folded) folded.clear(); + else cm.foldCode(Pos(line, 0), opts); + } + + function optionChange(cm, option) { + if (option == "mode") onChange(cm) + } + + function onChange(cm) { + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; + state.from = state.to = 0; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600); + } + + function onViewportChange(cm) { + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { + var vp = cm.getViewport(); + if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { + updateInViewport(cm); + } else { + cm.operation(function() { + if (vp.from < state.from) { + updateFoldInfo(cm, vp.from, state.from); + state.from = vp.from; + } + if (vp.to > state.to) { + updateFoldInfo(cm, state.to, vp.to); + state.to = vp.to; + } + }); + } + }, opts.updateViewportTimeSpan || 400); + } + + function onFold(cm, from) { + var state = cm.state.foldGutter; + if (!state) return; + var line = from.line; + if (line >= state.from && line < state.to) + updateFoldInfo(cm, line, line + 1); + } +}); diff --git a/public/codemirror/addon/fold/indent-fold.js b/public/codemirror/addon/fold/indent-fold.js new file mode 100644 index 0000000..470e84a --- /dev/null +++ b/public/codemirror/addon/fold/indent-fold.js @@ -0,0 +1,48 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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 lineIndent(cm, lineNo) { + var text = cm.getLine(lineNo) + var spaceTo = text.search(/\S/) + if (spaceTo == -1 || /\bcomment\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1)))) + return -1 + return CodeMirror.countColumn(text, null, cm.getOption("tabSize")) +} + +CodeMirror.registerHelper("fold", "indent", function(cm, start) { + var myIndent = lineIndent(cm, start.line) + if (myIndent < 0) return + var lastLineInFold = null + + // Go through lines until we find a line that definitely doesn't belong in + // the block we're folding, or to the end. + for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) { + var indent = lineIndent(cm, i) + if (indent == -1) { + } else if (indent > myIndent) { + // Lines with a greater indent are considered part of the block. + lastLineInFold = i; + } else { + // If this line has non-space, non-comment content, and is + // indented less or equal to the start line, it is the start of + // another block. + break; + } + } + if (lastLineInFold) return { + from: CodeMirror.Pos(start.line, cm.getLine(start.line).length), + to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length) + }; +}); + +}); diff --git a/public/codemirror/addon/selection/active-line.js b/public/codemirror/addon/selection/active-line.js new file mode 100644 index 0000000..ff81ffc --- /dev/null +++ b/public/codemirror/addon/selection/active-line.js @@ -0,0 +1,72 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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"; + var WRAP_CLASS = "CodeMirror-activeline"; + var BACK_CLASS = "CodeMirror-activeline-background"; + var GUTT_CLASS = "CodeMirror-activeline-gutter"; + + CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) { + var prev = old == CodeMirror.Init ? false : old; + if (val == prev) return + if (prev) { + cm.off("beforeSelectionChange", selectionChange); + clearActiveLines(cm); + delete cm.state.activeLines; + } + if (val) { + cm.state.activeLines = []; + updateActiveLines(cm, cm.listSelections()); + cm.on("beforeSelectionChange", selectionChange); + } + }); + + function clearActiveLines(cm) { + for (var i = 0; i < cm.state.activeLines.length; i++) { + cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS); + cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS); + cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS); + } + } + + function sameArray(a, b) { + if (a.length != b.length) return false; + for (var i = 0; i < a.length; i++) + if (a[i] != b[i]) return false; + return true; + } + + function updateActiveLines(cm, ranges) { + var active = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var option = cm.getOption("styleActiveLine"); + if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty()) + continue + var line = cm.getLineHandleVisualStart(range.head.line); + if (active[active.length - 1] != line) active.push(line); + } + if (sameArray(cm.state.activeLines, active)) return; + cm.operation(function() { + clearActiveLines(cm); + for (var i = 0; i < active.length; i++) { + cm.addLineClass(active[i], "wrap", WRAP_CLASS); + cm.addLineClass(active[i], "background", BACK_CLASS); + cm.addLineClass(active[i], "gutter", GUTT_CLASS); + } + cm.state.activeLines = active; + }); + } + + function selectionChange(cm, sel) { + updateActiveLines(cm, sel.ranges); + } +}); diff --git a/public/codemirror/addon/selection/selection-pointer.js b/public/codemirror/addon/selection/selection-pointer.js new file mode 100644 index 0000000..924b465 --- /dev/null +++ b/public/codemirror/addon/selection/selection-pointer.js @@ -0,0 +1,98 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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"; + + CodeMirror.defineOption("selectionPointer", false, function(cm, val) { + var data = cm.state.selectionPointer; + if (data) { + CodeMirror.off(cm.getWrapperElement(), "mousemove", data.mousemove); + CodeMirror.off(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.off(window, "scroll", data.windowScroll); + cm.off("cursorActivity", reset); + cm.off("scroll", reset); + cm.state.selectionPointer = null; + cm.display.lineDiv.style.cursor = ""; + } + if (val) { + data = cm.state.selectionPointer = { + value: typeof val == "string" ? val : "default", + mousemove: function(event) { mousemove(cm, event); }, + mouseout: function(event) { mouseout(cm, event); }, + windowScroll: function() { reset(cm); }, + rects: null, + mouseX: null, mouseY: null, + willUpdate: false + }; + CodeMirror.on(cm.getWrapperElement(), "mousemove", data.mousemove); + CodeMirror.on(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.on(window, "scroll", data.windowScroll); + cm.on("cursorActivity", reset); + cm.on("scroll", reset); + } + }); + + function mousemove(cm, event) { + var data = cm.state.selectionPointer; + if (event.buttons == null ? event.which : event.buttons) { + data.mouseX = data.mouseY = null; + } else { + data.mouseX = event.clientX; + data.mouseY = event.clientY; + } + scheduleUpdate(cm); + } + + function mouseout(cm, event) { + if (!cm.getWrapperElement().contains(event.relatedTarget)) { + var data = cm.state.selectionPointer; + data.mouseX = data.mouseY = null; + scheduleUpdate(cm); + } + } + + function reset(cm) { + cm.state.selectionPointer.rects = null; + scheduleUpdate(cm); + } + + function scheduleUpdate(cm) { + if (!cm.state.selectionPointer.willUpdate) { + cm.state.selectionPointer.willUpdate = true; + setTimeout(function() { + update(cm); + cm.state.selectionPointer.willUpdate = false; + }, 50); + } + } + + function update(cm) { + var data = cm.state.selectionPointer; + if (!data) return; + if (data.rects == null && data.mouseX != null) { + data.rects = []; + if (cm.somethingSelected()) { + for (var sel = cm.display.selectionDiv.firstChild; sel; sel = sel.nextSibling) + data.rects.push(sel.getBoundingClientRect()); + } + } + var inside = false; + if (data.mouseX != null) for (var i = 0; i < data.rects.length; i++) { + var rect = data.rects[i]; + if (rect.left <= data.mouseX && rect.right >= data.mouseX && + rect.top <= data.mouseY && rect.bottom >= data.mouseY) + inside = true; + } + var cursor = inside ? data.value : ""; + if (cm.display.lineDiv.style.cursor != cursor) + cm.display.lineDiv.style.cursor = cursor; + } +}); diff --git a/public/codemirror/lib/codemirror.css b/public/codemirror/lib/codemirror.css new file mode 100644 index 0000000..f4d5718 --- /dev/null +++ b/public/codemirror/lib/codemirror.css @@ -0,0 +1,344 @@ +/* BASICS */ + +.CodeMirror { + /* Set height, width, borders, and global font properties here */ + font-family: monospace; + height: 300px; + color: black; + direction: ltr; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre.CodeMirror-line, +.CodeMirror pre.CodeMirror-line-like { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror-cursor { + border-left: 1px solid black; + border-right: none; + width: 0; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.cm-fat-cursor .CodeMirror-cursor { + width: auto; + border: 0 !important; + background: #7e7; +} +.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} +.cm-fat-cursor .CodeMirror-line::selection, +.cm-fat-cursor .CodeMirror-line > span::selection, +.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; } +.cm-fat-cursor .CodeMirror-line::-moz-selection, +.cm-fat-cursor .CodeMirror-line > span::-moz-selection, +.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; } +.cm-fat-cursor { caret-color: transparent; } +@-moz-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@-webkit-keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} +@keyframes blink { + 0% {} + 50% { background-color: transparent; } + 100% {} +} + +/* Can style cursor different in overwrite (non-insert) mode */ +.CodeMirror-overwrite .CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-rulers { + position: absolute; + left: 0; right: 0; top: -50px; bottom: 0; + overflow: hidden; +} +.CodeMirror-ruler { + border-left: 1px solid #ccc; + top: 0; bottom: 0; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 50px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -50px; margin-right: -50px; + padding-bottom: 50px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; + z-index: 0; +} +.CodeMirror-sizer { + position: relative; + border-right: 50px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actual scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; + outline: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + min-height: 100%; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + vertical-align: top; + margin-bottom: -50px; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + background: none !important; + border: none !important; +} +.CodeMirror-gutter-background { + position: absolute; + top: 0; bottom: 0; + z-index: 4; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper ::selection { background-color: transparent } +.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre.CodeMirror-line, +.CodeMirror pre.CodeMirror-line-like { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; +} +.CodeMirror-wrap pre.CodeMirror-line, +.CodeMirror-wrap pre.CodeMirror-line-like { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + padding: 0.1px; /* Force widget margins to stay inside of the container */ +} + +.CodeMirror-widget {} + +.CodeMirror-rtl pre { direction: rtl; } + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.CodeMirror-cursor { + position: absolute; + pointer-events: none; +} +.CodeMirror-measure pre { position: static; } + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +div.CodeMirror-dragcursors { + visibility: visible; +} + +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } +.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background-color: #ffa; + background-color: rgba(255, 255, 0, .4); +} + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/public/codemirror/lib/codemirror.js b/public/codemirror/lib/codemirror.js new file mode 100644 index 0000000..5ca96da --- /dev/null +++ b/public/codemirror/lib/codemirror.js @@ -0,0 +1,9872 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/LICENSE + +// This is CodeMirror (https://codemirror.net/5), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.CodeMirror = factory()); +}(this, (function () { 'use strict'; + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + var userAgent = navigator.userAgent; + var platform = navigator.platform; + + var gecko = /gecko\/\d/i.test(userAgent); + var ie_upto10 = /MSIE \d/.test(userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); + var edge = /Edge\/(\d+)/.exec(userAgent); + var ie = ie_upto10 || ie_11up || edge; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]); + var webkit = !edge && /WebKit\//.test(userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); + var chrome = !edge && /Chrome\/(\d+)/.exec(userAgent); + var chrome_version = chrome && +chrome[1]; + var presto = /Opera\//.test(userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); + var phantom = /PhantomJS/.test(userAgent); + + var ios = safari && (/Mobile\/\w+/.test(userAgent) || navigator.maxTouchPoints > 2); + var android = /Android/.test(userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); + var mac = ios || /Mac/.test(platform); + var chromeOS = /\bCrOS\b/.test(userAgent); + var windows = /win/i.test(platform); + + var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) { presto_version = Number(presto_version[1]); } + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") } + + var rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + { e.removeChild(e.firstChild); } + return e + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e) + } + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) { e.className = className; } + if (style) { e.style.cssText = style; } + if (typeof content == "string") { e.appendChild(document.createTextNode(content)); } + else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } } + return e + } + // wrapper for elt, which removes the elt from the accessibility tree + function eltP(tag, content, className, style) { + var e = elt(tag, content, className, style); + e.setAttribute("role", "presentation"); + return e + } + + var range; + if (document.createRange) { range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r + }; } + else { range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r + }; } + + function contains(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + { child = child.parentNode; } + if (parent.contains) + { return parent.contains(child) } + do { + if (child.nodeType == 11) { child = child.host; } + if (child == parent) { return true } + } while (child = child.parentNode) + } + + function activeElt(doc) { + // IE and Edge may throw an "Unspecified Error" when accessing document.activeElement. + // IE < 10 will throw when accessed while the page is loading or in an iframe. + // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable. + var activeElement; + try { + activeElement = doc.activeElement; + } catch(e) { + activeElement = doc.body || null; + } + while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) + { activeElement = activeElement.shadowRoot.activeElement; } + return activeElement + } + + function addClass(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) { node.className += (current ? " " : "") + cls; } + } + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + { if (as[i] && !classTest(as[i]).test(b)) { b += " " + as[i]; } } + return b + } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; } + else if (ie) // Suppress mysterious IE10 errors + { selectInput = function(node) { try { node.select(); } catch(_e) {} }; } + + function doc(cm) { return cm.display.wrapper.ownerDocument } + + function win(cm) { return doc(cm).defaultView } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args)} + } + + function copyObj(obj, target, overwrite) { + if (!target) { target = {}; } + for (var prop in obj) + { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + { target[prop] = obj[prop]; } } + return target + } + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) { end = string.length; } + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + { return n + (end - i) } + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + } + + var Delayed = function() { + this.id = null; + this.f = null; + this.time = 0; + this.handler = bind(this.onTimeout, this); + }; + Delayed.prototype.onTimeout = function (self) { + self.id = 0; + if (self.time <= +new Date) { + self.f(); + } else { + setTimeout(self.handler, self.time - +new Date); + } + }; + Delayed.prototype.set = function (ms, f) { + this.f = f; + var time = +new Date + ms; + if (!this.id || time < this.time) { + clearTimeout(this.id); + this.id = setTimeout(this.handler, ms); + this.time = time; + } + }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + { if (array[i] == elt) { return i } } + return -1 + } + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 50; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = {toString: function(){return "CodeMirror.Pass"}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) { nextTab = string.length; } + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + { return pos + Math.min(skipped, goal - col) } + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) { return pos } + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + { spaceStrs.push(lst(spaceStrs) + " "); } + return spaceStrs[n] + } + + function lst(arr) { return arr[arr.length-1] } + + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); } + return out + } + + function insertSorted(array, value, score) { + var pos = 0, priority = score(value); + while (pos < array.length && score(array[pos]) <= priority) { pos++; } + array.splice(pos, 0, value); + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) { copyObj(props, inst); } + return inst + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + function isWordCharBasic(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)) + } + function isWordChar(ch, helper) { + if (!helper) { return isWordCharBasic(ch) } + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) { return true } + return helper.test(ch) + } + + function isEmpty(obj) { + for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } } + return true + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) } + + // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range. + function skipExtendingChars(str, pos, dir) { + while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; } + return pos + } + + // Returns the value from the range [`from`; `to`] that satisfies + // `pred` and is closest to `from`. Assumes that at least `to` + // satisfies `pred`. Supports `from` being greater than `to`. + function findFirst(pred, from, to) { + // At any point we are certain `to` satisfies `pred`, don't know + // whether `from` does. + var dir = from > to ? -1 : 1; + for (;;) { + if (from == to) { return from } + var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF); + if (mid == from) { return pred(mid) ? from : to } + if (pred(mid)) { to = mid; } + else { from = mid + dir; } + } + } + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) { return f(from, to, "ltr", 0) } + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr", i); + found = true; + } + } + if (!found) { f(from, to, "ltr"); } + } + + var bidiOther = null; + function getBidiPartAt(order, ch, sticky) { + var found; + bidiOther = null; + for (var i = 0; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < ch && cur.to > ch) { return i } + if (cur.to == ch) { + if (cur.from != cur.to && sticky == "before") { found = i; } + else { bidiOther = i; } + } + if (cur.from == ch) { + if (cur.from != cur.to && sticky != "before") { found = i; } + else { bidiOther = i; } + } + } + return found != null ? found : bidiOther + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6f9 + var arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111"; + function charType(code) { + if (code <= 0xf7) { return lowTypes.charAt(code) } + else if (0x590 <= code && code <= 0x5f4) { return "R" } + else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) } + else if (0x6ee <= code && code <= 0x8ac) { return "r" } + else if (0x2000 <= code && code <= 0x200b) { return "w" } + else if (code == 0x200c) { return "b" } + else { return "L" } + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str, direction) { + var outerType = direction == "ltr" ? "L" : "R"; + + if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) { return false } + var len = str.length, types = []; + for (var i = 0; i < len; ++i) + { types.push(charType(str.charCodeAt(i))); } + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) { + var type = types[i$1]; + if (type == "m") { types[i$1] = prev; } + else { prev = type; } + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) { + var type$1 = types[i$2]; + if (type$1 == "1" && cur == "r") { types[i$2] = "n"; } + else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == "r") { types[i$2] = "R"; } } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) { + var type$2 = types[i$3]; + if (type$2 == "+" && prev$1 == "1" && types[i$3+1] == "1") { types[i$3] = "1"; } + else if (type$2 == "," && prev$1 == types[i$3+1] && + (prev$1 == "1" || prev$1 == "n")) { types[i$3] = prev$1; } + prev$1 = type$2; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i$4 = 0; i$4 < len; ++i$4) { + var type$3 = types[i$4]; + if (type$3 == ",") { types[i$4] = "N"; } + else if (type$3 == "%") { + var end = (void 0); + for (end = i$4 + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i$4 && types[i$4-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i$4; j < end; ++j) { types[j] = replace; } + i$4 = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) { + var type$4 = types[i$5]; + if (cur$1 == "L" && type$4 == "1") { types[i$5] = "L"; } + else if (isStrong.test(type$4)) { cur$1 = type$4; } + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i$6 = 0; i$6 < len; ++i$6) { + if (isNeutral.test(types[i$6])) { + var end$1 = (void 0); + for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {} + var before = (i$6 ? types[i$6-1] : outerType) == "L"; + var after = (end$1 < len ? types[end$1] : outerType) == "L"; + var replace$1 = before == after ? (before ? "L" : "R") : outerType; + for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; } + i$6 = end$1 - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i$7 = 0; i$7 < len;) { + if (countsAsLeft.test(types[i$7])) { + var start = i$7; + for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {} + order.push(new BidiSpan(0, start, i$7)); + } else { + var pos = i$7, at = order.length, isRTL = direction == "rtl" ? 1 : 0; + for (++i$7; i$7 < len && types[i$7] != "L"; ++i$7) {} + for (var j$2 = pos; j$2 < i$7;) { + if (countsAsNum.test(types[j$2])) { + if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); at += isRTL; } + var nstart = j$2; + for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {} + order.splice(at, 0, new BidiSpan(2, nstart, j$2)); + at += isRTL; + pos = j$2; + } else { ++j$2; } + } + if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); } + } + } + if (direction == "ltr") { + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + } + + return direction == "rtl" ? order.reverse() : order + } + })(); + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line, direction) { + var order = line.order; + if (order == null) { order = line.order = bidiOrdering(line.text, direction); } + return order + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var noHandlers = []; + + var on = function(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false); + } else if (emitter.attachEvent) { + emitter.attachEvent("on" + type, f); + } else { + var map = emitter._handlers || (emitter._handlers = {}); + map[type] = (map[type] || noHandlers).concat(f); + } + }; + + function getHandlers(emitter, type) { + return emitter._handlers && emitter._handlers[type] || noHandlers + } + + function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false); + } else if (emitter.detachEvent) { + emitter.detachEvent("on" + type, f); + } else { + var map = emitter._handlers, arr = map && map[type]; + if (arr) { + var index = indexOf(arr, f); + if (index > -1) + { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); } + } + } + } + + function signal(emitter, type /*, values...*/) { + var handlers = getHandlers(emitter, type); + if (!handlers.length) { return } + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); } + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; } + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) { return } + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1) + { set.push(arr[i]); } } + } + + function hasHandler(emitter, type) { + return getHandlers(emitter, type).length > 0 + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + function e_preventDefault(e) { + if (e.preventDefault) { e.preventDefault(); } + else { e.returnValue = false; } + } + function e_stopPropagation(e) { + if (e.stopPropagation) { e.stopPropagation(); } + else { e.cancelBubble = true; } + } + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false + } + function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} + + function e_target(e) {return e.target || e.srcElement} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) { b = 1; } + else if (e.button & 2) { b = 3; } + else if (e.button & 4) { b = 2; } + } + if (mac && e.ctrlKey && b == 1) { b = 3; } + return b + } + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) { return false } + var div = elt('div'); + return "draggable" in div || "dragDrop" in div + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); } + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) { return badBidiRects } + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + var r1 = range(txt, 1, 2).getBoundingClientRect(); + removeChildren(measure); + if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3) + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? function (string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) { nl = string.length; } + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result + } : function (string) { return string.split(/\r\n?|\n/); }; + + var hasSelection = window.getSelection ? function (te) { + try { return te.selectionStart != te.selectionEnd } + catch(e) { return false } + } : function (te) { + var range; + try {range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) { return false } + return range.compareEndPoints("StartToEnd", range) != 0 + }; + + var hasCopyEvent = (function () { + var e = elt("div"); + if ("oncopy" in e) { return true } + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function" + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) { return badZoomedRects } + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 + } + + // Known modes, by name and by MIME + var modes = {}, mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + function defineMode(name, mode) { + if (arguments.length > 2) + { mode.dependencies = Array.prototype.slice.call(arguments, 2); } + modes[name] = mode; + } + + function defineMIME(mime, spec) { + mimeModes[mime] = spec; + } + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + function resolveMode(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") { found = {name: found}; } + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return resolveMode("application/xml") + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { + return resolveMode("application/json") + } + if (typeof spec == "string") { return {name: spec} } + else { return spec || {name: "null"} } + } + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + function getMode(options, spec) { + spec = resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) { return getMode(options, "text/plain") } + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) { continue } + if (modeObj.hasOwnProperty(prop)) { modeObj["_" + prop] = modeObj[prop]; } + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) { modeObj.helperType = spec.helperType; } + if (spec.modeProps) { for (var prop$1 in spec.modeProps) + { modeObj[prop$1] = spec.modeProps[prop$1]; } } + + return modeObj + } + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = {}; + function extendMode(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + } + + function copyState(mode, state) { + if (state === true) { return state } + if (mode.copyState) { return mode.copyState(state) } + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) { val = val.concat([]); } + nstate[n] = val; + } + return nstate + } + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + function innerMode(mode, state) { + var info; + while (mode.innerMode) { + info = mode.innerMode(state); + if (!info || info.mode == mode) { break } + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state} + } + + function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true + } + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = function(string, tabSize, lineOracle) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + this.lineOracle = lineOracle; + }; + + StringStream.prototype.eol = function () {return this.pos >= this.string.length}; + StringStream.prototype.sol = function () {return this.pos == this.lineStart}; + StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined}; + StringStream.prototype.next = function () { + if (this.pos < this.string.length) + { return this.string.charAt(this.pos++) } + }; + StringStream.prototype.eat = function (match) { + var ch = this.string.charAt(this.pos); + var ok; + if (typeof match == "string") { ok = ch == match; } + else { ok = ch && (match.test ? match.test(ch) : match(ch)); } + if (ok) {++this.pos; return ch} + }; + StringStream.prototype.eatWhile = function (match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start + }; + StringStream.prototype.eatSpace = function () { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this.pos; } + return this.pos > start + }; + StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;}; + StringStream.prototype.skipTo = function (ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true} + }; + StringStream.prototype.backUp = function (n) {this.pos -= n;}; + StringStream.prototype.column = function () { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }; + StringStream.prototype.indentation = function () { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + }; + StringStream.prototype.match = function (pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; }; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) { this.pos += pattern.length; } + return true + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) { return null } + if (match && consume !== false) { this.pos += match[0].length; } + return match + } + }; + StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)}; + StringStream.prototype.hideFirstChars = function (n, inner) { + this.lineStart += n; + try { return inner() } + finally { this.lineStart -= n; } + }; + StringStream.prototype.lookAhead = function (n) { + var oracle = this.lineOracle; + return oracle && oracle.lookAhead(n) + }; + StringStream.prototype.baseToken = function () { + var oracle = this.lineOracle; + return oracle && oracle.baseToken(this.pos) + }; + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) { throw new Error("There is no line " + (n + doc.first) + " in the document.") } + var chunk = doc; + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break } + n -= sz; + } + } + return chunk.lines[n] + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function (line) { + var text = line.text; + if (n == end.line) { text = text.slice(0, end.ch); } + if (n == start.line) { text = text.slice(start.ch); } + out.push(text); + ++n; + }); + return out + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value + return out + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } } + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) { return null } + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) { break } + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) { + var child = chunk.children[i$1], ch = child.height; + if (h < ch) { chunk = child; continue outer } + h -= ch; + n += child.chunkSize(); + } + return n + } while (!chunk.lines) + var i = 0; + for (; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) { break } + h -= lh; + } + return n + i + } + + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size} + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)) + } + + // A Pos instance represents a position within the text. + function Pos(line, ch, sticky) { + if ( sticky === void 0 ) sticky = null; + + if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) } + this.line = line; + this.ch = ch; + this.sticky = sticky; + } + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + + function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + + function copyPos(x) {return Pos(x.line, x.ch)} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} + function clipPos(doc, pos) { + if (pos.line < doc.first) { return Pos(doc.first, 0) } + var last = doc.first + doc.size - 1; + if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) } + return clipToLen(pos, getLine(doc, pos.line).text.length) + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) { return Pos(pos.line, linelen) } + else if (ch < 0) { return Pos(pos.line, 0) } + else { return pos } + } + function clipPosArray(doc, array) { + var out = []; + for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); } + return out + } + + var SavedContext = function(state, lookAhead) { + this.state = state; + this.lookAhead = lookAhead; + }; + + var Context = function(doc, state, line, lookAhead) { + this.state = state; + this.doc = doc; + this.line = line; + this.maxLookAhead = lookAhead || 0; + this.baseTokens = null; + this.baseTokenPos = 1; + }; + + Context.prototype.lookAhead = function (n) { + var line = this.doc.getLine(this.line + n); + if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; } + return line + }; + + Context.prototype.baseToken = function (n) { + if (!this.baseTokens) { return null } + while (this.baseTokens[this.baseTokenPos] <= n) + { this.baseTokenPos += 2; } + var type = this.baseTokens[this.baseTokenPos + 1]; + return {type: type && type.replace(/( |^)overlay .*/, ""), + size: this.baseTokens[this.baseTokenPos] - n} + }; + + Context.prototype.nextLine = function () { + this.line++; + if (this.maxLookAhead > 0) { this.maxLookAhead--; } + }; + + Context.fromSaved = function (doc, saved, line) { + if (saved instanceof SavedContext) + { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) } + else + { return new Context(doc, copyState(doc.mode, saved), line) } + }; + + Context.prototype.save = function (copy) { + var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state; + return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state + }; + + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, context, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); }, + lineClasses, forceToEnd); + var state = context.state; + + // Run overlays, adjust style array. + var loop = function ( o ) { + context.baseTokens = st; + var overlay = cm.state.overlays[o], i = 1, at = 0; + context.state = true; + runMode(cm, line.text, overlay.mode, context, function (end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + { st.splice(i, 1, end, st[i+1], i_end); } + i += 2; + at = Math.min(end, i_end); + } + if (!style) { return } + if (overlay.opaque) { + st.splice(start, i - start, end, "overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "overlay " + style; + } + } + }, lineClasses); + context.state = state; + context.baseTokens = null; + context.baseTokenPos = 1; + }; + + for (var o = 0; o < cm.state.overlays.length; ++o) loop( o ); + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var context = getContextBefore(cm, lineNo(line)); + var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state); + var result = highlightLine(cm, line, context); + if (resetState) { context.state = resetState; } + line.stateAfter = context.save(!resetState); + line.styles = result.styles; + if (result.classes) { line.styleClasses = result.classes; } + else if (line.styleClasses) { line.styleClasses = null; } + if (updateFrontier === cm.doc.highlightFrontier) + { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); } + } + return line.styles + } + + function getContextBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) { return new Context(doc, true, n) } + var start = findStartLine(cm, n, precise); + var saved = start > doc.first && getLine(doc, start - 1).stateAfter; + var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start); + + doc.iter(start, n, function (line) { + processLine(cm, line.text, context); + var pos = context.line; + line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null; + context.nextLine(); + }); + if (precise) { doc.modeFrontier = context.line; } + return context + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, context, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize, context); + stream.start = stream.pos = startAt || 0; + if (text == "") { callBlankLine(mode, context.state); } + while (!stream.eol()) { + readToken(mode, stream, context.state); + stream.start = stream.pos; + } + } + + function callBlankLine(mode, state) { + if (mode.blankLine) { return mode.blankLine(state) } + if (!mode.innerMode) { return } + var inner = innerMode(mode, state); + if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) } + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) { inner[0] = innerMode(mode, state).mode; } + var style = mode.token(stream, state); + if (stream.pos > stream.start) { return style } + } + throw new Error("Mode " + mode.name + " failed to advance stream.") + } + + var Token = function(stream, type, state) { + this.start = stream.start; this.end = stream.pos; + this.string = stream.current(); + this.type = type || null; + this.state = state; + }; + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize, context), tokens; + if (asArray) { tokens = []; } + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, context.state); + if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); } + } + return asArray ? tokens : new Token(stream, style, context.state) + } + + function extractLineClasses(type, output) { + if (type) { for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) { break } + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + { output[prop] = lineClass[2]; } + else if (!(new RegExp("(?:^|\\s)" + lineClass[2] + "(?:$|\\s)")).test(output[prop])) + { output[prop] += " " + lineClass[2]; } + } } + return type + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; } + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize, context), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); } + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) { processLine(cm, text, context, stream.pos); } + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) { style = "m-" + (style ? mName + " " + style : mName); } + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 5000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 + // characters, and returns inaccurate measurements in nodes + // starting around 5000 chars. + var pos = Math.min(stream.pos, curStart + 5000); + f(pos, curStyle); + curStart = pos; + } + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) { return doc.first } + var line = getLine(doc, search - 1), after = line.stateAfter; + if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) + { return search } + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline + } + + function retreatFrontier(doc, n) { + doc.modeFrontier = Math.min(doc.modeFrontier, n); + if (doc.highlightFrontier < n - 10) { return } + var start = doc.first; + for (var line = n - 1; line > start; line--) { + var saved = getLine(doc, line).stateAfter; + // change is on 3 + // state on line 1 looked ahead 2 -- so saw 3 + // test 1 + 2 < 3 should cover this + if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { + start = line + 1; + break + } + } + doc.highlightFrontier = Math.min(doc.highlightFrontier, start); + } + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + function seeReadOnlySpans() { + sawReadOnlySpans = true; + } + + function seeCollapsedSpans() { + sawCollapsedSpans = true; + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) { return span } + } } + } + + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + var r; + for (var i = 0; i < spans.length; ++i) + { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } } + return r + } + + // Add a span to a line. + function addMarkedSpan(line, span, op) { + var inThisOp = op && window.WeakSet && (op.markedSpans || (op.markedSpans = new WeakSet)); + if (inThisOp && line.markedSpans && inThisOp.has(line.markedSpans)) { + line.markedSpans.push(span); + } else { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + if (inThisOp) { inThisOp.add(line.markedSpans); } + } + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + var nw; + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } } + return nw + } + function markedSpansAfter(old, endCh, isInsert) { + var nw; + if (old) { for (var i = 0; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } } + return nw + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) { return null } + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) { return null } + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) { span.to = startCh; } + else if (sameLine) { span.to = found.to == null ? null : found.to + offset; } + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i$1 = 0; i$1 < last.length; ++i$1) { + var span$1 = last[i$1]; + if (span$1.to != null) { span$1.to += offset; } + if (span$1.from == null) { + var found$1 = getMarkedSpanFor(first, span$1.marker); + if (!found$1) { + span$1.from = offset; + if (sameLine) { (first || (first = [])).push(span$1); } + } + } else { + span$1.from += offset; + if (sameLine) { (first || (first = [])).push(span$1); } + } + } + } + // Make sure we didn't create any zero-length spans + if (first) { first = clearEmptySpans(first); } + if (last && last != first) { last = clearEmptySpans(last); } + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + { for (var i$2 = 0; i$2 < first.length; ++i$2) + { if (first[i$2].to == null) + { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } } + for (var i$3 = 0; i$3 < gap; ++i$3) + { newMarkers.push(gapMarkers); } + newMarkers.push(last); + } + return newMarkers + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + { spans.splice(i--, 1); } + } + if (!spans.length) { return null } + return spans + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function (line) { + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + { (markers || (markers = [])).push(mark); } + } } + }); + if (!markers) { return null } + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue } + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + { newParts.push({from: p.from, to: m.from}); } + if (dto > 0 || !mk.inclusiveRight && !dto) + { newParts.push({from: m.to, to: p.to}); } + parts.splice.apply(parts, newParts); + j += newParts.length - 3; + } + } + return parts + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.detachLine(line); } + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) { return } + for (var i = 0; i < spans.length; ++i) + { spans[i].marker.attachLine(line); } + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) { return lenDiff } + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) { return -fromCmp } + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) { return toCmp } + return b.id - a.id + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + { found = sp.marker; } + } } + return found + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + + function collapsedSpanAround(line, ch) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; } + } } + return found + } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) { for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) { continue } + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue } + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + { return true } + } } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + { line = merged.find(-1, true).line; } + return line + } + + function visualLineEnd(line) { + var merged; + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line; } + return line + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line); + } + return lines + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) { return lineN } + return lineNo(vis) + } + + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) { return lineN } + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) { return lineN } + while (merged = collapsedSpanAtEnd(line)) + { line = merged.find(1, true).line; } + return lineNo(line) + 1 + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) { continue } + if (sp.from == null) { return true } + if (sp.marker.widgetNode) { continue } + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + { return true } + } } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + { return true } + for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) { return true } + } + } + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) { break } + else { h += line.height; } + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i$1 = 0; i$1 < p.children.length; ++i$1) { + var cur = p.children[i$1]; + if (cur == chunk) { break } + else { h += cur.height; } + } + } + return h + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) { return 0 } + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found$1 = merged.find(0, true); + len -= cur.text.length - found$1.from.ch; + cur = found$1.to.line; + len += cur.text.length - found$1.to.ch; + } + return len + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function (line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + + Line.prototype.lineNo = function () { return lineNo(this) }; + eventMixin(Line); + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) { line.stateAfter = null; } + if (line.styles) { line.styles = null; } + if (line.order != null) { line.order = null; } + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) { updateLineHeight(line, estHeight); } + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) { return null } + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")) + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = eltP("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0); + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) + { builder.addToken = buildTokenBadBidi(builder.addToken, order); } + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); } + if (line.styleClasses.textClass) + { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); } + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); } + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) + ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit) { + var last = builder.content.lastChild; + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + { builder.content.className = "cm-tab-wrap-hack"; } + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); } + + return builder + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, css, attributes) { + if (!text) { return } + var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text; + var special = builder.cm.state.specialChars, mustWrap = false; + var content; + if (!special.test(text)) { + builder.col += text.length; + content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) { mustWrap = true; } + builder.pos += text.length; + } else { + content = document.createDocumentFragment(); + var pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt])); } + else { content.appendChild(txt); } + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) { break } + pos += skipped + 1; + var txt$1 = (void 0); + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + txt$1 = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt$1.setAttribute("role", "presentation"); + txt$1.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else if (m[0] == "\r" || m[0] == "\n") { + txt$1 = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); + txt$1.setAttribute("cm-text", m[0]); + builder.col += 1; + } else { + txt$1 = builder.cm.options.specialCharPlaceholder(m[0]); + txt$1.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) { content.appendChild(elt("span", [txt$1])); } + else { content.appendChild(txt$1); } + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt$1); + builder.pos++; + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32; + if (style || startStyle || endStyle || mustWrap || css || attributes) { + var fullStyle = style || ""; + if (startStyle) { fullStyle += startStyle; } + if (endStyle) { fullStyle += endStyle; } + var token = elt("span", [content], fullStyle, css); + if (attributes) { + for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class") + { token.setAttribute(attr, attributes[attr]); } } + } + return builder.content.appendChild(token) + } + builder.content.appendChild(content); + } + + // Change some spaces to NBSP to prevent the browser from collapsing + // trailing spaces at the end of a line when rendering text (issue #1362). + function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) { return text } + var spaceBefore = trailingBefore, result = ""; + for (var i = 0; i < text.length; i++) { + var ch = text.charAt(i); + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + { ch = "\u00a0"; } + result += ch; + spaceBefore = ch == " "; + } + return result + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function (builder, text, style, startStyle, endStyle, css, attributes) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + var part = (void 0); + for (var i = 0; i < order.length; i++) { + part = order[i]; + if (part.to > start && part.from <= start) { break } + } + if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) } + inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + } + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); } + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + { widget = builder.content.appendChild(document.createElement("span")); } + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + builder.trailingSpace = false; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i$1 = 1; i$1 < styles.length; i$1+=2) + { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); } + return + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = css = ""; + attributes = null; + collapsed = null; nextChange = Infinity; + var foundBookmarks = [], endStyles = (void 0); + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m); + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to; + spanEndStyle = ""; + } + if (m.className) { spanStyle += " " + m.className; } + if (m.css) { css = (css ? css + ";" : "") + m.css; } + if (m.startStyle && sp.from == pos) { spanStartStyle += " " + m.startStyle; } + if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); } + // support for the old title property + // https://github.com/codemirror/CodeMirror/pull/5673 + if (m.title) { (attributes || (attributes = {})).title = m.title; } + if (m.attributes) { + for (var attr in m.attributes) + { (attributes || (attributes = {}))[attr] = m.attributes[attr]; } + } + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + { collapsed = sp; } + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + } + if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2) + { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += " " + endStyles[j$1]; } } } + + if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2) + { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) { return } + if (collapsed.to == pos) { collapsed = false; } + } + } + if (pos >= len) { break } + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", css, attributes); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array + } + + var operationGroup = null; + + function pushOperation(op) { + if (operationGroup) { + operationGroup.ops.push(op); + } else { + op.ownsGroup = operationGroup = { + ops: [op], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + { callbacks[i].call(null); } + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + { while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } } + } + } while (i < callbacks.length) + } + + function finishOperation(op, endCb) { + var group = op.ownsGroup; + if (!group) { return } + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + endCb(group); + } + } + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = getHandlers(emitter, type); + if (!arr.length) { return } + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + var loop = function ( i ) { + list.push(function () { return arr[i].apply(null, args); }); + }; + + for (var i = 0; i < arr.length; ++i) + loop( i ); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) { delayed[i](); } + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") { updateLineText(cm, lineView); } + else if (type == "gutter") { updateLineGutter(cm, lineView, lineN, dims); } + else if (type == "class") { updateLineClasses(cm, lineView); } + else if (type == "widget") { updateLineWidgets(cm, lineView, dims); } + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); } + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; } + } + return lineView.node + } + + function updateLineBackground(cm, lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) { cls += " CodeMirror-linebackground"; } + if (lineView.background) { + if (cls) { lineView.background.className = cls; } + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + cm.display.input.setUneditable(lineView.background); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built + } + return buildLineContent(cm, lineView) + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) { lineView.node = built.pre; } + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(cm, lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(cm, lineView) { + updateLineBackground(cm, lineView); + if (lineView.line.wrapClass) + { ensureLineWrapped(lineView).className = lineView.line.wrapClass; } + else if (lineView.node != lineView.text) + { lineView.node.className = ""; } + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + if (lineView.gutterBackground) { + lineView.node.removeChild(lineView.gutterBackground); + lineView.gutterBackground = null; + } + if (lineView.line.gutterClass) { + var wrap = ensureLineWrapped(lineView); + lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, + ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px; width: " + (dims.gutterTotalWidth) + "px")); + cm.display.input.setUneditable(lineView.gutterBackground); + wrap.insertBefore(lineView.gutterBackground, lineView.text); + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap$1 = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px")); + gutterWrap.setAttribute("aria-hidden", "true"); + cm.display.input.setUneditable(gutterWrap); + wrap$1.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + { gutterWrap.className += " " + lineView.line.gutterClass; } + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + { lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft["CodeMirror-linenumbers"]) + "px; width: " + (cm.display.lineNumInnerWidth) + "px"))); } + if (markers) { for (var k = 0; k < cm.display.gutterSpecs.length; ++k) { + var id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id]; + if (found) + { gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", + ("left: " + (dims.gutterLeft[id]) + "px; width: " + (dims.gutterWidth[id]) + "px"))); } + } } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) { lineView.alignable = null; } + var isWidget = classTest("CodeMirror-linewidget"); + for (var node = lineView.node.firstChild, next = (void 0); node; node = next) { + next = node.nextSibling; + if (isWidget.test(node.className)) { lineView.node.removeChild(node); } + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) { lineView.bgClass = built.bgClass; } + if (built.textClass) { lineView.textClass = built.textClass; } + + updateLineClasses(cm, lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } } + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) { return } + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget" + (widget.className ? " " + widget.className : "")); + if (!widget.handleMouseEvents) { node.setAttribute("cm-ignore-events", "true"); } + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + { wrap.insertBefore(node, lineView.gutter || lineView.text); } + else + { wrap.appendChild(node); } + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + "px"; } + } + } + + function widgetHeight(widget) { + if (widget.height != null) { return widget.height } + var cm = widget.doc.cm; + if (!cm) { return 0 } + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + { parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; } + if (widget.noHScroll) + { parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; } + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.parentNode.offsetHeight + } + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + { return true } + } + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight} + function paddingH(display) { + if (display.cachedPaddingH) { return display.cachedPaddingH } + var e = removeChildrenAndAdd(display.measure, elt("pre", "x", "CodeMirror-line-like")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; } + return data + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + { heights.push((cur.bottom + next.top) / 2 - rect.top); } + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + { return {map: lineView.measure.map, cache: lineView.measure.cache} } + if (lineView.rest) { + for (var i = 0; i < lineView.rest.length; i++) + { if (lineView.rest[i] == line) + { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } } + for (var i$1 = 0; i$1 < lineView.rest.length; i$1++) + { if (lineNo(lineView.rest[i$1]) > lineN) + { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } } + } + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias) + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + { return cm.display.view[findViewIndex(cm, lineN)] } + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + { return ext } + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) { + view = null; + } else if (view && view.changes) { + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + cm.curOp.forceUpdate = true; + } + if (!view) + { view = updateExternalMeasurement(cm, line); } + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + } + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) { ch = -1; } + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + { prepared.rect = prepared.view.text.getBoundingClientRect(); } + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) { prepared.cache[key] = found; } + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom} + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse, mStart, mEnd; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + mStart = map[i]; + mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) { collapse = "right"; } + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + { collapse = bias; } + if (bias == "left" && start == 0) + { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } } + if (bias == "right" && start == mEnd - mStart) + { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } } + break + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd} + } + + function getUsefulRect(rects, bias) { + var rect = nullRect; + if (bias == "left") { for (var i = 0; i < rects.length; i++) { + if ((rect = rects[i]).left != rect.right) { break } + } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) { + if ((rect = rects[i$1]).left != rect.right) { break } + } } + return rect + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; } + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; } + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) + { rect = node.parentNode.getBoundingClientRect(); } + else + { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); } + if (rect.left || rect.right || start == 0) { break } + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); } + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) { collapse = bias = "right"; } + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + { rect = rects[bias == "right" ? rects.length - 1 : 0]; } + else + { rect = node.getBoundingClientRect(); } + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; } + else + { rect = nullRect; } + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + var i = 0; + for (; i < heights.length - 1; i++) + { if (mid < heights[i]) { break } } + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) { result.bogus = true; } + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + { return rect } + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY} + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++) + { lineView.measure.caches[i] = {}; } } + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + { clearLineMeasurementCacheFor(cm.display.view[i]); } + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; } + cm.display.lineNumChars = null; + } + + function pageScrollX(doc) { + // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206 + // which causes page_Offset and bounding client rects to use + // different reference viewports and invalidate our calculations. + if (chrome && android) { return -(doc.body.getBoundingClientRect().left - parseInt(getComputedStyle(doc.body).marginLeft)) } + return doc.defaultView.pageXOffset || (doc.documentElement || doc.body).scrollLeft + } + function pageScrollY(doc) { + if (chrome && android) { return -(doc.body.getBoundingClientRect().top - parseInt(getComputedStyle(doc.body).marginTop)) } + return doc.defaultView.pageYOffset || (doc.documentElement || doc.body).scrollTop + } + + function widgetTopHeight(lineObj) { + var ref = visualLine(lineObj); + var widgets = ref.widgets; + var height = 0; + if (widgets) { for (var i = 0; i < widgets.length; ++i) { if (widgets[i].above) + { height += widgetHeight(widgets[i]); } } } + return height + } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"./null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) { + if (!includeWidgets) { + var height = widgetTopHeight(lineObj); + rect.top += height; rect.bottom += height; + } + if (context == "line") { return rect } + if (!context) { context = "local"; } + var yOff = heightAtLine(lineObj); + if (context == "local") { yOff += paddingTop(cm.display); } + else { yOff -= cm.display.viewOffset; } + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY(doc(cm))); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX(doc(cm))); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"./null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") { return coords } + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(doc(cm)); + top -= pageScrollY(doc(cm)); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top} + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) { lineObj = getLine(cm.doc, pos.line); } + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context) + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + // A cursor Pos(line, char, "before") is on the same visual line as `char - 1` + // and after `char - 1` in writing order of `char - 1` + // A cursor Pos(line, char, "after") is on the same visual line as `char` + // and before `char` in writing order of `char` + // Examples (upper-case letters are RTL, lower-case are LTR): + // Pos(0, 1, ...) + // before after + // ab a|b a|b + // aB a|B aB| + // Ab |Ab A|b + // AB B|A B|A + // Every position after the last character on a line is considered to stick + // to the last character on the line. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) { m.left = m.right; } else { m.right = m.left; } + return intoCoordSystem(cm, lineObj, m, context) + } + var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky; + if (ch >= lineObj.text.length) { + ch = lineObj.text.length; + sticky = "before"; + } else if (ch <= 0) { + ch = 0; + sticky = "after"; + } + if (!order) { return get(sticky == "before" ? ch - 1 : ch, sticky == "before") } + + function getBidi(ch, partPos, invert) { + var part = order[partPos], right = part.level == 1; + return get(invert ? ch - 1 : ch, right != invert) + } + var partPos = getBidiPartAt(order, ch, sticky); + var other = bidiOther; + var val = getBidi(ch, partPos, sticky == "before"); + if (other != null) { val.other = getBidi(ch, other, sticky != "before"); } + return val + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0; + pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; } + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height} + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, sticky, outside, xRel) { + var pos = Pos(line, ch, sticky); + pos.xRel = xRel; + if (outside) { pos.outside = outside; } + return pos + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) { return PosWithInfo(doc.first, 0, null, -1, -1) } + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) } + if (x < 0) { x = 0; } + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0)); + if (!collapsed) { return found } + var rangeEnd = collapsed.find(1); + if (rangeEnd.line == lineN) { return rangeEnd } + lineObj = getLine(doc, lineN = rangeEnd.line); + } + } + + function wrappedLineExtent(cm, lineObj, preparedMeasure, y) { + y -= widgetTopHeight(lineObj); + var end = lineObj.text.length; + var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0); + end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end); + return {begin: begin, end: end} + } + + function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) { + if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); } + var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top; + return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop) + } + + // Returns true if the given side of a box is after the given + // coordinates, in top-to-bottom, left-to-right order. + function boxIsAfter(box, x, y, left) { + return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + // Move y into line-local coordinate space + y -= heightAtLine(lineObj); + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + // When directly calling `measureCharPrepared`, we have to adjust + // for the widgets at this line. + var widgetHeight = widgetTopHeight(lineObj); + var begin = 0, end = lineObj.text.length, ltr = true; + + var order = getOrder(lineObj, cm.doc.direction); + // If the line isn't plain left-to-right text, first figure out + // which bidi section the coordinates fall into. + if (order) { + var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart) + (cm, lineObj, lineNo, preparedMeasure, order, x, y); + ltr = part.level != 1; + // The awkward -1 offsets are needed because findFirst (called + // on these below) will treat its first bound as inclusive, + // second as exclusive, but we want to actually address the + // characters in the part's range + begin = ltr ? part.from : part.to - 1; + end = ltr ? part.to : part.from - 1; + } + + // A binary search to find the first character whose bounding box + // starts after the coordinates. If we run across any whose box wrap + // the coordinates, store that. + var chAround = null, boxAround = null; + var ch = findFirst(function (ch) { + var box = measureCharPrepared(cm, preparedMeasure, ch); + box.top += widgetHeight; box.bottom += widgetHeight; + if (!boxIsAfter(box, x, y, false)) { return false } + if (box.top <= y && box.left <= x) { + chAround = ch; + boxAround = box; + } + return true + }, begin, end); + + var baseX, sticky, outside = false; + // If a box around the coordinates was found, use that + if (boxAround) { + // Distinguish coordinates nearer to the left or right side of the box + var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr; + ch = chAround + (atStart ? 0 : 1); + sticky = atStart ? "after" : "before"; + baseX = atLeft ? boxAround.left : boxAround.right; + } else { + // (Adjust for extended bound, if necessary.) + if (!ltr && (ch == end || ch == begin)) { ch++; } + // To determine which side to associate with, get the box to the + // left of the character and compare it's vertical position to the + // coordinates + sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" : + (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ? + "after" : "before"; + // Now get accurate coordinates for this place, in order to get a + // base X position + var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure); + baseX = coords.left; + outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0; + } + + ch = skipExtendingChars(lineObj.text, ch, 1); + return PosWithInfo(lineNo, ch, sticky, outside, x - baseX) + } + + function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) { + // Bidi parts are sorted left-to-right, and in a non-line-wrapping + // situation, we can take this ordering to correspond to the visual + // ordering. This finds the first part whose end is after the given + // coordinates. + var index = findFirst(function (i) { + var part = order[i], ltr = part.level != 1; + return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"), + "line", lineObj, preparedMeasure), x, y, true) + }, 0, order.length - 1); + var part = order[index]; + // If this isn't the first part, the part's start is also after + // the coordinates, and the coordinates aren't on the same line as + // that start, move one part back. + if (index > 0) { + var ltr = part.level != 1; + var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"), + "line", lineObj, preparedMeasure); + if (boxIsAfter(start, x, y, true) && start.top > y) + { part = order[index - 1]; } + } + return part + } + + function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) { + // In a wrapped line, rtl text on wrapping boundaries can do things + // that don't correspond to the ordering in our `order` array at + // all, so a binary search doesn't work, and we want to return a + // part that only spans one line so that the binary search in + // coordsCharInner is safe. As such, we first find the extent of the + // wrapped line, and then do a flat search in which we discard any + // spans that aren't on the line. + var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y); + var begin = ref.begin; + var end = ref.end; + if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; } + var part = null, closestDist = null; + for (var i = 0; i < order.length; i++) { + var p = order[i]; + if (p.from >= end || p.to <= begin) { continue } + var ltr = p.level != 1; + var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right; + // Weigh against spans ending before this, so that they are only + // picked if nothing ends after + var dist = endX < x ? x - endX + 1e9 : endX - x; + if (!part || closestDist > dist) { + part = p; + closestDist = dist; + } + } + if (!part) { part = order[order.length - 1]; } + // Clip the part to the wrapped line. + if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; } + if (part.to > end) { part = {from: part.from, to: end, level: part.level}; } + return part + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) { return display.cachedTextHeight } + if (measureText == null) { + measureText = elt("pre", null, "CodeMirror-line-like"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) { display.cachedTextHeight = height; } + removeChildren(display.measure); + return height || 1 + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) { return display.cachedCharWidth } + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor], "CodeMirror-line-like"); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) { display.cachedCharWidth = width; } + return width || 10 + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + var id = cm.display.gutterSpecs[i].className; + left[id] = n.offsetLeft + n.clientLeft + gutterLeft; + width[id] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth} + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function (line) { + if (lineIsHidden(cm.doc, line)) { return 0 } + + var widgetsHeight = 0; + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; } + } } + + if (wrapping) + { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th } + else + { return widgetsHeight + th } + } + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function (line) { + var estHeight = est(line); + if (estHeight != line.height) { updateLineHeight(line, estHeight); } + }); + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") { return null } + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e$1) { return null } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel > 0 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) { return null } + n -= cm.display.viewFrom; + if (n < 0) { return null } + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) { return i } + } + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) { from = cm.doc.first; } + if (to == null) { to = cm.doc.first + cm.doc.size; } + if (!lendiff) { lendiff = 0; } + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + { display.updateLineNumbers = from; } + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + { resetView(cm); } + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut$1 = viewCuttingPoint(cm, from, from, -1); + if (cut$1) { + display.view = display.view.slice(0, cut$1.index); + display.viewTo = cut$1.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + { ext.lineN += lendiff; } + else if (from < ext.lineN + ext.size) + { display.externalMeasured = null; } + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + { display.externalMeasured = null; } + + if (line < display.viewFrom || line >= display.viewTo) { return } + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) { return } + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) { arr.push(type); } + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + { return {index: index, lineN: newN} } + var n = cm.display.viewFrom; + for (var i = 0; i < index; i++) + { n += view[i].size; } + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) { return null } + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) { return null } + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN} + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); } + else if (display.viewFrom < from) + { display.view = display.view.slice(findViewIndex(cm, from)); } + display.viewFrom = from; + if (display.viewTo < to) + { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); } + else if (display.viewTo > to) + { display.view = display.view.slice(0, findViewIndex(cm, to)); } + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; } + } + return dirty + } + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + if ( primary === void 0 ) primary = true; + + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + var customCursor = cm.options.$customCursor; + if (customCursor) { primary = true; } + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (!primary && i == doc.sel.primIndex) { continue } + var range = doc.sel.ranges[i]; + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue } + var collapsed = range.empty(); + if (customCursor) { + var head = customCursor(cm, range); + if (head) { drawSelectionCursor(cm, head, curFragment); } + } else if (collapsed || cm.options.showCursorWhenSelecting) { + drawSelectionCursor(cm, range.head, curFragment); + } + if (!collapsed) + { drawSelectionRange(cm, range, selFragment); } + } + return result + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, head, output) { + var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (/\bcm-fat-cursor\b/.test(cm.getWrapperElement().className)) { + var charPos = charCoords(cm, head, "div", null, null); + var width = charPos.right - charPos.left; + cursor.style.width = (width > 0 ? width : cm.defaultCharWidth()) + "px"; + } + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + var docLTR = doc.direction == "ltr"; + + function add(left, top, width, bottom) { + if (top < 0) { top = 0; } + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n height: " + (bottom - top) + "px"))); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias) + } + + function wrapX(pos, dir, side) { + var extent = wrappedLineExtentChar(cm, lineObj, null, pos); + var prop = (dir == "ltr") == (side == "after") ? "left" : "right"; + var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1); + return coords(ch, prop)[prop] + } + + var order = getOrder(lineObj, doc.direction); + iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) { + var ltr = dir == "ltr"; + var fromPos = coords(from, ltr ? "left" : "right"); + var toPos = coords(to - 1, ltr ? "right" : "left"); + + var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen; + var first = i == 0, last = !order || i == order.length - 1; + if (toPos.top - fromPos.top <= 3) { // Single line + var openLeft = (docLTR ? openStart : openEnd) && first; + var openRight = (docLTR ? openEnd : openStart) && last; + var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left; + var right = openRight ? rightSide : (ltr ? toPos : fromPos).right; + add(left, fromPos.top, right - left, fromPos.bottom); + } else { // Multiple lines + var topLeft, topRight, botLeft, botRight; + if (ltr) { + topLeft = docLTR && openStart && first ? leftSide : fromPos.left; + topRight = docLTR ? rightSide : wrapX(from, dir, "before"); + botLeft = docLTR ? leftSide : wrapX(to, dir, "after"); + botRight = docLTR && openEnd && last ? rightSide : toPos.right; + } else { + topLeft = !docLTR ? leftSide : wrapX(from, dir, "before"); + topRight = !docLTR && openStart && first ? rightSide : fromPos.right; + botLeft = !docLTR && openEnd && last ? leftSide : toPos.left; + botRight = !docLTR ? rightSide : wrapX(to, dir, "after"); + } + add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom); + if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); } + add(botLeft, toPos.top, botRight - botLeft, toPos.bottom); + } + + if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; } + if (cmpCoords(toPos, start) < 0) { start = toPos; } + if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; } + if (cmpCoords(toPos, end) < 0) { end = toPos; } + }); + return {start: start, end: end} + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + { add(leftSide, leftEnd.bottom, null, rightStart.top); } + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) { return } + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + { display.blinker = setInterval(function () { + if (!cm.hasFocus()) { onBlur(cm); } + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); } + else if (cm.options.cursorBlinkRate < 0) + { display.cursorDiv.style.visibility = "hidden"; } + } + + function ensureFocus(cm) { + if (!cm.hasFocus()) { + cm.display.input.focus(); + if (!cm.state.focused) { onFocus(cm); } + } + } + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function () { if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + if (cm.state.focused) { onBlur(cm); } + } }, 100); + } + + function onFocus(cm, e) { + if (cm.state.delayingBlurEvent && !cm.state.draggingText) { cm.state.delayingBlurEvent = false; } + + if (cm.options.readOnly == "nocursor") { return } + if (!cm.state.focused) { + signal(cm, "focus", cm, e); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm, e) { + if (cm.state.delayingBlurEvent) { return } + + if (cm.state.focused) { + signal(cm, "blur", cm, e); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150); + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + var viewTop = Math.max(0, display.scroller.getBoundingClientRect().top); + var oldHeight = display.lineDiv.getBoundingClientRect().top; + var mustScroll = 0; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], wrapping = cm.options.lineWrapping; + var height = (void 0), width = 0; + if (cur.hidden) { continue } + oldHeight += cur.line.height; + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + // Check that lines don't extend past the right of the current + // editor width + if (!wrapping && cur.text.firstChild) + { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; } + } + var diff = cur.line.height - height; + if (diff > .005 || diff < -.005) { + if (oldHeight < viewTop) { mustScroll -= diff; } + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) { for (var j = 0; j < cur.rest.length; j++) + { updateWidgetHeight(cur.rest[j]); } } + } + if (width > cm.display.sizerWidth) { + var chWidth = Math.ceil(width / charWidth(cm.display)); + if (chWidth > cm.display.maxLineLength) { + cm.display.maxLineLength = chWidth; + cm.display.maxLine = cur.line; + cm.display.maxLineChanged = true; + } + } + } + if (Math.abs(mustScroll) > 2) { display.scroller.scrollTop += mustScroll; } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) { + var w = line.widgets[i], parent = w.node.parentNode; + if (parent) { w.height = parent.offsetHeight; } + } } + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)} + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, rect) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) { return } + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + var doc = display.wrapper.ownerDocument; + if (rect.top + box.top < 0) { doScroll = true; } + else if (rect.bottom + box.top > (doc.defaultView.innerHeight || doc.documentElement.clientHeight)) { doScroll = false; } + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;")); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) { margin = 0; } + var rect; + if (!cm.options.lineWrapping && pos == end) { + // Set pos and end to the cursor positions around the character pos sticks to + // If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch + // If pos == Pos(_, 0, "before"), pos and end are unchanged + end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos; + pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos; + } + for (var limit = 0; limit < 5; limit++) { + var changed = false; + var coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + rect = {left: Math.min(coords.left, endCoords.left), + top: Math.min(coords.top, endCoords.top) - margin, + right: Math.max(coords.left, endCoords.left), + bottom: Math.max(coords.bottom, endCoords.bottom) + margin}; + var scrollPos = calculateScrollPos(cm, rect); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + updateScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; } + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; } + } + if (!changed) { break } + } + return rect + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, rect) { + var scrollPos = calculateScrollPos(cm, rect); + if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); } + if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); } + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, rect) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (rect.top < 0) { rect.top = 0; } + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; } + var docBottom = cm.doc.height + paddingVert(display); + var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin; + if (rect.top < screentop) { + result.scrollTop = atTop ? 0 : rect.top; + } else if (rect.bottom > screentop + screen) { + var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen); + if (newTop != screentop) { result.scrollTop = newTop; } + } + + var gutterSpace = cm.options.fixedGutter ? 0 : display.gutters.offsetWidth; + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft - gutterSpace; + var screenw = displayWidth(cm) - display.gutters.offsetWidth; + var tooWide = rect.right - rect.left > screenw; + if (tooWide) { rect.right = rect.left + screenw; } + if (rect.left < 10) + { result.scrollLeft = 0; } + else if (rect.left < screenleft) + { result.scrollLeft = Math.max(0, rect.left + gutterSpace - (tooWide ? 0 : 10)); } + else if (rect.right > screenw + screenleft - 3) + { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; } + return result + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollTop(cm, top) { + if (top == null) { return } + resolveScrollToPos(cm); + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(); + cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin}; + } + + function scrollToCoords(cm, x, y) { + if (x != null || y != null) { resolveScrollToPos(cm); } + if (x != null) { cm.curOp.scrollLeft = x; } + if (y != null) { cm.curOp.scrollTop = y; } + } + + function scrollToRange(cm, range) { + resolveScrollToPos(cm); + cm.curOp.scrollToPos = range; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + scrollToCoordsRange(cm, from, to, range.margin); + } + } + + function scrollToCoordsRange(cm, from, to, margin) { + var sPos = calculateScrollPos(cm, { + left: Math.min(from.left, to.left), + top: Math.min(from.top, to.top) - margin, + right: Math.max(from.right, to.right), + bottom: Math.max(from.bottom, to.bottom) + margin + }); + scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop); + } + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function updateScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) { return } + if (!gecko) { updateDisplaySimple(cm, {top: val}); } + setScrollTop(cm, val, true); + if (gecko) { updateDisplaySimple(cm); } + startWorker(cm, 100); + } + + function setScrollTop(cm, val, forceScroll) { + val = Math.max(0, Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val)); + if (cm.display.scroller.scrollTop == val && !forceScroll) { return } + cm.doc.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; } + } + + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller, forceScroll) { + val = Math.max(0, Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth)); + if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return } + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; } + cm.display.scrollbars.setScrollLeft(val); + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + } + } + + var NativeScrollbars = function(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + vert.tabIndex = horiz.tabIndex = -1; + place(vert); place(horiz); + + on(vert, "scroll", function () { + if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); } + }); + on(horiz, "scroll", function () { + if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); } + }); + + this.checkedZeroWidth = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; } + }; + + NativeScrollbars.prototype.update = function (measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.scrollTop = 0; + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedZeroWidth && measure.clientHeight > 0) { + if (sWidth == 0) { this.zeroWidthHack(); } + this.checkedZeroWidth = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} + }; + + NativeScrollbars.prototype.setScrollLeft = function (pos) { + if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; } + if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); } + }; + + NativeScrollbars.prototype.setScrollTop = function (pos) { + if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; } + if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); } + }; + + NativeScrollbars.prototype.zeroWidthHack = function () { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.height = this.vert.style.width = w; + this.horiz.style.visibility = this.vert.style.visibility = "hidden"; + this.disableHoriz = new Delayed; + this.disableVert = new Delayed; + }; + + NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) { + bar.style.visibility = ""; + function maybeDisable() { + // To find out whether the scrollbar is still visible, we + // check whether the element under the pixel in the bottom + // right corner of the scrollbar box is the scrollbar box + // itself (when the bar is still visible) or its filler child + // (when the bar is hidden). If it is still visible, we keep + // it enabled, if it's hidden, we disable pointer events. + var box = bar.getBoundingClientRect(); + var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) + : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1); + if (elt != bar) { bar.style.visibility = "hidden"; } + else { delay.set(1000, maybeDisable); } + } + delay.set(1000, maybeDisable); + }; + + NativeScrollbars.prototype.clear = function () { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + }; + + var NullScrollbars = function () {}; + + NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} }; + NullScrollbars.prototype.setScrollLeft = function () {}; + NullScrollbars.prototype.setScrollTop = function () {}; + NullScrollbars.prototype.clear = function () {}; + + function updateScrollbars(cm, measure) { + if (!measure) { measure = measureForScrollbars(cm); } + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + { updateHeightsInViewport(cm); } + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else { d.scrollbarFiller.style.display = ""; } + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else { d.gutterFiller.style.display = ""; } + } + + var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); } + } + + cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function () { + if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); } + }); + node.setAttribute("cm-not-content", "true"); + }, function (pos, axis) { + if (axis == "horizontal") { setScrollLeft(cm, pos); } + else { updateScrollTop(cm, pos); } + }, cm); + if (cm.display.scrollbars.addClass) + { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); } + } + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: 0, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId, // Unique ID + markArrays: null // Used by addMarkedSpan + }; + pushOperation(cm.curOp); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp; + if (op) { finishOperation(op, function (group) { + for (var i = 0; i < group.ops.length; i++) + { group.ops[i].cm.curOp = null; } + endOperations(group); + }); } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + { endOperation_R1(ops[i]); } + for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe) + { endOperation_W1(ops[i$1]); } + for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM + { endOperation_R2(ops[i$2]); } + for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe) + { endOperation_W2(ops[i$3]); } + for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM + { endOperation_finish(ops[i$4]); } + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) { findMaxLine(cm); } + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) { updateHeightsInViewport(cm); } + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + { op.preparedSelection = display.input.prepareSelection(); } + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); } + cm.display.maxLineChanged = false; + } + + var takeFocus = op.focus && op.focus == activeElt(doc(cm)); + if (op.preparedSelection) + { cm.display.input.showSelection(op.preparedSelection, takeFocus); } + if (op.updatedDisplay || op.startHeight != cm.doc.height) + { updateScrollbars(cm, op.barMeasure); } + if (op.updatedDisplay) + { setDocumentHeight(cm, op.barMeasure); } + + if (op.selectionChanged) { restartBlink(cm); } + + if (cm.state.focused && op.updateInput) + { cm.display.input.reset(op.typing); } + if (takeFocus) { ensureFocus(op.cm); } + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); } + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + { display.wheelStartX = display.wheelStartY = null; } + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); } + + if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + maybeScrollWindow(cm, rect); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) { for (var i = 0; i < hidden.length; ++i) + { if (!hidden[i].lines.length) { signal(hidden[i], "hide"); } } } + if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1) + { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], "unhide"); } } } + + if (display.wrapper.offsetHeight) + { doc.scrollTop = cm.display.scroller.scrollTop; } + + // Fire change events, and delayed event handlers + if (op.changeObjs) + { signal(cm, "changes", cm, op.changeObjs); } + if (op.update) + { op.update.finish(); } + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) { return f() } + startOperation(cm); + try { return f() } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) { return f.apply(cm, arguments) } + startOperation(cm); + try { return f.apply(cm, arguments) } + finally { endOperation(cm); } + } + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) { return f.apply(this, arguments) } + startOperation(this); + try { return f.apply(this, arguments) } + finally { endOperation(this); } + } + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) { return f.apply(this, arguments) } + startOperation(cm); + try { return f.apply(this, arguments) } + finally { endOperation(cm); } + } + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.highlightFrontier < cm.display.viewTo) + { cm.state.highlight.set(time, bind(highlightWorker, cm)); } + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.highlightFrontier >= cm.display.viewTo) { return } + var end = +new Date + cm.options.workTime; + var context = getContextBefore(cm, doc.highlightFrontier); + var changedLines = []; + + doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) { + if (context.line >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null; + var highlighted = highlightLine(cm, line, context, true); + if (resetState) { context.state = resetState; } + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) { line.styleClasses = newCls; } + else if (oldCls) { line.styleClasses = null; } + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; } + if (ischange) { changedLines.push(context.line); } + line.stateAfter = context.save(); + context.nextLine(); + } else { + if (line.text.length <= cm.options.maxHighlightLength) + { processLine(cm, line.text, context); } + line.stateAfter = context.line % 5 == 0 ? context.save() : null; + context.nextLine(); + } + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true + } + }); + doc.highlightFrontier = context.line; + doc.modeFrontier = Math.max(doc.modeFrontier, context.line); + if (changedLines.length) { runInOp(cm, function () { + for (var i = 0; i < changedLines.length; i++) + { regLineChange(cm, changedLines[i], "text"); } + }); } + } + + // DISPLAY DRAWING + + var DisplayUpdate = function(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + }; + + DisplayUpdate.prototype.signal = function (emitter, type) { + if (hasHandler(emitter, type)) + { this.events.push(arguments); } + }; + DisplayUpdate.prototype.finish = function () { + for (var i = 0; i < this.events.length; i++) + { signal.apply(null, this.events[i]); } + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + function selectionSnapshot(cm) { + if (cm.hasFocus()) { return null } + var active = activeElt(doc(cm)); + if (!active || !contains(cm.display.lineDiv, active)) { return null } + var result = {activeElt: active}; + if (window.getSelection) { + var sel = win(cm).getSelection(); + if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { + result.anchorNode = sel.anchorNode; + result.anchorOffset = sel.anchorOffset; + result.focusNode = sel.focusNode; + result.focusOffset = sel.focusOffset; + } + } + return result + } + + function restoreSelection(snapshot) { + if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(snapshot.activeElt.ownerDocument)) { return } + snapshot.activeElt.focus(); + if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) && + snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { + var doc = snapshot.activeElt.ownerDocument; + var sel = doc.defaultView.getSelection(), range = doc.createRange(); + range.setEnd(snapshot.anchorNode, snapshot.anchorOffset); + range.collapse(false); + sel.removeAllRanges(); + sel.addRange(range); + sel.extend(snapshot.focusNode, snapshot.focusOffset); + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + { return false } + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); } + if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); } + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + { return false } + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var selSnapshot = selectionSnapshot(cm); + if (toUpdate > 4) { display.lineDiv.style.display = "none"; } + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) { display.lineDiv.style.display = ""; } + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + restoreSelection(selSnapshot); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = display.sizer.style.minHeight = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true + } + + function postUpdateDisplay(cm, update) { + var viewport = update.viewport; + + for (var first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; } + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + { break } + } else if (first) { + update.visible = visibleLines(cm.display, cm.doc, viewport); + } + if (!updateDisplayIfNeeded(cm, update)) { break } + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + update.force = false; + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + updateScrollbars(cm, barMeasure); + setDocumentHeight(cm, barMeasure); + update.finish(); + } + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + { node.style.display = "none"; } + else + { node.parentNode.removeChild(node); } + return next + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) { cur = rm(cur); } + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) { updateNumber = false; } + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) { cur = rm(cur); } + } + + function updateGutterSpace(display) { + var width = display.gutters.offsetWidth; + display.sizer.style.marginLeft = width + "px"; + // Send an event to consumers responding to changes in gutter width. + signalLater(display, "gutterChanged", display); + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + cm.display.heightForcer.style.top = measure.docHeight + "px"; + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px"; + } + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return } + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) { if (!view[i].hidden) { + if (cm.options.fixedGutter) { + if (view[i].gutter) + { view[i].gutter.style.left = left; } + if (view[i].gutterBackground) + { view[i].gutterBackground.style.left = left; } + } + var align = view[i].alignable; + if (align) { for (var j = 0; j < align.length; j++) + { align[j].style.left = left; } } + } } + if (cm.options.fixedGutter) + { display.gutters.style.left = (comp + gutterW) + "px"; } + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) { return false } + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm.display); + return true + } + return false + } + + function getGutters(gutters, lineNumbers) { + var result = [], sawLineNumbers = false; + for (var i = 0; i < gutters.length; i++) { + var name = gutters[i], style = null; + if (typeof name != "string") { style = name.style; name = name.className; } + if (name == "CodeMirror-linenumbers") { + if (!lineNumbers) { continue } + else { sawLineNumbers = true; } + } + result.push({className: name, style: style}); + } + if (lineNumbers && !sawLineNumbers) { result.push({className: "CodeMirror-linenumbers", style: null}); } + return result + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function renderGutters(display) { + var gutters = display.gutters, specs = display.gutterSpecs; + removeChildren(gutters); + display.lineGutter = null; + for (var i = 0; i < specs.length; ++i) { + var ref = specs[i]; + var className = ref.className; + var style = ref.style; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className)); + if (style) { gElt.style.cssText = style; } + if (className == "CodeMirror-linenumbers") { + display.lineGutter = gElt; + gElt.style.width = (display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = specs.length ? "" : "none"; + updateGutterSpace(display); + } + + function updateGutters(cm) { + renderGutters(cm.display); + regChange(cm); + alignHorizontally(cm); + } + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input, options) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = eltP("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = eltP("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + var lines = eltP("div", [d.lineSpace], "CodeMirror-lines"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [lines], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + // See #6982. FIXME remove when this has been fixed for a while in Chrome + if (chrome && chrome_version >= 105) { d.wrapper.style.clipPath = "inset(0px)"; } + + // This attribute is respected by automatic translation systems such as Google Translate, + // and may also be respected by tools used by human translators. + d.wrapper.setAttribute('translate', 'no'); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; } + + if (place) { + if (place.appendChild) { place.appendChild(d.wrapper); } + else { place(d.wrapper); } + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + d.gutterSpecs = getGutters(options.gutters, options.lineNumbers); + renderGutters(d); + + input.init(d); + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) { wheelPixelsPerUnit = -.53; } + else if (gecko) { wheelPixelsPerUnit = 15; } + else if (chrome) { wheelPixelsPerUnit = -.7; } + else if (safari) { wheelPixelsPerUnit = -1/3; } + + function wheelEventDelta(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; } + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; } + else if (dy == null) { dy = e.wheelDelta; } + return {x: dx, y: dy} + } + function wheelEventPixels(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta + } + + function onScrollWheel(cm, e) { + // On Chrome 102, viewport updates somehow stop wheel-based + // scrolling. Turning off pointer events during the scroll seems + // to avoid the issue. + if (chrome && chrome_version == 102) { + if (cm.display.chromeScrollHack == null) { cm.display.sizer.style.pointerEvents = "none"; } + else { clearTimeout(cm.display.chromeScrollHack); } + cm.display.chromeScrollHack = setTimeout(function () { + cm.display.chromeScrollHack = null; + cm.display.sizer.style.pointerEvents = ""; + }, 100); + } + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + var pixelsPerUnit = wheelPixelsPerUnit; + if (e.deltaMode === 0) { + dx = e.deltaX; + dy = e.deltaY; + pixelsPerUnit = 1; + } + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + var canScrollX = scroll.scrollWidth > scroll.clientWidth; + var canScrollY = scroll.scrollHeight > scroll.clientHeight; + if (!(dx && canScrollX || dy && canScrollY)) { return } + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && pixelsPerUnit != null) { + if (dy && canScrollY) + { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * pixelsPerUnit)); } + setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * pixelsPerUnit)); + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + { e_preventDefault(e); } + display.wheelStartX = null; // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && pixelsPerUnit != null) { + var pixels = dy * pixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) { top = Math.max(0, top + pixels - 50); } + else { bot = Math.min(cm.doc.height, bot + pixels + 50); } + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20 && e.deltaMode !== 0) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function () { + if (display.wheelStartX == null) { return } + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) { return } + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + var Selection = function(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + }; + + Selection.prototype.primary = function () { return this.ranges[this.primIndex] }; + + Selection.prototype.equals = function (other) { + if (other == this) { return true } + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false } + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false } + } + return true + }; + + Selection.prototype.deepCopy = function () { + var out = []; + for (var i = 0; i < this.ranges.length; i++) + { out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); } + return new Selection(out, this.primIndex) + }; + + Selection.prototype.somethingSelected = function () { + for (var i = 0; i < this.ranges.length; i++) + { if (!this.ranges[i].empty()) { return true } } + return false + }; + + Selection.prototype.contains = function (pos, end) { + if (!end) { end = pos; } + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + { return i } + } + return -1 + }; + + var Range = function(anchor, head) { + this.anchor = anchor; this.head = head; + }; + + Range.prototype.from = function () { return minPos(this.anchor, this.head) }; + Range.prototype.to = function () { return maxPos(this.anchor, this.head) }; + Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(cm, ranges, primIndex) { + var mayTouch = cm && cm.options.selectionsMayTouch; + var prim = ranges[primIndex]; + ranges.sort(function (a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + var diff = cmp(prev.to(), cur.from()); + if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) { --primIndex; } + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex) + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0) + } + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + function changeEnd(change) { + if (!change.text) { return change.to } + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)) + } + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) { return pos } + if (cmp(pos, change.to) <= 0) { return changeEnd(change) } + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; } + return Pos(line, ch) + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(doc.cm, out, doc.sel.primIndex) + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + { return Pos(nw.line, pos.ch - old.ch + nw.ch) } + else + { return Pos(nw.line + (pos.line - old.line), pos.ch) } + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex) + } + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function (line) { + if (line.stateAfter) { line.stateAfter = null; } + if (line.styles) { line.styles = null; } + }); + cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) { regChange(cm); } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore) + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + var result = []; + for (var i = start; i < end; ++i) + { result.push(new Line(text[i], spansFor(i), estimateHeight)); } + return result + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) { doc.remove(from.line, nlines); } + if (added.length) { doc.insert(from.line, added); } + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added$1 = linesFor(1, text.length - 1); + added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added$1); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added$2 = linesFor(1, text.length - 1); + if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); } + doc.insert(from.line + 1, added$2); + } + + signalLater(doc, "change", doc, change); + } + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) { continue } + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) { continue } + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) { throw new Error("This document is already in use.") } + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + setDirectionClass(cm); + cm.options.direction = doc.direction; + if (!cm.options.lineWrapping) { findMaxLine(cm); } + cm.options.mode = doc.modeOption; + regChange(cm); + } + + function setDirectionClass(cm) { + (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl"); + } + + function directionChanged(cm) { + runInOp(cm, function () { + setDirectionClass(cm); + regChange(cm); + }); + } + + function History(prev) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = prev ? prev.undoDepth : Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = prev ? prev.maxGeneration : 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true); + return histChange + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) { array.pop(); } + else { break } + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done) + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done) + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done) + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, or are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + var last; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + { pushSelectionToHistory(doc.sel, hist.done); } + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) { hist.done.shift(); } + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) { signal(doc, "historyAdded"); } + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + { hist.done[hist.done.length - 1] = sel; } + else + { pushSelectionToHistory(sel, hist.done); } + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + { clearSelectionEvents(hist.undone); } + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + { dest.push(sel); } + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) { + if (line.markedSpans) + { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; } + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) { return null } + var out; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } } + else if (out) { out.push(spans[i]); } + } + return !out ? spans : out.length ? out : null + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) { return null } + var nw = []; + for (var i = 0; i < change.text.length; ++i) + { nw.push(removeClearedSpans(found[i])); } + return nw + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) { return stretched } + if (!stretched) { return old } + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + { if (oldCur[k].marker == span.marker) { continue spans } } + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + var copy = []; + for (var i = 0; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m = (void 0); + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } } } + } + } + return copy + } + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(range, head, other, extend) { + if (extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head) + } else { + return new Range(other || head, head) + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options, extend) { + if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); } + setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + var out = []; + var extend = doc.cm && (doc.cm.display.shift || doc.extend); + for (var i = 0; i < doc.sel.ranges.length; i++) + { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); } + var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel, options) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + { this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); } + }, + origin: options && options.origin + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) { signal(doc.cm, "beforeSelectionChange", doc.cm, obj); } + if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) } + else { return sel } + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + { sel = filterSelectionChange(doc, sel, options); } + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm && doc.cm.getOption("readOnly") != "nocursor") + { ensureCursorVisible(doc.cm); } + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) { return } + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = 1; + doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); + var newHead = range.head == range.anchor ? newAnchor : skipAtomic(doc, range.head, old && old.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) { out = sel.ranges.slice(0, i); } + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel + } + + function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + var line = getLine(doc, pos.line); + if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + + // Determine if we should prevent the cursor being placed to the left/right of an atomic marker + // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it + // is with selectLeft/Right + var preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft; + var preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight; + + if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) { break } + else {--i; continue} + } + } + if (!m.atomic) { continue } + + if (oldPos) { + var near = m.find(dir < 0 ? 1 : -1), diff = (void 0); + if (dir < 0 ? preventCursorRight : preventCursorLeft) + { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); } + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + { return skipAtomicInner(doc, near, pos, dir, mayClear) } + } + + var far = m.find(dir < 0 ? -1 : 1); + if (dir < 0 ? preventCursorLeft : preventCursorRight) + { far = movePos(doc, far, dir, far.line == pos.line ? line : null); } + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null + } + } } + return pos + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, oldPos, bias, mayClear) { + var dir = bias || 1; + var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); + if (!found) { + doc.cantEdit = true; + return Pos(doc.first, 0) + } + return found + } + + function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) } + else { return null } + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) } + else { return null } + } else { + return new Pos(pos.line, pos.ch + dir) + } + } + + function selectAll(cm) { + cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll); + } + + // UPDATING + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function () { return obj.canceled = true; } + }; + if (update) { obj.update = function (from, to, text, origin) { + if (from) { obj.from = clipPos(doc, from); } + if (to) { obj.to = clipPos(doc, to); } + if (text) { obj.text = text; } + if (origin !== undefined) { obj.origin = origin; } + }; } + signal(doc, "beforeChange", doc, obj); + if (doc.cm) { signal(doc.cm, "beforeChange", doc.cm, obj); } + + if (obj.canceled) { + if (doc.cm) { doc.cm.curOp.updateInput = 2; } + return null + } + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin} + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) } + if (doc.cm.state.suppressEdits) { return } + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) { return } + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text, origin: change.origin}); } + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) { return } + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + var suppress = doc.cm && doc.cm.state.suppressEdits; + if (suppress && !allowSelectionOnly) { return } + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + var i = 0; + for (; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + { break } + } + if (i == source.length) { return } + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return + } + selAfter = event; + } else if (suppress) { + source.push(event); + return + } else { break } + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + var loop = function ( i ) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return {} + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); } + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function (doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + }; + + for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) { + var returned = loop( i$1 ); + + if ( returned ) return returned.v; + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) { return } + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range( + Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch) + ); }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + { regLineChange(doc.cm, l, "gutter"); } + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) } + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return + } + if (change.from.line > doc.lastLine()) { return } + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) { selAfter = computeSelAfterChange(doc, change); } + if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); } + else { updateDoc(doc, change, spans); } + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + + if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0))) + { doc.cantEdit = false; } + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function (line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + { signalCursorActivity(cm); } + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function (line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; } + } + + retreatFrontier(doc, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + { regChange(cm); } + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + { regLineChange(cm, from.line, "text"); } + else + { regChange(cm, from.line, to.line + 1, lendiff); } + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) { signalLater(cm, "change", cm, obj); } + if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); } + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + var assign; + + if (!to) { to = from; } + if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); } + if (typeof code == "string") { code = doc.splitLines(code); } + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue + } + for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) { + var cur = sub.changes[j$1]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") { line = getLine(doc, clipLine(doc, handle)); } + else { no = lineNo(handle); } + if (no == null) { return null } + if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); } + return line + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + var height = 0; + for (var i = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length }, + + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) { lines[i].parent = this; } + }, + + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + { if (op(this.lines[at])) { return true } } + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size }, + + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) { break } + at = 0; + } else { at -= sz; } + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) { this.children[i].collapse(lines); } + }, + + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + var remaining = child.lines.length % 25 + 25; + for (var pos = remaining; pos < child.lines.length;) { + var leaf = new LeafChunk(child.lines.slice(pos, pos += 25)); + child.height -= leaf.height; + this.children.splice(++i, 0, leaf); + leaf.parent = this; + } + child.lines = child.lines.slice(0, remaining); + this.maybeSpill(); + } + break + } + at -= sz; + } + }, + + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) { return } + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10) + me.parent.maybeSpill(); + }, + + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) { return true } + if ((n -= used) == 0) { break } + at = 0; + } else { at -= sz; } + } + } + }; + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = function(doc, node, options) { + if (options) { for (var opt in options) { if (options.hasOwnProperty(opt)) + { this[opt] = options[opt]; } } } + this.doc = doc; + this.node = node; + }; + + LineWidget.prototype.clear = function () { + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) { return } + for (var i = 0; i < ws.length; ++i) { if (ws[i] == this) { ws.splice(i--, 1); } } + if (!ws.length) { line.widgets = null; } + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) { + runInOp(cm, function () { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + signalLater(cm, "lineWidgetCleared", cm, this, no); + } + }; + + LineWidget.prototype.changed = function () { + var this$1 = this; + + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) { return } + if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); } + if (cm) { + runInOp(cm, function () { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + signalLater(cm, "lineWidgetChanged", cm, this$1, lineNo(line)); + }); + } + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + { addToScrollTop(cm, diff); } + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) { cm.display.alignWidgets = true; } + changeLine(doc, handle, "widget", function (line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) { widgets.push(widget); } + else { widgets.splice(Math.min(widgets.length, Math.max(0, widget.insertAt)), 0, widget); } + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) { addToScrollTop(cm, widget.height); } + cm.curOp.forceUpdate = true; + } + return true + }); + if (cm) { signalLater(cm, "lineWidgetAdded", cm, widget, typeof handle == "number" ? handle : lineNo(handle)); } + return widget + } + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + var TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + + // Clear the marker. + TextMarker.prototype.clear = function () { + if (this.explicitlyCleared) { return } + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) { startOperation(cm); } + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) { signalLater(this, "clear", found.from, found.to); } + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) { regLineChange(cm, lineNo(line), "text"); } + else if (cm) { + if (span.to != null) { max = lineNo(line); } + if (span.from != null) { min = lineNo(line); } + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + { updateLineHeight(line, textHeight(cm.display)); } + } + if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) { + var visual = visualLine(this.lines[i$1]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } } + + if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); } + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) { reCheckSelection(cm.doc); } + } + if (cm) { signalLater(cm, "markerCleared", cm, this, min, max); } + if (withOp) { endOperation(cm); } + if (this.parent) { this.parent.clear(); } + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function (side, lineObj) { + if (side == null && this.type == "bookmark") { side = 1; } + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) { return from } + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) { return to } + } + } + return from && {from: from, to: to} + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function () { + var this$1 = this; + + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) { return } + runInOp(cm, function () { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + { updateLineHeight(line, line.height + dHeight); } + } + signalLater(cm, "markerChanged", cm, this$1); + }); + }; + + TextMarker.prototype.attachLine = function (line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); } + } + this.lines.push(line); + }; + + TextMarker.prototype.detachLine = function (line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp + ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + eventMixin(TextMarker); + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) { return markTextShared(doc, from, to, options, type) } + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) } + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) { copyObj(options, marker, false); } + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + { return marker } + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = eltP("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) { marker.widgetNode.setAttribute("cm-ignore-events", "true"); } + if (options.insertLeft) { marker.widgetNode.insertLeft = true; } + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + { throw new Error("Inserting collapsed marker partially overlapping an existing one") } + seeCollapsedSpans(); + } + + if (marker.addToHistory) + { addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); } + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function (line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + { updateMaxLine = true; } + if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); } + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null), doc.cm && doc.cm.curOp); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) { + if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); } + }); } + + if (marker.clearOnEnter) { on(marker, "beforeCursorEnter", function () { return marker.clear(); }); } + + if (marker.readOnly) { + seeReadOnlySpans(); + if (doc.history.done.length || doc.history.undone.length) + { doc.clearHistory(); } + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) { cm.curOp.updateMaxLine = true; } + if (marker.collapsed) + { regChange(cm, from.line, to.line + 1); } + else if (marker.className || marker.startStyle || marker.endStyle || marker.css || + marker.attributes || marker.title) + { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, "text"); } } + if (marker.atomic) { reCheckSelection(cm.doc); } + signalLater(cm, "markerAdded", cm, marker); + } + return marker + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + { markers[i].parent = this; } + }; + + SharedTextMarker.prototype.clear = function () { + if (this.explicitlyCleared) { return } + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + { this.markers[i].clear(); } + signalLater(this, "clear"); + }; + + SharedTextMarker.prototype.find = function (side, lineObj) { + return this.primary.find(side, lineObj) + }; + eventMixin(SharedTextMarker); + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function (doc) { + if (widget) { options.widgetNode = widget.cloneNode(true); } + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + { if (doc.linked[i].isParent) { return } } + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary) + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; }) + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + var loop = function ( i ) { + var marker = markers[i], linked = [marker.primary.doc]; + linkedDocs(marker.primary.doc, function (d) { return linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + }; + + for (var i = 0; i < markers.length; i++) loop( i ); + } + + var nextDocId = 0; + var Doc = function(text, mode, firstLine, lineSep, direction) { + if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) } + if (firstLine == null) { firstLine = 0; } + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.modeFrontier = this.highlightFrontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + this.lineSep = lineSep; + this.direction = (direction == "rtl") ? "rtl" : "ltr"; + this.extend = false; + + if (typeof text == "string") { text = this.splitLines(text); } + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) { this.iterN(from - this.first, to - from, op); } + else { this.iterN(this.first, this.first + this.size, from); } + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) { height += lines[i].height; } + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) { return lines } + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true); + if (this.cm) { scrollToCoords(this.cm, 0, 0); } + setSelection(this, simpleSelection(top), sel_dontScroll); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) { return lines } + if (lineSep === '') { return lines.join('') } + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text}, + + getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }}, + getLineNumber: function(line) {return lineNo(line)}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") { line = getLine(this, line); } + return visualLine(line) + }, + + lineCount: function() {return this.size}, + firstLine: function() {return this.first}, + lastLine: function() {return this.first + this.size - 1}, + + clipPos: function(pos) {return clipPos(this, pos)}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") { pos = range.head; } + else if (start == "anchor") { pos = range.anchor; } + else if (start == "end" || start == "to" || start === false) { pos = range.to(); } + else { pos = range.from(); } + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + var heads = map(this.sel.ranges, f); + extendSelections(this, clipPosArray(this, heads), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) { return } + var out = []; + for (var i = 0; i < ranges.length; i++) + { out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head || ranges[i].anchor)); } + if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); } + setSelection(this, normalizeSelection(this.cm, out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) { return lines } + else { return lines.join(lineSep || this.lineSeparator()) } + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) { sel = sel.join(lineSep || this.lineSeparator()); } + parts[i] = sel; + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + { dup[i] = code; } + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i$1 = changes.length - 1; i$1 >= 0; i$1--) + { makeChange(this, changes[i$1]); } + if (newSel) { setSelectionReplaceHistory(this, newSel); } + else if (this.cm) { ensureCursorVisible(this.cm); } + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } } + for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } } + return {undo: done, redo: undone} + }, + clearHistory: function() { + var this$1 = this; + + this.history = new History(this.history); + linkedDocs(this, function (doc) { return doc.history = this$1.history; }, true); + }, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; } + return this.history.generation + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration) + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)} + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", function (line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) { line.gutterMarkers = null; } + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + var this$1 = this; + + this.iter(function (line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this$1, line, "gutter", function () { + line.gutterMarkers[gutterID] = null; + if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; } + return true + }); + } + }); + }), + + lineInfo: function(line) { + var n; + if (typeof line == "number") { + if (!isLine(this, line)) { return null } + n = line; + line = getLine(this, line); + if (!line) { return null } + } else { + n = lineNo(line); + if (n == null) { return null } + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) { line[prop] = cls; } + else if (classTest(cls).test(line[prop])) { return false } + else { line[prop] += " " + cls; } + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function (line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) { return false } + else if (cls == null) { line[prop] = null; } + else { + var found = cur.match(classTest(cls)); + if (!found) { return false } + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true + }) + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) { for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + { markers.push(span.marker.parent || span.marker); } + } } + return markers + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function (line) { + var spans = line.markedSpans; + if (spans) { for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(span.to != null && lineNo == from.line && from.ch >= span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + { found.push(span.marker.parent || span.marker); } + } } + ++lineNo; + }); + return found + }, + getAllMarks: function() { + var markers = []; + this.iter(function (line) { + var sps = line.markedSpans; + if (sps) { for (var i = 0; i < sps.length; ++i) + { if (sps[i].from != null) { markers.push(sps[i].marker); } } } + }); + return markers + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first, sepSize = this.lineSeparator().length; + this.iter(function (line) { + var sz = line.text.length + sepSize; + if (sz > off) { ch = off; return true } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) { return 0 } + var sepSize = this.lineSeparator().length; + this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize; + }); + return index + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep, this.direction); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc + }, + + linkedDoc: function(options) { + if (!options) { options = {}; } + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) { from = options.from; } + if (options.to != null && options.to < to) { to = options.to; } + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction); + if (options.sharedHist) { copy.history = this.history + ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) { other = other.doc; } + if (this.linked) { for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) { continue } + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break + } } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode}, + getEditor: function() {return this.cm}, + + splitLines: function(str) { + if (this.lineSep) { return str.split(this.lineSep) } + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" }, + + setDirection: docMethodOp(function (dir) { + if (dir != "rtl") { dir = "ltr"; } + if (dir == this.direction) { return } + this.direction = dir; + this.iter(function (line) { return line.order = null; }); + if (this.cm) { directionChanged(this.cm); } + }) + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + clearDragCursor(cm); + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + { return } + e_preventDefault(e); + if (ie) { lastDrop = +new Date; } + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || cm.isReadOnly()) { return } + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var markAsReadAndPasteIfAllFilesAreRead = function () { + if (++read == n) { + operation(cm, function () { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, + text: cm.doc.splitLines( + text.filter(function (t) { return t != null; }).join(cm.doc.lineSeparator())), + origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(clipPos(cm.doc, pos), clipPos(cm.doc, changeEnd(change)))); + })(); + } + }; + var readTextFromFile = function (file, i) { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) { + markAsReadAndPasteIfAllFilesAreRead(); + return + } + var reader = new FileReader; + reader.onerror = function () { return markAsReadAndPasteIfAllFilesAreRead(); }; + reader.onload = function () { + var content = reader.result; + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) { + markAsReadAndPasteIfAllFilesAreRead(); + return + } + text[i] = content; + markAsReadAndPasteIfAllFilesAreRead(); + }; + reader.readAsText(file); + }; + for (var i = 0; i < files.length; i++) { readTextFromFile(files[i], i); } + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function () { return cm.display.input.focus(); }, 20); + return + } + try { + var text$1 = e.dataTransfer.getData("Text"); + if (text$1) { + var selected; + if (cm.state.draggingText && !cm.state.draggingText.copy) + { selected = cm.listSelections(); } + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1) + { replaceRange(cm.doc, "", selected[i$1].anchor, selected[i$1].head, "drag"); } } + cm.replaceSelection(text$1, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e$1){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return } + + e.dataTransfer.setData("Text", cm.getSelection()); + e.dataTransfer.effectAllowed = "copyMove"; + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) { img.parentNode.removeChild(img); } + } + } + + function onDragOver(cm, e) { + var pos = posFromMouse(cm, e); + if (!pos) { return } + var frag = document.createDocumentFragment(); + drawSelectionCursor(cm, pos, frag); + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); + } + removeChildrenAndAdd(cm.display.dragCursor, frag); + } + + function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor); + cm.display.dragCursor = null; + } + } + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.getElementsByClassName) { return } + var byClass = document.getElementsByClassName("CodeMirror"), editors = []; + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) { editors.push(cm); } + } + if (editors.length) { editors[0].operation(function () { + for (var i = 0; i < editors.length; i++) { f(editors[i]); } + }); } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) { return } + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function () { + if (resizeTimer == null) { resizeTimer = setTimeout(function () { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); } + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function () { return forEachCodeMirror(onBlur); }); + } + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + var keyNames = { + 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", + 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 224: "Mod", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" + }; + + // Number keys + for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); } + // Alphabetic keys + for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); } + // Function keys + for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = "F" + i$2; } + + var keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + "fallthrough": "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", + "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", + "Ctrl-T": "transposeChars", "Ctrl-O": "openLine" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + "fallthrough": ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/); + name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; } + else if (/^a(lt)?$/i.test(mod)) { alt = true; } + else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; } + else if (/^s(hift)?$/i.test(mod)) { shift = true; } + else { throw new Error("Unrecognized modifier name: " + mod) } + } + if (alt) { name = "Alt-" + name; } + if (ctrl) { name = "Ctrl-" + name; } + if (cmd) { name = "Cmd-" + name; } + if (shift) { name = "Shift-" + name; } + return name + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + function normalizeKeyMap(keymap) { + var copy = {}; + for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue } + if (value == "...") { delete keymap[keyname]; continue } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val = (void 0), name = (void 0); + if (i == keys.length - 1) { + name = keys.join(" "); + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) { copy[name] = val; } + else if (prev != val) { throw new Error("Inconsistent bindings for " + name) } + } + delete keymap[keyname]; + } } + for (var prop in copy) { keymap[prop] = copy[prop]; } + return keymap + } + + function lookupKey(key, map, handle, context) { + map = getKeyMap(map); + var found = map.call ? map.call(key, context) : map[key]; + if (found === false) { return "nothing" } + if (found === "...") { return "multi" } + if (found != null && handle(found)) { return "handled" } + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + { return lookupKey(key, map.fallthrough, handle, context) } + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context); + if (result) { return result } + } + } + } + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + function isModifierKey(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" + } + + function addModifierNames(name, event, noShift) { + var base = name; + if (event.altKey && base != "Alt") { name = "Alt-" + name; } + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") { name = "Ctrl-" + name; } + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Mod") { name = "Cmd-" + name; } + if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; } + return name + } + + // Look up the name of a key as indicated by an event object. + function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) { return false } + var name = keyNames[event.keyCode]; + if (name == null || event.altGraphKey) { return false } + // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause, + // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+) + if (event.keyCode == 3 && event.code) { name = event.code; } + return addModifierNames(name, event, noShift) + } + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function () { + for (var i = kill.length - 1; i >= 0; i--) + { replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); } + ensureCursorVisible(cm); + }); + } + + function moveCharLogically(line, ch, dir) { + var target = skipExtendingChars(line.text, ch + dir, dir); + return target < 0 || target > line.text.length ? null : target + } + + function moveLogically(line, start, dir) { + var ch = moveCharLogically(line, start.ch, dir); + return ch == null ? null : new Pos(start.line, ch, dir < 0 ? "after" : "before") + } + + function endOfLine(visually, cm, lineObj, lineNo, dir) { + if (visually) { + if (cm.doc.direction == "rtl") { dir = -dir; } + var order = getOrder(lineObj, cm.doc.direction); + if (order) { + var part = dir < 0 ? lst(order) : order[0]; + var moveInStorageOrder = (dir < 0) == (part.level == 1); + var sticky = moveInStorageOrder ? "after" : "before"; + var ch; + // With a wrapped rtl chunk (possibly spanning multiple bidi parts), + // it could be that the last bidi part is not on the last visual line, + // since visual lines contain content order-consecutive chunks. + // Thus, in rtl, we are looking for the first (content-order) character + // in the rtl chunk that is on the last line (that is, the same line + // as the last (content-order) character). + if (part.level > 0 || cm.doc.direction == "rtl") { + var prep = prepareMeasureForLine(cm, lineObj); + ch = dir < 0 ? lineObj.text.length - 1 : 0; + var targetTop = measureCharPrepared(cm, prep, ch).top; + ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch); + if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1); } + } else { ch = dir < 0 ? part.to : part.from; } + return new Pos(lineNo, ch, sticky) + } + } + return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? "before" : "after") + } + + function moveVisually(cm, line, start, dir) { + var bidi = getOrder(line, cm.doc.direction); + if (!bidi) { return moveLogically(line, start, dir) } + if (start.ch >= line.text.length) { + start.ch = line.text.length; + start.sticky = "before"; + } else if (start.ch <= 0) { + start.ch = 0; + start.sticky = "after"; + } + var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos]; + if (cm.doc.direction == "ltr" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) { + // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines, + // nothing interesting happens. + return moveLogically(line, start, dir) + } + + var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); }; + var prep; + var getWrappedLineExtent = function (ch) { + if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} } + prep = prep || prepareMeasureForLine(cm, line); + return wrappedLineExtentChar(cm, line, prep, ch) + }; + var wrappedLineExtent = getWrappedLineExtent(start.sticky == "before" ? mv(start, -1) : start.ch); + + if (cm.doc.direction == "rtl" || part.level == 1) { + var moveInStorageOrder = (part.level == 1) == (dir < 0); + var ch = mv(start, moveInStorageOrder ? 1 : -1); + if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) { + // Case 2: We move within an rtl part or in an rtl editor on the same visual line + var sticky = moveInStorageOrder ? "before" : "after"; + return new Pos(start.line, ch, sticky) + } + } + + // Case 3: Could not move within this bidi part in this visual line, so leave + // the current bidi part + + var searchInVisualLine = function (partPos, dir, wrappedLineExtent) { + var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder + ? new Pos(start.line, mv(ch, 1), "before") + : new Pos(start.line, ch, "after"); }; + + for (; partPos >= 0 && partPos < bidi.length; partPos += dir) { + var part = bidi[partPos]; + var moveInStorageOrder = (dir > 0) == (part.level != 1); + var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1); + if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) } + ch = moveInStorageOrder ? part.from : mv(part.to, -1); + if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) } + } + }; + + // Case 3a: Look for other bidi parts on the same visual line + var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent); + if (res) { return res } + + // Case 3b: Look for other bidi parts on the next visual line + var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1); + if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) { + res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh)); + if (res) { return res } + } + + // Case 4: Nowhere to move + return null + } + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = { + selectAll: selectAll, + singleSelection: function (cm) { return cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); }, + killLine: function (cm) { return deleteNearSelection(cm, function (range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + { return {from: range.head, to: Pos(range.head.line + 1, 0)} } + else + { return {from: range.head, to: Pos(range.head.line, len)} } + } else { + return {from: range.from(), to: range.to()} + } + }); }, + deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0)) + }); }); }, + delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({ + from: Pos(range.from().line, 0), to: range.from() + }); }); }, + delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()} + }); }, + delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos } + }); }, + undo: function (cm) { return cm.undo(); }, + redo: function (cm) { return cm.redo(); }, + undoSelection: function (cm) { return cm.undoSelection(); }, + redoSelection: function (cm) { return cm.redoSelection(); }, + goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); }, + goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); }, + goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1} + ); }, + goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); }, + {origin: "+move", bias: 1} + ); }, + goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1} + ); }, + goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div") + }, sel_move); }, + goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div") + }, sel_move); }, + goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) { + var top = cm.cursorCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) { return lineStartSmart(cm, range.head) } + return pos + }, sel_move); }, + goLineUp: function (cm) { return cm.moveV(-1, "line"); }, + goLineDown: function (cm) { return cm.moveV(1, "line"); }, + goPageUp: function (cm) { return cm.moveV(-1, "page"); }, + goPageDown: function (cm) { return cm.moveV(1, "page"); }, + goCharLeft: function (cm) { return cm.moveH(-1, "char"); }, + goCharRight: function (cm) { return cm.moveH(1, "char"); }, + goColumnLeft: function (cm) { return cm.moveH(-1, "column"); }, + goColumnRight: function (cm) { return cm.moveH(1, "column"); }, + goWordLeft: function (cm) { return cm.moveH(-1, "word"); }, + goGroupRight: function (cm) { return cm.moveH(1, "group"); }, + goGroupLeft: function (cm) { return cm.moveH(-1, "group"); }, + goWordRight: function (cm) { return cm.moveH(1, "word"); }, + delCharBefore: function (cm) { return cm.deleteH(-1, "codepoint"); }, + delCharAfter: function (cm) { return cm.deleteH(1, "char"); }, + delWordBefore: function (cm) { return cm.deleteH(-1, "word"); }, + delWordAfter: function (cm) { return cm.deleteH(1, "word"); }, + delGroupBefore: function (cm) { return cm.deleteH(-1, "group"); }, + delGroupAfter: function (cm) { return cm.deleteH(1, "group"); }, + indentAuto: function (cm) { return cm.indentSelection("smart"); }, + indentMore: function (cm) { return cm.indentSelection("add"); }, + indentLess: function (cm) { return cm.indentSelection("subtract"); }, + insertTab: function (cm) { return cm.replaceSelection("\t"); }, + insertSoftTab: function (cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(spaceStr(tabSize - col % tabSize)); + } + cm.replaceSelections(spaces); + }, + defaultTab: function (cm) { + if (cm.somethingSelected()) { cm.indentSelection("add"); } + else { cm.execCommand("insertTab"); } + }, + // Swap the two chars left and right of each selection's head. + // Move cursor behind the two swapped characters afterwards. + // + // Doesn't consider line feeds a character. + // Doesn't scan more than one line above to find a character. + // Doesn't do anything on an empty line. + // Doesn't do anything with non-empty selections. + transposeChars: function (cm) { return runInOp(cm, function () { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) { continue } + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); } + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) { + cur = new Pos(cur.line, 1); + cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), cur, "+transpose"); + } + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); }, + newlineAndIndent: function (cm) { return runInOp(cm, function () { + var sels = cm.listSelections(); + for (var i = sels.length - 1; i >= 0; i--) + { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, "+input"); } + sels = cm.listSelections(); + for (var i$1 = 0; i$1 < sels.length; i$1++) + { cm.indentLine(sels[i$1].from().line, null, true); } + ensureCursorVisible(cm); + }); }, + openLine: function (cm) { return cm.replaceSelection("\n", "start"); }, + toggleOverwrite: function (cm) { return cm.toggleOverwrite(); } + }; + + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) { lineN = lineNo(visual); } + return endOfLine(true, cm, visual, lineN, 1) + } + function lineEnd(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLineEnd(line); + if (visual != line) { lineN = lineNo(visual); } + return endOfLine(true, cm, line, lineN, -1) + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line, cm.doc.direction); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(start.ch, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky) + } + return start + } + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) { return false } + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true; } + if (dropShift) { cm.display.shift = false; } + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) { return result } + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm) + } + + // Note that, despite the name, this function is also used to check + // for bound mouse clicks. + + var stopSeq = new Delayed; + + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) { return "handled" } + if (/\'$/.test(name)) + { cm.state.keySeq = null; } + else + { stopSeq.set(50, function () { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); } + if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true } + } + return dispatchKeyInner(cm, name, e, handle) + } + + function dispatchKeyInner(cm, name, e, handle) { + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + { cm.state.keySeq = name; } + if (result == "handled") + { signalLater(cm, "keyHandled", cm, name, e); } + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + return !!result + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) { return false } + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function (b) { return doHandleBinding(cm, b, true); }) + || dispatchKey(cm, name, e, function (b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + { return doHandleBinding(cm, b) } + }) + } else { + return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); }) + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); }) + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + if (e.target && e.target != cm.display.input.getField()) { return } + cm.curOp.focus = activeElt(doc(cm)); + if (signalDOMEvent(cm, e)) { return } + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; } + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + { cm.replaceSelection("", null, "cut"); } + } + if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand) + { document.execCommand("cut"); } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + { showCrossHair(cm); } + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) { this.doc.sel.shift = false; } + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (e.target && e.target != cm.display.input.getField()) { return } + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return } + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return } + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + // Some browsers fire keypress events for backspace + if (ch == "\x08") { return } + if (handleCharBinding(cm, e, ch)) { return } + cm.display.input.onKeyPress(e); + } + + var DOUBLECLICK_DELAY = 400; + + var PastClick = function(time, pos, button) { + this.time = time; + this.pos = pos; + this.button = button; + }; + + PastClick.prototype.compare = function (time, pos, button) { + return this.time + DOUBLECLICK_DELAY > time && + cmp(pos, this.pos) == 0 && button == this.button + }; + + var lastClick, lastDoubleClick; + function clickRepeat(pos, button) { + var now = +new Date; + if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) { + lastClick = lastDoubleClick = null; + return "triple" + } else if (lastClick && lastClick.compare(now, pos, button)) { + lastDoubleClick = new PastClick(now, pos, button); + lastClick = null; + return "double" + } else { + lastClick = new PastClick(now, pos, button); + lastDoubleClick = null; + return "single" + } + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return } + display.input.ensurePolled(); + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function () { return display.scroller.draggable = true; }, 100); + } + return + } + if (clickInGutter(cm, e)) { return } + var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single"; + win(cm).focus(); + + // #3261: make sure, that we're not starting a second selection + if (button == 1 && cm.state.selectingText) + { cm.state.selectingText(e); } + + if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return } + + if (button == 1) { + if (pos) { leftButtonDown(cm, pos, repeat, e); } + else if (e_target(e) == display.scroller) { e_preventDefault(e); } + } else if (button == 2) { + if (pos) { extendSelection(cm.doc, pos); } + setTimeout(function () { return display.input.focus(); }, 20); + } else if (button == 3) { + if (captureRightClick) { cm.display.input.onContextMenu(e); } + else { delayBlurEvent(cm); } + } + } + + function handleMappedButton(cm, button, pos, repeat, event) { + var name = "Click"; + if (repeat == "double") { name = "Double" + name; } + else if (repeat == "triple") { name = "Triple" + name; } + name = (button == 1 ? "Left" : button == 2 ? "Middle" : "Right") + name; + + return dispatchKey(cm, addModifierNames(name, event), event, function (bound) { + if (typeof bound == "string") { bound = commands[bound]; } + if (!bound) { return false } + var done = false; + try { + if (cm.isReadOnly()) { cm.state.suppressEdits = true; } + done = bound(cm, pos) != Pass; + } finally { + cm.state.suppressEdits = false; + } + return done + }) + } + + function configureMouse(cm, repeat, event) { + var option = cm.getOption("configureMouse"); + var value = option ? option(cm, repeat, event) : {}; + if (value.unit == null) { + var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey; + value.unit = rect ? "rectangle" : repeat == "single" ? "char" : repeat == "double" ? "word" : "line"; + } + if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; } + if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; } + if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); } + return value + } + + function leftButtonDown(cm, pos, repeat, event) { + if (ie) { setTimeout(bind(ensureFocus, cm), 0); } + else { cm.curOp.focus = activeElt(doc(cm)); } + + var behavior = configureMouse(cm, repeat, event); + + var sel = cm.doc.sel, contained; + if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && + repeat == "single" && (contained = sel.contains(pos)) > -1 && + (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) && + (cmp(contained.to(), pos) > 0 || pos.xRel < 0)) + { leftButtonStartDrag(cm, event, pos, behavior); } + else + { leftButtonSelect(cm, event, pos, behavior); } + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, event, pos, behavior) { + var display = cm.display, moved = false; + var dragEnd = operation(cm, function (e) { + if (webkit) { display.scroller.draggable = false; } + cm.state.draggingText = false; + if (cm.state.delayingBlurEvent) { + if (cm.hasFocus()) { cm.state.delayingBlurEvent = false; } + else { delayBlurEvent(cm); } + } + off(display.wrapper.ownerDocument, "mouseup", dragEnd); + off(display.wrapper.ownerDocument, "mousemove", mouseMove); + off(display.scroller, "dragstart", dragStart); + off(display.scroller, "drop", dragEnd); + if (!moved) { + e_preventDefault(e); + if (!behavior.addNew) + { extendSelection(cm.doc, pos, null, null, behavior.extend); } + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if ((webkit && !safari) || ie && ie_version == 9) + { setTimeout(function () {display.wrapper.ownerDocument.body.focus({preventScroll: true}); display.input.focus();}, 20); } + else + { display.input.focus(); } + } + }); + var mouseMove = function(e2) { + moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10; + }; + var dragStart = function () { return moved = true; }; + // Let the drag handler handle this. + if (webkit) { display.scroller.draggable = true; } + cm.state.draggingText = dragEnd; + dragEnd.copy = !behavior.moveOnDrag; + on(display.wrapper.ownerDocument, "mouseup", dragEnd); + on(display.wrapper.ownerDocument, "mousemove", mouseMove); + on(display.scroller, "dragstart", dragStart); + on(display.scroller, "drop", dragEnd); + + cm.state.delayingBlurEvent = true; + setTimeout(function () { return display.input.focus(); }, 20); + // IE's approach to draggable + if (display.scroller.dragDrop) { display.scroller.dragDrop(); } + } + + function rangeForUnit(cm, pos, unit) { + if (unit == "char") { return new Range(pos, pos) } + if (unit == "word") { return cm.findWordAt(pos) } + if (unit == "line") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) } + var result = unit(cm, pos); + return new Range(result.from, result.to) + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, event, start, behavior) { + if (ie) { delayBlurEvent(cm); } + var display = cm.display, doc$1 = cm.doc; + e_preventDefault(event); + + var ourRange, ourIndex, startSel = doc$1.sel, ranges = startSel.ranges; + if (behavior.addNew && !behavior.extend) { + ourIndex = doc$1.sel.contains(start); + if (ourIndex > -1) + { ourRange = ranges[ourIndex]; } + else + { ourRange = new Range(start, start); } + } else { + ourRange = doc$1.sel.primary(); + ourIndex = doc$1.sel.primIndex; + } + + if (behavior.unit == "rectangle") { + if (!behavior.addNew) { ourRange = new Range(start, start); } + start = posFromMouse(cm, event, true, true); + ourIndex = -1; + } else { + var range = rangeForUnit(cm, start, behavior.unit); + if (behavior.extend) + { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend); } + else + { ourRange = range; } + } + + if (!behavior.addNew) { + ourIndex = 0; + setSelection(doc$1, new Selection([ourRange], 0), sel_mouse); + startSel = doc$1.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc$1, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) { + setSelection(doc$1, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), + {scroll: false, origin: "*mouse"}); + startSel = doc$1.sel; + } else { + replaceOneSelection(doc$1, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) { return } + lastPos = pos; + + if (behavior.unit == "rectangle") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc$1, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc$1, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc$1, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); } + else if (text.length > leftPos) + { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); } + } + if (!ranges.length) { ranges.push(new Range(start, start)); } + setSelection(doc$1, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var range = rangeForUnit(cm, pos, behavior.unit); + var anchor = oldRange.anchor, head; + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + var ranges$1 = startSel.ranges.slice(0); + ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc$1, anchor), head)); + setSelection(doc$1, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle"); + if (!cur) { return } + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(doc(cm)); + extendTo(cur); + var visible = visibleLines(display, doc$1); + if (cur.line >= visible.to || cur.line < visible.from) + { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); } + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) { setTimeout(operation(cm, function () { + if (counter != curCount) { return } + display.scroller.scrollTop += outside; + extend(e); + }), 50); } + } + } + + function done(e) { + cm.state.selectingText = false; + counter = Infinity; + // If e is null or undefined we interpret this as someone trying + // to explicitly cancel the selection rather than the user + // letting go of the mouse button. + if (e) { + e_preventDefault(e); + display.input.focus(); + } + off(display.wrapper.ownerDocument, "mousemove", move); + off(display.wrapper.ownerDocument, "mouseup", up); + doc$1.history.lastSelOrigin = null; + } + + var move = operation(cm, function (e) { + if (e.buttons === 0 || !e_button(e)) { done(e); } + else { extend(e); } + }); + var up = operation(cm, done); + cm.state.selectingText = up; + on(display.wrapper.ownerDocument, "mousemove", move); + on(display.wrapper.ownerDocument, "mouseup", up); + } + + // Used when mouse-selecting to adjust the anchor to the proper side + // of a bidi jump depending on the visual position of the head. + function bidiSimplify(cm, range) { + var anchor = range.anchor; + var head = range.head; + var anchorLine = getLine(cm.doc, anchor.line); + if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range } + var order = getOrder(anchorLine); + if (!order) { return range } + var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index]; + if (part.from != anchor.ch && part.to != anchor.ch) { return range } + var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1); + if (boundary == 0 || boundary == order.length) { return range } + + // Compute the relative visual position of the head compared to the + // anchor (<0 is to the left, >0 to the right) + var leftSide; + if (head.line != anchor.line) { + leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0; + } else { + var headIndex = getBidiPartAt(order, head.ch, head.sticky); + var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1); + if (headIndex == boundary - 1 || headIndex == boundary) + { leftSide = dir < 0; } + else + { leftSide = dir > 0; } + } + + var usePart = order[boundary + (leftSide ? -1 : 0)]; + var from = leftSide == (usePart.level == 1); + var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before"; + return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head) + } + + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent) { + var mX, mY; + if (e.touches) { + mX = e.touches[0].clientX; + mY = e.touches[0].clientY; + } else { + try { mX = e.clientX; mY = e.clientY; } + catch(e$1) { return false } + } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false } + if (prevent) { e_preventDefault(e); } + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) } + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.display.gutterSpecs.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.display.gutterSpecs[i]; + signal(cm, type, cm, line, gutter.className, e); + return e_defaultPrevented(e) + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true) + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return } + if (signalDOMEvent(cm, e, "contextmenu")) { return } + if (!captureRightClick) { cm.display.input.onContextMenu(e); } + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) { return false } + return gutterEvent(cm, e, "gutterContextMenu", false) + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + var Init = {toString: function(){return "CodeMirror.Init"}}; + + var defaults = {}; + var optionHandlers = {}; + + function defineOptions(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) { optionHandlers[name] = + notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; } + } + + CodeMirror.defineOption = option; + + // Passed to option handlers when there is no old value. + CodeMirror.Init = Init; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function (cm, val) { return cm.setValue(val); }, true); + option("mode", null, function (cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function (cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + + option("lineSeparator", null, function (cm, val) { + cm.doc.lineSep = val; + if (!val) { return } + var newBreaks = [], lineNo = cm.doc.first; + cm.doc.iter(function (line) { + for (var pos = 0;;) { + var found = line.text.indexOf(val, pos); + if (found == -1) { break } + pos = found + val.length; + newBreaks.push(Pos(lineNo, found)); + } + lineNo++; + }); + for (var i = newBreaks.length - 1; i >= 0; i--) + { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); } + }); + option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/g, function (cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != Init) { cm.refresh(); } + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function () { + throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME + }, true); + option("spellcheck", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true); + option("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true); + option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function (cm) { + themeChanged(cm); + updateGutters(cm); + }, true); + option("keyMap", "default", function (cm, val, old) { + var next = getKeyMap(val); + var prev = old != Init && getKeyMap(old); + if (prev && prev.detach) { prev.detach(cm, next); } + if (next.attach) { next.attach(cm, prev || null); } + }); + option("extraKeys", null); + option("configureMouse", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function (cm, val) { + cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers); + updateGutters(cm); + }, true); + option("fixedGutter", true, function (cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function (cm) { return updateScrollbars(cm); }, true); + option("scrollbarStyle", "native", function (cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function (cm, val) { + cm.display.gutterSpecs = getGutters(cm.options.gutters, val); + updateGutters(cm); + }, true); + option("firstLineNumber", 1, updateGutters, true); + option("lineNumberFormatter", function (integer) { return integer; }, updateGutters, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + option("pasteLinesPerSelection", true); + option("selectionsMayTouch", false); + + option("readOnly", false, function (cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + } + cm.display.input.readOnlyChanged(val); + }); + + option("screenReaderLabel", null, function (cm, val) { + val = (val === '') ? null : val; + cm.display.input.screenReaderLabelChanged(val); + }); + + option("disableInput", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true); + option("dragDrop", true, dragDropChanged); + option("allowDropFileTypes", null); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function (cm, val) { return cm.doc.history.undoDepth = val; }); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function (cm) { return cm.refresh(); }, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function (cm, val) { + if (!val) { cm.display.input.resetPosition(); } + }); + + option("tabindex", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || ""; }); + option("autofocus", null); + option("direction", "ltr", function (cm, val) { return cm.doc.setDirection(val); }, true); + option("phrases", null); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.enter); + toggle(cm.display.scroller, "dragover", funcs.over); + toggle(cm.display.scroller, "dragleave", funcs.leave); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function () { return updateScrollbars(cm); }, 100); + } + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + var this$1 = this; + + if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) } + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + + var doc = options.value; + if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); } + else if (options.mode) { doc.modeOption = options.mode; } + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input, options); + display.wrapper.CodeMirror = this; + themeChanged(this); + if (options.lineWrapping) + { this.display.wrapper.className += " CodeMirror-wrap"; } + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll + selectingText: false, + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + if (options.autofocus && !mobile) { display.input.focus(); } + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); } + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || this.hasFocus()) + { setTimeout(function () { + if (this$1.hasFocus() && !this$1.state.focused) { onFocus(this$1); } + }, 20); } + else + { onBlur(this); } + + for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt)) + { optionHandlers[opt](this, options[opt], Init); } } + maybeUpdateLineNumberWidth(this); + if (options.finishInit) { options.finishInit(this); } + for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this); } + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + { display.lineDiv.style.textRendering = "auto"; } + } + + // The default configuration options. + CodeMirror.defaults = defaults; + // Functions to run when options are changed. + CodeMirror.optionHandlers = optionHandlers; + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + { on(d.scroller, "dblclick", operation(cm, function (e) { + if (signalDOMEvent(cm, e)) { return } + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return } + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); } + else + { on(d.scroller, "dblclick", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); } + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); }); + on(d.input.getField(), "contextmenu", function (e) { + if (!d.scroller.contains(e.target)) { onContextMenu(cm, e); } + }); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + } + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) { return false } + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1 + } + function farAway(touch, other) { + if (other.left == null) { return true } + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20 + } + on(d.scroller, "touchstart", function (e) { + if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) { + d.input.ensurePolled(); + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function () { + if (d.activeTouch) { d.activeTouch.moved = true; } + }); + on(d.scroller, "touchend", function (e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + { range = new Range(pos, pos); } + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + { range = cm.findWordAt(pos); } + else // Triple tap + { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); } + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function () { + if (d.scroller.clientHeight) { + updateScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function (e) { return onScrollWheel(cm, e); }); + on(d.scroller, "DOMMouseScroll", function (e) { return onScrollWheel(cm, e); }); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }}, + over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, + start: function (e) { return onDragStart(cm, e); }, + drop: operation(cm, onDrop), + leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }} + }; + + var inp = d.input.getField(); + on(inp, "keyup", function (e) { return onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", function (e) { return onFocus(cm, e); }); + on(inp, "blur", function (e) { return onBlur(cm, e); }); + } + + var initHooks = []; + CodeMirror.defineInitHook = function (f) { return initHooks.push(f); }; + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) { how = "add"; } + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) { how = "prev"; } + else { state = getContextBefore(cm, n).state; } + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) { line.stateAfter = null; } + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) { return } + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); } + else { indentation = 0; } + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} } + if (pos < indentation) { indentString += spaceStr(indentation - pos); } + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + line.stateAfter = null; + return true + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) { + var range = doc.sel.ranges[i$1]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos$1 = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i$1, new Range(pos$1, pos$1)); + break + } + } + } + } + + // This will be set to a {lineWise: bool, text: [string]} object, so + // that, when pasting, we know what kind of selections the copied + // text was made out of. + var lastCopied = null; + + function setLastCopied(newLastCopied) { + lastCopied = newLastCopied; + } + + function applyTextInput(cm, inserted, deleted, sel, origin) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) { sel = doc.sel; } + + var recent = +new Date - 200; + var paste = origin == "paste" || cm.state.pasteIncoming > recent; + var textLines = splitLinesAuto(inserted), multiPaste = null; + // When pasting N lines into N selections, insert one line per selection + if (paste && sel.ranges.length > 1) { + if (lastCopied && lastCopied.text.join("\n") == inserted) { + if (sel.ranges.length % lastCopied.text.length == 0) { + multiPaste = []; + for (var i = 0; i < lastCopied.text.length; i++) + { multiPaste.push(doc.splitLines(lastCopied.text[i])); } + } + } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) { + multiPaste = map(textLines, function (l) { return [l]; }); + } + } + + var updateInput = cm.curOp.updateInput; + // Normal behavior is to insert the new text into every selection + for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) { + var range = sel.ranges[i$1]; + var from = range.from(), to = range.to(); + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + { from = Pos(from.line, from.ch - deleted); } + else if (cm.state.overwrite && !paste) // Handle overwrite + { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); } + else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == textLines.join("\n")) + { from = to = Pos(from.line, 0); } + } + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines, + origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + } + if (inserted && !paste) + { triggerElectric(cm, inserted); } + + ensureCursorVisible(cm); + if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; } + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = -1; + } + + function handlePaste(e, cm) { + var pasted = e.clipboardData && e.clipboardData.getData("Text"); + if (pasted) { + e.preventDefault(); + if (!cm.isReadOnly() && !cm.options.disableInput && cm.hasFocus()) + { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); } + return true + } + } + + function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) { return } + var sel = cm.doc.sel; + + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue } + var mode = cm.getModeAt(range.head); + var indented = false; + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range.head.line, "smart"); + break + } } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) + { indented = indentLine(cm, range.head.line, "smart"); } + } + if (indented) { signalLater(cm, "electricInput", cm, range.head.line); } + } + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges} + } + + function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) { + field.setAttribute("autocorrect", autocorrect ? "" : "off"); + field.setAttribute("autocapitalize", autocapitalize ? "" : "off"); + field.setAttribute("spellcheck", !!spellcheck); + } + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; min-height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) { te.style.width = "1000px"; } + else { te.setAttribute("wrap", "off"); } + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) { te.style.border = "1px solid black"; } + disableBrowserMagic(te); + return div + } + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + function addEditorMethods(CodeMirror) { + var optionHandlers = CodeMirror.optionHandlers; + + var helpers = CodeMirror.helpers = {}; + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){win(this).focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") { return } + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + { operation(this, optionHandlers[option])(this, value, old); } + signal(this, "optionChange", this, option); + }, + + getOption: function(option) {return this.options[option]}, + getDoc: function() {return this.doc}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + { if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1); + return true + } } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) { throw new Error("Overlays may not be stateful.") } + insertSorted(this.state.overlays, + {mode: mode, modeSpec: spec, opaque: options && options.opaque, + priority: (options && options.priority) || 0}, + function (overlay) { return overlay.priority; }); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) { dir = this.options.smartIndent ? "smart" : "prev"; } + else { dir = dir ? "add" : "subtract"; } + } + if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); } + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + { indentLine(this, j, how); } + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + { replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); } + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) { ensureCursorVisible(this); } + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise) + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true) + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) { type = styles[2]; } + else { for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; } + else if (styles[mid * 2 + 1] < ch) { before = mid + 1; } + else { type = styles[mid * 2 + 2]; break } + } } + var cut = type ? type.indexOf("overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) { return mode } + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0] + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) { return found } + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) { found.push(help[mode[type]]); } + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) { found.push(val); } + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i$1 = 0; i$1 < help._global.length; i$1++) { + var cur = help._global[i$1]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + { found.push(cur.val); } + } + return found + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getContextBefore(this, line + 1, precise).state + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) { pos = range.head; } + else if (typeof start == "object") { pos = clipPos(this.doc, start); } + else { pos = start ? range.from() : range.to(); } + return cursorCoords(this, pos, mode || "page") + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page") + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top) + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset) + }, + heightAtLine: function(line, mode, includeWidgets) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) { line = this.doc.first; } + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + + (end ? this.doc.height - heightAtLine(lineObj) : 0) + }, + + defaultTextHeight: function() { return textHeight(this.display) }, + defaultCharWidth: function() { return charWidth(this.display) }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + { top = pos.top - node.offsetHeight; } + else if (pos.bottom + node.offsetHeight <= vspace) + { top = pos.bottom; } + if (left + node.offsetWidth > hspace) + { left = hspace - node.offsetWidth; } + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") { left = 0; } + else if (horiz == "middle") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; } + node.style.left = left + "px"; + } + if (scroll) + { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); } + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + triggerOnMouseDown: methodOp(onMouseDown), + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + { return commands[cmd].call(null, this) } + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + var cur = clipPos(this.doc, from); + for (var i = 0; i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) { break } + } + return cur + }, + + moveH: methodOp(function(dir, unit) { + var this$1 = this; + + this.extendSelectionsBy(function (range) { + if (this$1.display.shift || this$1.doc.extend || range.empty()) + { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) } + else + { return dir < 0 ? range.from() : range.to() } + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + { doc.replaceSelection("", null, "+delete"); } + else + { deleteNearSelection(this, function (range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} + }); } + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + var cur = clipPos(this.doc, from); + for (var i = 0; i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) { x = coords.left; } + else { coords.left = x; } + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) { break } + } + return cur + }, + + moveV: methodOp(function(dir, unit) { + var this$1 = this; + + var doc = this.doc, goals = []; + var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function (range) { + if (collapse) + { return dir < 0 ? range.from() : range.to() } + var headPos = cursorCoords(this$1, range.head, "div"); + if (range.goalColumn != null) { headPos.left = range.goalColumn; } + goals.push(headPos.left); + var pos = findPosV(this$1, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + { addToScrollTop(this$1, charCoords(this$1, pos, "div").top - headPos.top); } + return pos + }, sel_move); + if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++) + { doc.sel.ranges[i].goalColumn = goals[i]; } } + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.sticky == "before" || end == line.length) && start) { --start; } else { ++end; } + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function (ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function (ch) { return /\s/.test(ch); } + : function (ch) { return (!/\s/.test(ch) && !isWordChar(ch)); }; + while (start > 0 && check(line.charAt(start - 1))) { --start; } + while (end < line.length && check(line.charAt(end))) { ++end; } + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)) + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) { return } + if (this.state.overwrite = !this.state.overwrite) + { addClass(this.display.cursorDiv, "CodeMirror-overwrite"); } + else + { rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); } + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt(doc(this)) }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, + + scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)} + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) { margin = this.options.cursorScrollMargin; } + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) { range.to = range.from; } + range.margin = margin || 0; + + if (range.from.line != null) { + scrollToRange(this, range); + } else { + scrollToCoordsRange(this, range.from, range.to, range.margin); + } + }), + + setSize: methodOp(function(width, height) { + var this$1 = this; + + var interpret = function (val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; }; + if (width != null) { this.display.wrapper.style.width = interpret(width); } + if (height != null) { this.display.wrapper.style.height = interpret(height); } + if (this.options.lineWrapping) { clearLineMeasurementCache(this); } + var lineNo = this.display.viewFrom; + this.doc.iter(lineNo, this.display.viewTo, function (line) { + if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) + { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, "widget"); break } } } + ++lineNo; + }); + this.curOp.forceUpdate = true; + signal(this, "refresh", this); + }), + + operation: function(f){return runInOp(this, f)}, + startOperation: function(){return startOperation(this)}, + endOperation: function(){return endOperation(this)}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this.display); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5 || this.options.lineWrapping) + { estimateLineHeights(this); } + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + // Cancel the current text selection if any (#5821) + if (this.state.selectingText) { this.state.selectingText(); } + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + scrollToCoords(this, doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old + }), + + phrase: function(phraseText) { + var phrases = this.options.phrases; + return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText + }, + + getInputField: function(){return this.display.input.getField()}, + getWrapperElement: function(){return this.display.wrapper}, + getScrollerElement: function(){return this.display.scroller}, + getGutterElement: function(){return this.display.gutters} + }; + eventMixin(CodeMirror); + + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; } + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "codepoint", "char", "column" (like char, but + // doesn't cross line boundaries), "word" (across next word), or + // "group" (to the start of next group of word or + // non-word-non-whitespace chars). The visually param controls + // whether, in right-to-left text, direction 1 means to move towards + // the next index in the string, or towards the character to the right + // of the current position. The resulting position will have a + // hitSide=true property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var oldPos = pos; + var origDir = dir; + var lineObj = getLine(doc, pos.line); + var lineDir = visually && doc.direction == "rtl" ? -dir : dir; + function findNextLine() { + var l = pos.line + lineDir; + if (l < doc.first || l >= doc.first + doc.size) { return false } + pos = new Pos(l, pos.ch, pos.sticky); + return lineObj = getLine(doc, l) + } + function moveOnce(boundToLine) { + var next; + if (unit == "codepoint") { + var ch = lineObj.text.charCodeAt(pos.ch + (dir > 0 ? 0 : -1)); + if (isNaN(ch)) { + next = null; + } else { + var astral = dir > 0 ? ch >= 0xD800 && ch < 0xDC00 : ch >= 0xDC00 && ch < 0xDFFF; + next = new Pos(pos.line, Math.max(0, Math.min(lineObj.text.length, pos.ch + dir * (astral ? 2 : 1))), -dir); + } + } else if (visually) { + next = moveVisually(doc.cm, lineObj, pos, dir); + } else { + next = moveLogically(lineObj, pos, dir); + } + if (next == null) { + if (!boundToLine && findNextLine()) + { pos = endOfLine(visually, doc.cm, lineObj, pos.line, lineDir); } + else + { return false } + } else { + pos = next; + } + return true + } + + if (unit == "char" || unit == "codepoint") { + moveOnce(); + } else if (unit == "column") { + moveOnce(true); + } else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) { break } + var cur = lineObj.text.charAt(pos.ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) { type = "s"; } + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after";} + break + } + + if (type) { sawType = type; } + if (dir > 0 && !moveOnce(!first)) { break } + } + } + var result = skipAtomic(doc, pos, oldPos, origDir, true); + if (equalCursorPos(oldPos, result)) { result.hitSide = true; } + return result + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, win(cm).innerHeight || doc(cm).documentElement.clientHeight); + var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3); + y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount; + + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + var target; + for (;;) { + target = coordsChar(cm, x, y); + if (!target.outside) { break } + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break } + y += dir * 5; + } + return target + } + + // CONTENTEDITABLE INPUT STYLE + + var ContentEditableInput = function(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.composing = null; + this.gracePeriod = false; + this.readDOMTimeout = null; + }; + + ContentEditableInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + div.contentEditable = true; + disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize); + + function belongsToInput(e) { + for (var t = e.target; t; t = t.parentNode) { + if (t == div) { return true } + if (/\bCodeMirror-(?:line)?widget\b/.test(t.className)) { break } + } + return false + } + + on(div, "paste", function (e) { + if (!belongsToInput(e) || signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + // IE doesn't fire input events, so we schedule a read for the pasted content in this way + if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); } + }); + + on(div, "compositionstart", function (e) { + this$1.composing = {data: e.data, done: false}; + }); + on(div, "compositionupdate", function (e) { + if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; } + }); + on(div, "compositionend", function (e) { + if (this$1.composing) { + if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); } + this$1.composing.done = true; + } + }); + + on(div, "touchstart", function () { return input.forceCompositionEnd(); }); + + on(div, "input", function () { + if (!this$1.composing) { this$1.readFromDOMSoon(); } + }); + + function onCopyCut(e) { + if (!belongsToInput(e) || signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}); + if (e.type == "cut") { cm.replaceSelection("", null, "cut"); } + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm); + setLastCopied({lineWise: true, text: ranges.text}); + if (e.type == "cut") { + cm.operation(function () { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + if (e.clipboardData) { + e.clipboardData.clearData(); + var content = lastCopied.text.join("\n"); + // iOS exposes the clipboard API, but seems to discard content inserted into it + e.clipboardData.setData("Text", content); + if (e.clipboardData.getData("Text") == content) { + e.preventDefault(); + return + } + } + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.text.join("\n"); + var hadFocus = activeElt(div.ownerDocument); + selectInput(te); + setTimeout(function () { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + if (hadFocus == div) { input.showPrimarySelection(); } + }, 50); + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }; + + ContentEditableInput.prototype.screenReaderLabelChanged = function (label) { + // Label for screenreaders, accessibility + if(label) { + this.div.setAttribute('aria-label', label); + } else { + this.div.removeAttribute('aria-label'); + } + }; + + ContentEditableInput.prototype.prepareSelection = function () { + var result = prepareSelection(this.cm, false); + result.focus = activeElt(this.div.ownerDocument) == this.div; + return result + }; + + ContentEditableInput.prototype.showSelection = function (info, takeFocus) { + if (!info || !this.cm.display.view.length) { return } + if (info.focus || takeFocus) { this.showPrimarySelection(); } + this.showMultipleSelections(info); + }; + + ContentEditableInput.prototype.getSelection = function () { + return this.cm.display.wrapper.ownerDocument.getSelection() + }; + + ContentEditableInput.prototype.showPrimarySelection = function () { + var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary(); + var from = prim.from(), to = prim.to(); + + if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { + sel.removeAllRanges(); + return + } + + var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), from) == 0 && + cmp(maxPos(curAnchor, curFocus), to) == 0) + { return } + + var view = cm.display.view; + var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || + {node: view[0].measure.map[2], offset: 0}; + var end = to.line < cm.display.viewTo && posToDOM(cm, to); + if (!end) { + var measure = view[view.length - 1].measure; + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; + } + + if (!start || !end) { + sel.removeAllRanges(); + return + } + + var old = sel.rangeCount && sel.getRangeAt(0), rng; + try { rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && cm.state.focused) { + sel.collapse(start.node, start.offset); + if (!rng.collapsed) { + sel.removeAllRanges(); + sel.addRange(rng); + } + } else { + sel.removeAllRanges(); + sel.addRange(rng); + } + if (old && sel.anchorNode == null) { sel.addRange(old); } + else if (gecko) { this.startGracePeriod(); } + } + this.rememberSelection(); + }; + + ContentEditableInput.prototype.startGracePeriod = function () { + var this$1 = this; + + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function () { + this$1.gracePeriod = false; + if (this$1.selectionChanged()) + { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); } + }, 20); + }; + + ContentEditableInput.prototype.showMultipleSelections = function (info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }; + + ContentEditableInput.prototype.rememberSelection = function () { + var sel = this.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }; + + ContentEditableInput.prototype.selectionInEditor = function () { + var sel = this.getSelection(); + if (!sel.rangeCount) { return false } + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node) + }; + + ContentEditableInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor") { + if (!this.selectionInEditor() || activeElt(this.div.ownerDocument) != this.div) + { this.showSelection(this.prepareSelection(), true); } + this.div.focus(); + } + }; + ContentEditableInput.prototype.blur = function () { this.div.blur(); }; + ContentEditableInput.prototype.getField = function () { return this.div }; + + ContentEditableInput.prototype.supportsTouch = function () { return true }; + + ContentEditableInput.prototype.receivedFocus = function () { + var this$1 = this; + + var input = this; + if (this.selectionInEditor()) + { setTimeout(function () { return this$1.pollSelection(); }, 20); } + else + { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); } + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }; + + ContentEditableInput.prototype.selectionChanged = function () { + var sel = this.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset + }; + + ContentEditableInput.prototype.pollSelection = function () { + if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return } + var sel = this.getSelection(), cm = this.cm; + // On Android Chrome (version 56, at least), backspacing into an + // uneditable block element will put the cursor in that element, + // and then, because it's not editable, hide the virtual keyboard. + // Because Android doesn't allow us to actually detect backspace + // presses in a sane way, this code checks for when that happens + // and simulates a backspace press in this case. + if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) { + this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}); + this.blur(); + this.focus(); + return + } + if (this.composing) { return } + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) { runInOp(cm, function () { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; } + }); } + }; + + ContentEditableInput.prototype.pollContent = function () { + if (this.readDOMTimeout != null) { + clearTimeout(this.readDOMTimeout); + this.readDOMTimeout = null; + } + + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.ch == 0 && from.line > cm.firstLine()) + { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); } + if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) + { to = Pos(to.line + 1, 0); } + if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false } + + var fromIndex, fromLine, fromNode; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + fromLine = lineNo(display.view[0].line); + fromNode = display.view[0].node; + } else { + fromLine = lineNo(display.view[fromIndex].line); + fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + var toLine, toNode; + if (toIndex == display.view.length - 1) { + toLine = display.viewTo - 1; + toNode = display.lineDiv.lastChild; + } else { + toLine = lineNo(display.view[toIndex + 1].line) - 1; + toNode = display.view[toIndex + 1].node.previousSibling; + } + + if (!fromNode) { return false } + var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else { break } + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + { ++cutFront; } + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + { ++cutEnd; } + // Try to move start of change to start of selection if ambiguous + if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { + while (cutFront && cutFront > from.ch && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { + cutFront--; + cutEnd++; + } + } + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, ""); + newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, ""); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true + } + }; + + ContentEditableInput.prototype.ensurePolled = function () { + this.forceCompositionEnd(); + }; + ContentEditableInput.prototype.reset = function () { + this.forceCompositionEnd(); + }; + ContentEditableInput.prototype.forceCompositionEnd = function () { + if (!this.composing) { return } + clearTimeout(this.readDOMTimeout); + this.composing = null; + this.updateFromDOM(); + this.div.blur(); + this.div.focus(); + }; + ContentEditableInput.prototype.readFromDOMSoon = function () { + var this$1 = this; + + if (this.readDOMTimeout != null) { return } + this.readDOMTimeout = setTimeout(function () { + this$1.readDOMTimeout = null; + if (this$1.composing) { + if (this$1.composing.done) { this$1.composing = null; } + else { return } + } + this$1.updateFromDOM(); + }, 80); + }; + + ContentEditableInput.prototype.updateFromDOM = function () { + var this$1 = this; + + if (this.cm.isReadOnly() || !this.pollContent()) + { runInOp(this.cm, function () { return regChange(this$1.cm); }); } + }; + + ContentEditableInput.prototype.setUneditable = function (node) { + node.contentEditable = "false"; + }; + + ContentEditableInput.prototype.onKeyPress = function (e) { + if (e.charCode == 0 || this.composing) { return } + e.preventDefault(); + if (!this.cm.isReadOnly()) + { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); } + }; + + ContentEditableInput.prototype.readOnlyChanged = function (val) { + this.div.contentEditable = String(val != "nocursor"); + }; + + ContentEditableInput.prototype.onContextMenu = function () {}; + ContentEditableInput.prototype.resetPosition = function () {}; + + ContentEditableInput.prototype.needsContentAttribute = true; + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) { return null } + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line, cm.doc.direction), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); + result.offset = result.collapse == "right" ? result.end : result.start; + return result + } + + function isInGutter(node) { + for (var scan = node; scan; scan = scan.parentNode) + { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } } + return false + } + + function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false; + function recognizeMarker(id) { return function (marker) { return marker.id == id; } } + function close() { + if (closing) { + text += lineSep; + if (extraLinebreak) { text += lineSep; } + closing = extraLinebreak = false; + } + } + function addText(str) { + if (str) { + close(); + text += str; + } + } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText) { + addText(cmText); + return + } + var markerID = node.getAttribute("cm-marker"), range; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range = found[0].find(0))) + { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)); } + return + } + if (node.getAttribute("contenteditable") == "false") { return } + var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName); + if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return } + + if (isBlock) { close(); } + for (var i = 0; i < node.childNodes.length; i++) + { walk(node.childNodes[i]); } + + if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; } + if (isBlock) { closing = true; } + } else if (node.nodeType == 3) { + addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " ")); + } + } + for (;;) { + walk(from); + if (from == to) { break } + from = from.nextSibling; + extraLinebreak = false; + } + return text + } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) } + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) { return null } + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break } + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + { return locateNodeInLineView(lineView, node, offset) } + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) } + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad) + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) { offset = textNode.nodeValue.length; } + } + while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; } + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map[j] + offset; + if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)]; } + return Pos(line, ch) + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) { return badPos(found, bad) } + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + { return badPos(Pos(found.line, found.ch - dist), bad) } + else + { dist += after.textContent.length; } + } + for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + { return badPos(Pos(found.line, found.ch + dist$1), bad) } + else + { dist$1 += before.textContent.length; } + } + } + + // TEXTAREA INPUT STYLE + + var TextareaInput = function(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + this.composing = null; + this.resetting = false; + }; + + TextareaInput.prototype.init = function (display) { + var this$1 = this; + + var input = this, cm = this.cm; + this.createField(display); + var te = this.textarea; + + display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) { te.style.width = "0px"; } + + on(te, "input", function () { + if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; } + input.poll(); + }); + + on(te, "paste", function (e) { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return } + + cm.state.pasteIncoming = +new Date; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) { return } + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}); + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + var ranges = copyableRanges(cm); + setLastCopied({lineWise: true, text: ranges.text}); + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") { cm.state.cutIncoming = +new Date; } + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function (e) { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return } + if (!te.dispatchEvent) { + cm.state.pasteIncoming = +new Date; + input.focus(); + return + } + + // Pass the `paste` event to the textarea so it's handled by its event listener. + var event = new Event("paste"); + event.clipboardData = e.clipboardData; + te.dispatchEvent(event); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function (e) { + if (!eventInWidget(display, e)) { e_preventDefault(e); } + }); + + on(te, "compositionstart", function () { + var start = cm.getCursor("from"); + if (input.composing) { input.composing.range.clear(); } + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + }; + }); + on(te, "compositionend", function () { + if (input.composing) { + input.poll(); + input.composing.range.clear(); + input.composing = null; + } + }); + }; + + TextareaInput.prototype.createField = function (_display) { + // Wraps and hides input textarea + this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + this.textarea = this.wrapper.firstChild; + }; + + TextareaInput.prototype.screenReaderLabelChanged = function (label) { + // Label for screenreaders, accessibility + if(label) { + this.textarea.setAttribute('aria-label', label); + } else { + this.textarea.removeAttribute('aria-label'); + } + }; + + TextareaInput.prototype.prepareSelection = function () { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result + }; + + TextareaInput.prototype.showSelection = function (drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }; + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + TextareaInput.prototype.reset = function (typing) { + if (this.contextMenuPending || this.composing && typing) { return } + var cm = this.cm; + this.resetting = true; + if (cm.somethingSelected()) { + this.prevInput = ""; + var content = cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) { selectInput(this.textarea); } + if (ie && ie_version >= 9) { this.hasSelection = content; } + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) { this.hasSelection = null; } + } + this.resetting = false; + }; + + TextareaInput.prototype.getField = function () { return this.textarea }; + + TextareaInput.prototype.supportsTouch = function () { return false }; + + TextareaInput.prototype.focus = function () { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(this.textarea.ownerDocument) != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }; + + TextareaInput.prototype.blur = function () { this.textarea.blur(); }; + + TextareaInput.prototype.resetPosition = function () { + this.wrapper.style.top = this.wrapper.style.left = 0; + }; + + TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); }; + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + TextareaInput.prototype.slowPoll = function () { + var this$1 = this; + + if (this.pollingFast) { return } + this.polling.set(this.cm.options.pollInterval, function () { + this$1.poll(); + if (this$1.cm.state.focused) { this$1.slowPoll(); } + }); + }; + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + TextareaInput.prototype.fastPoll = function () { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }; + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + TextareaInput.prototype.poll = function () { + var this$1 = this; + + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || this.resetting || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + { return false } + + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) { return false } + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) { prevInput = "\u200b"; } + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; } + + runInOp(cm, function () { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, this$1.composing ? "*compose" : null); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) { input.value = this$1.prevInput = ""; } + else { this$1.prevInput = text; } + + if (this$1.composing) { + this$1.composing.range.clear(); + this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}); + } + }); + return true + }; + + TextareaInput.prototype.ensurePolled = function () { + if (this.pollingFast && this.poll()) { this.pollingFast = false; } + }; + + TextareaInput.prototype.onKeyPress = function () { + if (ie && ie_version >= 9) { this.hasSelection = null; } + this.fastPoll(); + }; + + TextareaInput.prototype.onContextMenu = function (e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + if (input.contextMenuPending) { input.contextMenuPending(); } + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) { return } // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); } + + var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText; + var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect(); + input.wrapper.style.cssText = "position: static"; + te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + var oldScrollY; + if (webkit) { oldScrollY = te.ownerDocument.defaultView.scrollY; } // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) { te.ownerDocument.defaultView.scrollTo(null, oldScrollY); } + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) { te.value = input.prevInput = " "; } + input.contextMenuPending = rehide; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + if (input.contextMenuPending != rehide) { return } + input.contextMenuPending = false; + input.wrapper.style.cssText = oldWrapperCSS; + te.style.cssText = oldCSS; + if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); } + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); } + var i = 0, poll = function () { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") { + operation(cm, selectAll)(cm); + } else if (i++ < 10) { + display.detectingSelectAll = setTimeout(poll, 500); + } else { + display.selForContextMenu = null; + display.input.reset(); + } + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) { prepareSelectAllHack(); } + if (captureRightClick) { + e_stop(e); + var mouseup = function () { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }; + + TextareaInput.prototype.readOnlyChanged = function (val) { + if (!val) { this.reset(); } + this.textarea.disabled = val == "nocursor"; + this.textarea.readOnly = !!val; + }; + + TextareaInput.prototype.setUneditable = function () {}; + + TextareaInput.prototype.needsContentAttribute = false; + + function fromTextArea(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + { options.tabindex = textarea.tabIndex; } + if (!options.placeholder && textarea.placeholder) + { options.placeholder = textarea.placeholder; } + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(textarea.ownerDocument); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + + var realSubmit; + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form; + realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function () { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function (cm) { + cm.save = save; + cm.getTextArea = function () { return textarea; }; + cm.toTextArea = function () { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == "function") + { textarea.form.submit = realSubmit; } + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); }, + options); + return cm + } + + function addLegacyProps(CodeMirror) { + CodeMirror.off = off; + CodeMirror.on = on; + CodeMirror.wheelEventPixels = wheelEventPixels; + CodeMirror.Doc = Doc; + CodeMirror.splitLines = splitLinesAuto; + CodeMirror.countColumn = countColumn; + CodeMirror.findColumn = findColumn; + CodeMirror.isWordChar = isWordCharBasic; + CodeMirror.Pass = Pass; + CodeMirror.signal = signal; + CodeMirror.Line = Line; + CodeMirror.changeEnd = changeEnd; + CodeMirror.scrollbarModel = scrollbarModel; + CodeMirror.Pos = Pos; + CodeMirror.cmpPos = cmp; + CodeMirror.modes = modes; + CodeMirror.mimeModes = mimeModes; + CodeMirror.resolveMode = resolveMode; + CodeMirror.getMode = getMode; + CodeMirror.modeExtensions = modeExtensions; + CodeMirror.extendMode = extendMode; + CodeMirror.copyState = copyState; + CodeMirror.startState = startState; + CodeMirror.innerMode = innerMode; + CodeMirror.commands = commands; + CodeMirror.keyMap = keyMap; + CodeMirror.keyName = keyName; + CodeMirror.isModifierKey = isModifierKey; + CodeMirror.lookupKey = lookupKey; + CodeMirror.normalizeKeyMap = normalizeKeyMap; + CodeMirror.StringStream = StringStream; + CodeMirror.SharedTextMarker = SharedTextMarker; + CodeMirror.TextMarker = TextMarker; + CodeMirror.LineWidget = LineWidget; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + CodeMirror.e_stop = e_stop; + CodeMirror.addClass = addClass; + CodeMirror.contains = contains; + CodeMirror.rmClass = rmClass; + CodeMirror.keyNames = keyNames; + } + + // EDITOR CONSTRUCTOR + + defineOptions(CodeMirror); + + addEditorMethods(CodeMirror); + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); + for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + { CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments)} + })(Doc.prototype[prop]); } } + + eventMixin(Doc); + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name/*, mode, …*/) { + if (!CodeMirror.defaults.mode && name != "null") { CodeMirror.defaults.mode = name; } + defineMode.apply(this, arguments); + }; + + CodeMirror.defineMIME = defineMIME; + + // Minimal default mode. + CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); }); + CodeMirror.defineMIME("text/plain", "null"); + + // EXTENSIONS + + CodeMirror.defineExtension = function (name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function (name, func) { + Doc.prototype[name] = func; + }; + + CodeMirror.fromTextArea = fromTextArea; + + addLegacyProps(CodeMirror); + + CodeMirror.version = "5.65.9"; + + return CodeMirror; + +}))); diff --git a/public/codemirror/mode/clike/clike.js b/public/codemirror/mode/clike/clike.js new file mode 100644 index 0000000..748909e --- /dev/null +++ b/public/codemirror/mode/clike/clike.js @@ -0,0 +1,940 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/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 Context(indented, column, type, info, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.info = info; + this.align = align; + this.prev = prev; +} +function pushContext(state, col, type, info) { + var indent = state.indented; + if (state.context && state.context.type == "statement" && type != "statement") + indent = state.context.indented; + return state.context = new Context(indent, col, type, info, null, state.context); +} +function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; +} + +function typeBefore(stream, state, pos) { + if (state.prevToken == "variable" || state.prevToken == "type") return true; + if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true; + if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true; +} + +function isTopScope(context) { + for (;;) { + if (!context || context.type == "top") return true; + if (context.type == "}" && context.prev.info != "namespace") return false; + context = context.prev; + } +} + +CodeMirror.defineMode("clike", function(config, parserConfig) { + var indentUnit = config.indentUnit, + statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, + dontAlignCalls = parserConfig.dontAlignCalls, + keywords = parserConfig.keywords || {}, + types = parserConfig.types || {}, + builtin = parserConfig.builtin || {}, + blockKeywords = parserConfig.blockKeywords || {}, + defKeywords = parserConfig.defKeywords || {}, + atoms = parserConfig.atoms || {}, + hooks = parserConfig.hooks || {}, + multiLineStrings = parserConfig.multiLineStrings, + indentStatements = parserConfig.indentStatements !== false, + indentSwitch = parserConfig.indentSwitch !== false, + namespaceSeparator = parserConfig.namespaceSeparator, + isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/, + numberStart = parserConfig.numberStart || /[\d\.]/, + number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i, + isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/, + isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/, + // An optional function that takes a {string} token and returns true if it + // should be treated as a builtin. + isReservedIdentifier = parserConfig.isReservedIdentifier || false; + + var curPunc, isDefKeyword; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (numberStart.test(ch)) { + stream.backUp(1) + if (stream.match(number)) return "number" + stream.next() + } + if (isPunctuationChar.test(ch)) { + curPunc = ch; + return null; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {} + return "operator"; + } + stream.eatWhile(isIdentifierChar); + if (namespaceSeparator) while (stream.match(namespaceSeparator)) + stream.eatWhile(isIdentifierChar); + + var cur = stream.current(); + if (contains(keywords, cur)) { + if (contains(blockKeywords, cur)) curPunc = "newstatement"; + if (contains(defKeywords, cur)) isDefKeyword = true; + return "keyword"; + } + if (contains(types, cur)) return "type"; + if (contains(builtin, cur) + || (isReservedIdentifier && isReservedIdentifier(cur))) { + if (contains(blockKeywords, cur)) curPunc = "newstatement"; + return "builtin"; + } + if (contains(atoms, cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function maybeEOL(stream, state) { + if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context)) + state.typeAtEndOfLine = typeBefore(stream, state, stream.pos) + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false), + indented: 0, + startOfLine: true, + prevToken: null + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) { maybeEOL(stream, state); return null; } + curPunc = isDefKeyword = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == ";" || curPunc == ":" || (curPunc == "," && stream.match(/^\s*(?:\/\/.*)?$/, false))) + while (state.context.type == "statement") popContext(state); + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (indentStatements && + (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") || + (ctx.type == "statement" && curPunc == "newstatement"))) { + pushContext(state, stream.column(), "statement", stream.current()); + } + + if (style == "variable" && + ((state.prevToken == "def" || + (parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) && + isTopScope(state.context) && stream.match(/^\s*\(/, false))))) + style = "def"; + + if (hooks.token) { + var result = hooks.token(stream, state, style); + if (result !== undefined) style = result; + } + + if (style == "def" && parserConfig.styleDefs === false) style = "variable"; + + state.startOfLine = false; + state.prevToken = isDefKeyword ? "def" : style || curPunc; + maybeEOL(stream, state); + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + var closing = firstChar == ctx.type; + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + if (parserConfig.dontIndentStatements) + while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info)) + ctx = ctx.prev + if (hooks.indent) { + var hook = hooks.indent(state, ctx, textAfter, indentUnit); + if (typeof hook == "number") return hook + } + var switchBlock = ctx.prev && ctx.prev.info == "switch"; + if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) { + while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev + return ctx.indented + } + if (ctx.type == "statement") + return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); + if (ctx.align && (!dontAlignCalls || ctx.type != ")")) + return ctx.column + (closing ? 0 : 1); + if (ctx.type == ")" && !closing) + return ctx.indented + statementIndentUnit; + + return ctx.indented + (closing ? 0 : indentUnit) + + (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0); + }, + + electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/, + blockCommentStart: "/*", + blockCommentEnd: "*/", + blockCommentContinue: " * ", + lineComment: "//", + fold: "brace" + }; +}); + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + function contains(words, word) { + if (typeof words === "function") { + return words(word); + } else { + return words.propertyIsEnumerable(word); + } + } + var cKeywords = "auto if break case register continue return default do sizeof " + + "static else struct switch extern typedef union for goto while enum const " + + "volatile inline restrict asm fortran"; + + // Keywords from https://en.cppreference.com/w/cpp/keyword includes C++20. + var cppKeywords = "alignas alignof and and_eq audit axiom bitand bitor catch " + + "class compl concept constexpr const_cast decltype delete dynamic_cast " + + "explicit export final friend import module mutable namespace new noexcept " + + "not not_eq operator or or_eq override private protected public " + + "reinterpret_cast requires static_assert static_cast template this " + + "thread_local throw try typeid typename using virtual xor xor_eq"; + + var objCKeywords = "bycopy byref in inout oneway out self super atomic nonatomic retain copy " + + "readwrite readonly strong weak assign typeof nullable nonnull null_resettable _cmd " + + "@interface @implementation @end @protocol @encode @property @synthesize @dynamic @class " + + "@public @package @private @protected @required @optional @try @catch @finally @import " + + "@selector @encode @defs @synchronized @autoreleasepool @compatibility_alias @available"; + + var objCBuiltins = "FOUNDATION_EXPORT FOUNDATION_EXTERN NS_INLINE NS_FORMAT_FUNCTION " + + " NS_RETURNS_RETAINEDNS_ERROR_ENUM NS_RETURNS_NOT_RETAINED NS_RETURNS_INNER_POINTER " + + "NS_DESIGNATED_INITIALIZER NS_ENUM NS_OPTIONS NS_REQUIRES_NIL_TERMINATION " + + "NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_SWIFT_NAME NS_REFINED_FOR_SWIFT" + + // Do not use this. Use the cTypes function below. This is global just to avoid + // excessive calls when cTypes is being called multiple times during a parse. + var basicCTypes = words("int long char short double float unsigned signed " + + "void bool"); + + // Do not use this. Use the objCTypes function below. This is global just to avoid + // excessive calls when objCTypes is being called multiple times during a parse. + var basicObjCTypes = words("SEL instancetype id Class Protocol BOOL"); + + // Returns true if identifier is a "C" type. + // C type is defined as those that are reserved by the compiler (basicTypes), + // and those that end in _t (Reserved by POSIX for types) + // http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html + function cTypes(identifier) { + return contains(basicCTypes, identifier) || /.+_t$/.test(identifier); + } + + // Returns true if identifier is a "Objective C" type. + function objCTypes(identifier) { + return cTypes(identifier) || contains(basicObjCTypes, identifier); + } + + var cBlockKeywords = "case do else for if switch while struct enum union"; + var cDefKeywords = "struct enum union"; + + function cppHook(stream, state) { + if (!state.startOfLine) return false + for (var ch, next = null; ch = stream.peek();) { + if (ch == "\\" && stream.match(/^.$/)) { + next = cppHook + break + } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) { + break + } + stream.next() + } + state.tokenize = next + return "meta" + } + + function pointerHook(_stream, state) { + if (state.prevToken == "type") return "type"; + return false; + } + + // For C and C++ (and ObjC): identifiers starting with __ + // or _ followed by a capital letter are reserved for the compiler. + function cIsReservedIdentifier(token) { + if (!token || token.length < 2) return false; + if (token[0] != '_') return false; + return (token[1] == '_') || (token[1] !== token[1].toLowerCase()); + } + + function cpp14Literal(stream) { + stream.eatWhile(/[\w\.']/); + return "number"; + } + + function cpp11StringHook(stream, state) { + stream.backUp(1); + // Raw strings. + if (stream.match(/^(?:R|u8R|uR|UR|LR)/)) { + var match = stream.match(/^"([^\s\\()]{0,16})\(/); + if (!match) { + return false; + } + state.cpp11RawStringDelim = match[1]; + state.tokenize = tokenRawString; + return tokenRawString(stream, state); + } + // Unicode strings/chars. + if (stream.match(/^(?:u8|u|U|L)/)) { + if (stream.match(/^["']/, /* eat */ false)) { + return "string"; + } + return false; + } + // Ignore this hook. + stream.next(); + return false; + } + + function cppLooksLikeConstructor(word) { + var lastTwo = /(\w+)::~?(\w+)$/.exec(word); + return lastTwo && lastTwo[1] == lastTwo[2]; + } + + // C#-style strings where "" escapes a quote. + function tokenAtString(stream, state) { + var next; + while ((next = stream.next()) != null) { + if (next == '"' && !stream.eat('"')) { + state.tokenize = null; + break; + } + } + return "string"; + } + + // C++11 raw string literal is "( anything )", where + // can be a string up to 16 characters long. + function tokenRawString(stream, state) { + // Escape characters that have special regex meanings. + var delim = state.cpp11RawStringDelim.replace(/[^\w\s]/g, '\\$&'); + var match = stream.match(new RegExp(".*?\\)" + delim + '"')); + if (match) + state.tokenize = null; + else + stream.skipToEnd(); + return "string"; + } + + function def(mimes, mode) { + if (typeof mimes == "string") mimes = [mimes]; + var words = []; + function add(obj) { + if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) + words.push(prop); + } + add(mode.keywords); + add(mode.types); + add(mode.builtin); + add(mode.atoms); + if (words.length) { + mode.helperType = mimes[0]; + CodeMirror.registerHelper("hintWords", mimes[0], words); + } + + for (var i = 0; i < mimes.length; ++i) + CodeMirror.defineMIME(mimes[i], mode); + } + + def(["text/x-csrc", "text/x-c", "text/x-chdr"], { + name: "clike", + keywords: words(cKeywords), + types: cTypes, + blockKeywords: words(cBlockKeywords), + defKeywords: words(cDefKeywords), + typeFirstDefinitions: true, + atoms: words("NULL true false"), + isReservedIdentifier: cIsReservedIdentifier, + hooks: { + "#": cppHook, + "*": pointerHook, + }, + modeProps: {fold: ["brace", "include"]} + }); + + def(["text/x-c++src", "text/x-c++hdr"], { + name: "clike", + keywords: words(cKeywords + " " + cppKeywords), + types: cTypes, + blockKeywords: words(cBlockKeywords + " class try catch"), + defKeywords: words(cDefKeywords + " class namespace"), + typeFirstDefinitions: true, + atoms: words("true false NULL nullptr"), + dontIndentStatements: /^template$/, + isIdentifierChar: /[\w\$_~\xa1-\uffff]/, + isReservedIdentifier: cIsReservedIdentifier, + hooks: { + "#": cppHook, + "*": pointerHook, + "u": cpp11StringHook, + "U": cpp11StringHook, + "L": cpp11StringHook, + "R": cpp11StringHook, + "0": cpp14Literal, + "1": cpp14Literal, + "2": cpp14Literal, + "3": cpp14Literal, + "4": cpp14Literal, + "5": cpp14Literal, + "6": cpp14Literal, + "7": cpp14Literal, + "8": cpp14Literal, + "9": cpp14Literal, + token: function(stream, state, style) { + if (style == "variable" && stream.peek() == "(" && + (state.prevToken == ";" || state.prevToken == null || + state.prevToken == "}") && + cppLooksLikeConstructor(stream.current())) + return "def"; + } + }, + namespaceSeparator: "::", + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-java", { + name: "clike", + keywords: words("abstract assert break case catch class const continue default " + + "do else enum extends final finally for goto if implements import " + + "instanceof interface native new package private protected public " + + "return static strictfp super switch synchronized this throw throws transient " + + "try volatile while @interface"), + types: words("var byte short int long float double boolean char void Boolean Byte Character Double Float " + + "Integer Long Number Object Short String StringBuffer StringBuilder Void"), + blockKeywords: words("catch class do else finally for if switch try while"), + defKeywords: words("class interface enum @interface"), + typeFirstDefinitions: true, + atoms: words("true false null"), + number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i, + hooks: { + "@": function(stream) { + // Don't match the @interface keyword. + if (stream.match('interface', false)) return false; + + stream.eatWhile(/[\w\$_]/); + return "meta"; + }, + '"': function(stream, state) { + if (!stream.match(/""$/)) return false; + state.tokenize = tokenTripleString; + return state.tokenize(stream, state); + } + }, + modeProps: {fold: ["brace", "import"]} + }); + + def("text/x-csharp", { + name: "clike", + keywords: words("abstract as async await base break case catch checked class const continue" + + " default delegate do else enum event explicit extern finally fixed for" + + " foreach goto if implicit in interface internal is lock namespace new" + + " operator out override params private protected public readonly ref return sealed" + + " sizeof stackalloc static struct switch this throw try typeof unchecked" + + " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + + " global group into join let orderby partial remove select set value var yield"), + types: words("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func" + + " Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32" + + " UInt64 bool byte char decimal double short int long object" + + " sbyte float string ushort uint ulong"), + blockKeywords: words("catch class do else finally for foreach if struct switch try while"), + defKeywords: words("class interface namespace struct var"), + typeFirstDefinitions: true, + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + if (stream.eat('"')) { + state.tokenize = tokenAtString; + return tokenAtString(stream, state); + } + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); + + function tokenTripleString(stream, state) { + var escaped = false; + while (!stream.eol()) { + if (!escaped && stream.match('"""')) { + state.tokenize = null; + break; + } + escaped = stream.next() == "\\" && !escaped; + } + return "string"; + } + + function tokenNestedComment(depth) { + return function (stream, state) { + var ch + while (ch = stream.next()) { + if (ch == "*" && stream.eat("/")) { + if (depth == 1) { + state.tokenize = null + break + } else { + state.tokenize = tokenNestedComment(depth - 1) + return state.tokenize(stream, state) + } + } else if (ch == "/" && stream.eat("*")) { + state.tokenize = tokenNestedComment(depth + 1) + return state.tokenize(stream, state) + } + } + return "comment" + } + } + + def("text/x-scala", { + name: "clike", + keywords: words( + /* scala */ + "abstract case catch class def do else extends final finally for forSome if " + + "implicit import lazy match new null object override package private protected return " + + "sealed super this throw trait try type val var while with yield _ " + + + /* package scala */ + "assert assume require print println printf readLine readBoolean readByte readShort " + + "readChar readInt readLong readFloat readDouble" + ), + types: words( + "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + + "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " + + "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + + "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + + "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " + + + /* package java.lang */ + "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" + ), + multiLineStrings: true, + blockKeywords: words("catch class enum do else finally for forSome if match switch try while"), + defKeywords: words("class enum def object package trait type val var"), + atoms: words("true false null"), + indentStatements: false, + indentSwitch: false, + isOperatorChar: /[+\-*&%=<>!?|\/#:@]/, + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + }, + '"': function(stream, state) { + if (!stream.match('""')) return false; + state.tokenize = tokenTripleString; + return state.tokenize(stream, state); + }, + "'": function(stream) { + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + return "atom"; + }, + "=": function(stream, state) { + var cx = state.context + if (cx.type == "}" && cx.align && stream.eat(">")) { + state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev) + return "operator" + } else { + return false + } + }, + + "/": function(stream, state) { + if (!stream.eat("*")) return false + state.tokenize = tokenNestedComment(1) + return state.tokenize(stream, state) + } + }, + modeProps: {closeBrackets: {pairs: '()[]{}""', triples: '"'}} + }); + + function tokenKotlinString(tripleString){ + return function (stream, state) { + var escaped = false, next, end = false; + while (!stream.eol()) { + if (!tripleString && !escaped && stream.match('"') ) {end = true; break;} + if (tripleString && stream.match('"""')) {end = true; break;} + next = stream.next(); + if(!escaped && next == "$" && stream.match('{')) + stream.skipTo("}"); + escaped = !escaped && next == "\\" && !tripleString; + } + if (end || !tripleString) + state.tokenize = null; + return "string"; + } + } + + def("text/x-kotlin", { + name: "clike", + keywords: words( + /*keywords*/ + "package as typealias class interface this super val operator " + + "var fun for is in This throw return annotation " + + "break continue object if else while do try when !in !is as? " + + + /*soft keywords*/ + "file import where by get set abstract enum open inner override private public internal " + + "protected catch finally out final vararg reified dynamic companion constructor init " + + "sealed field property receiver param sparam lateinit data inline noinline tailrec " + + "external annotation crossinline const operator infix suspend actual expect setparam value" + ), + types: words( + /* package java.lang */ + "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray " + + "ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy " + + "LazyThreadSafetyMode LongArray Nothing ShortArray Unit" + ), + intendSwitch: false, + indentStatements: false, + multiLineStrings: true, + number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i, + blockKeywords: words("catch class do else finally for if where try while enum"), + defKeywords: words("class val var object interface fun"), + atoms: words("true false null this"), + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + }, + '*': function(_stream, state) { + return state.prevToken == '.' ? 'variable' : 'operator'; + }, + '"': function(stream, state) { + state.tokenize = tokenKotlinString(stream.match('""')); + return state.tokenize(stream, state); + }, + "/": function(stream, state) { + if (!stream.eat("*")) return false; + state.tokenize = tokenNestedComment(1); + return state.tokenize(stream, state) + }, + indent: function(state, ctx, textAfter, indentUnit) { + var firstChar = textAfter && textAfter.charAt(0); + if ((state.prevToken == "}" || state.prevToken == ")") && textAfter == "") + return state.indented; + if ((state.prevToken == "operator" && textAfter != "}" && state.context.type != "}") || + state.prevToken == "variable" && firstChar == "." || + (state.prevToken == "}" || state.prevToken == ")") && firstChar == ".") + return indentUnit * 2 + ctx.indented; + if (ctx.align && ctx.type == "}") + return ctx.indented + (state.context.type == (textAfter || "").charAt(0) ? 0 : indentUnit); + } + }, + modeProps: {closeBrackets: {triples: '"'}} + }); + + def(["x-shader/x-vertex", "x-shader/x-fragment"], { + name: "clike", + keywords: words("sampler1D sampler2D sampler3D samplerCube " + + "sampler1DShadow sampler2DShadow " + + "const attribute uniform varying " + + "break continue discard return " + + "for while do if else struct " + + "in out inout"), + types: words("float int bool void " + + "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " + + "mat2 mat3 mat4"), + blockKeywords: words("for while do if else struct"), + builtin: words("radians degrees sin cos tan asin acos atan " + + "pow exp log exp2 sqrt inversesqrt " + + "abs sign floor ceil fract mod min max clamp mix step smoothstep " + + "length distance dot cross normalize ftransform faceforward " + + "reflect refract matrixCompMult " + + "lessThan lessThanEqual greaterThan greaterThanEqual " + + "equal notEqual any all not " + + "texture1D texture1DProj texture1DLod texture1DProjLod " + + "texture2D texture2DProj texture2DLod texture2DProjLod " + + "texture3D texture3DProj texture3DLod texture3DProjLod " + + "textureCube textureCubeLod " + + "shadow1D shadow2D shadow1DProj shadow2DProj " + + "shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " + + "dFdx dFdy fwidth " + + "noise1 noise2 noise3 noise4"), + atoms: words("true false " + + "gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " + + "gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " + + "gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " + + "gl_FogCoord gl_PointCoord " + + "gl_Position gl_PointSize gl_ClipVertex " + + "gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " + + "gl_TexCoord gl_FogFragCoord " + + "gl_FragCoord gl_FrontFacing " + + "gl_FragData gl_FragDepth " + + "gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " + + "gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " + + "gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " + + "gl_TextureMatrixTranspose gl_ModelViewMatrixInverseTranspose " + + "gl_ProjectionMatrixInverseTranspose " + + "gl_ModelViewProjectionMatrixInverseTranspose " + + "gl_TextureMatrixInverseTranspose " + + "gl_NormalScale gl_DepthRange gl_ClipPlane " + + "gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " + + "gl_FrontLightModelProduct gl_BackLightModelProduct " + + "gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " + + "gl_FogParameters " + + "gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " + + "gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " + + "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " + + "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " + + "gl_MaxDrawBuffers"), + indentSwitch: false, + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-nesc", { + name: "clike", + keywords: words(cKeywords + " as atomic async call command component components configuration event generic " + + "implementation includes interface module new norace nx_struct nx_union post provides " + + "signal task uses abstract extends"), + types: cTypes, + blockKeywords: words(cBlockKeywords), + atoms: words("null true false"), + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-objectivec", { + name: "clike", + keywords: words(cKeywords + " " + objCKeywords), + types: objCTypes, + builtin: words(objCBuiltins), + blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized"), + defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class"), + dontIndentStatements: /^@.*$/, + typeFirstDefinitions: true, + atoms: words("YES NO NULL Nil nil true false nullptr"), + isReservedIdentifier: cIsReservedIdentifier, + hooks: { + "#": cppHook, + "*": pointerHook, + }, + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-objectivec++", { + name: "clike", + keywords: words(cKeywords + " " + objCKeywords + " " + cppKeywords), + types: objCTypes, + builtin: words(objCBuiltins), + blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized class try catch"), + defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class class namespace"), + dontIndentStatements: /^@.*$|^template$/, + typeFirstDefinitions: true, + atoms: words("YES NO NULL Nil nil true false nullptr"), + isReservedIdentifier: cIsReservedIdentifier, + hooks: { + "#": cppHook, + "*": pointerHook, + "u": cpp11StringHook, + "U": cpp11StringHook, + "L": cpp11StringHook, + "R": cpp11StringHook, + "0": cpp14Literal, + "1": cpp14Literal, + "2": cpp14Literal, + "3": cpp14Literal, + "4": cpp14Literal, + "5": cpp14Literal, + "6": cpp14Literal, + "7": cpp14Literal, + "8": cpp14Literal, + "9": cpp14Literal, + token: function(stream, state, style) { + if (style == "variable" && stream.peek() == "(" && + (state.prevToken == ";" || state.prevToken == null || + state.prevToken == "}") && + cppLooksLikeConstructor(stream.current())) + return "def"; + } + }, + namespaceSeparator: "::", + modeProps: {fold: ["brace", "include"]} + }); + + def("text/x-squirrel", { + name: "clike", + keywords: words("base break clone continue const default delete enum extends function in class" + + " foreach local resume return this throw typeof yield constructor instanceof static"), + types: cTypes, + blockKeywords: words("case catch class else for foreach if switch try while"), + defKeywords: words("function local class"), + typeFirstDefinitions: true, + atoms: words("true false null"), + hooks: {"#": cppHook}, + modeProps: {fold: ["brace", "include"]} + }); + + // Ceylon Strings need to deal with interpolation + var stringTokenizer = null; + function tokenCeylonString(type) { + return function(stream, state) { + var escaped = false, next, end = false; + while (!stream.eol()) { + if (!escaped && stream.match('"') && + (type == "single" || stream.match('""'))) { + end = true; + break; + } + if (!escaped && stream.match('``')) { + stringTokenizer = tokenCeylonString(type); + end = true; + break; + } + next = stream.next(); + escaped = type == "single" && !escaped && next == "\\"; + } + if (end) + state.tokenize = null; + return "string"; + } + } + + def("text/x-ceylon", { + name: "clike", + keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" + + " exists extends finally for function given if import in interface is let module new" + + " nonempty object of out outer package return satisfies super switch then this throw" + + " try value void while"), + types: function(word) { + // In Ceylon all identifiers that start with an uppercase are types + var first = word.charAt(0); + return (first === first.toUpperCase() && first !== first.toLowerCase()); + }, + blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"), + defKeywords: words("class dynamic function interface module object package value"), + builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" + + " native optional sealed see serializable shared suppressWarnings tagged throws variable"), + isPunctuationChar: /[\[\]{}\(\),;\:\.`]/, + isOperatorChar: /[+\-*&%=<>!?|^~:\/]/, + numberStart: /[\d#$]/, + number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i, + multiLineStrings: true, + typeFirstDefinitions: true, + atoms: words("true false null larger smaller equal empty finished"), + indentSwitch: false, + styleDefs: false, + hooks: { + "@": function(stream) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + }, + '"': function(stream, state) { + state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single"); + return state.tokenize(stream, state); + }, + '`': function(stream, state) { + if (!stringTokenizer || !stream.match('`')) return false; + state.tokenize = stringTokenizer; + stringTokenizer = null; + return state.tokenize(stream, state); + }, + "'": function(stream) { + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + return "atom"; + }, + token: function(_stream, state, style) { + if ((style == "variable" || style == "type") && + state.prevToken == ".") { + return "variable-2"; + } + } + }, + modeProps: { + fold: ["brace", "import"], + closeBrackets: {triples: '"'} + } + }); + +}); diff --git a/public/codemirror/mode/php/php.js b/public/codemirror/mode/php/php.js new file mode 100644 index 0000000..281c290 --- /dev/null +++ b/public/codemirror/mode/php/php.js @@ -0,0 +1,234 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/5/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../clike/clike")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../clike/clike"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + function keywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + // Helper for phpString + function matchSequence(list, end, escapes) { + if (list.length == 0) return phpString(end); + return function (stream, state) { + var patterns = list[0]; + for (var i = 0; i < patterns.length; i++) if (stream.match(patterns[i][0])) { + state.tokenize = matchSequence(list.slice(1), end); + return patterns[i][1]; + } + state.tokenize = phpString(end, escapes); + return "string"; + }; + } + function phpString(closing, escapes) { + return function(stream, state) { return phpString_(stream, state, closing, escapes); }; + } + function phpString_(stream, state, closing, escapes) { + // "Complex" syntax + if (escapes !== false && stream.match("${", false) || stream.match("{$", false)) { + state.tokenize = null; + return "string"; + } + + // Simple syntax + if (escapes !== false && stream.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*/)) { + // After the variable name there may appear array or object operator. + if (stream.match("[", false)) { + // Match array operator + state.tokenize = matchSequence([ + [["[", null]], + [[/\d[\w\.]*/, "number"], + [/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"], + [/[\w\$]+/, "variable"]], + [["]", null]] + ], closing, escapes); + } + if (stream.match(/^->\w/, false)) { + // Match object operator + state.tokenize = matchSequence([ + [["->", null]], + [[/[\w]+/, "variable"]] + ], closing, escapes); + } + return "variable-2"; + } + + var escaped = false; + // Normal string + while (!stream.eol() && + (escaped || escapes === false || + (!stream.match("{$", false) && + !stream.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false)))) { + if (!escaped && stream.match(closing)) { + state.tokenize = null; + state.tokStack.pop(); state.tokStack.pop(); + break; + } + escaped = stream.next() == "\\" && !escaped; + } + return "string"; + } + + var phpKeywords = "abstract and array as break case catch class clone const continue declare default " + + "do else elseif enddeclare endfor endforeach endif endswitch endwhile enum extends final " + + "for foreach function global goto if implements interface instanceof namespace " + + "new or private protected public static switch throw trait try use var while xor " + + "die echo empty exit eval include include_once isset list require require_once return " + + "print unset __halt_compiler self static parent yield insteadof finally readonly match"; + var phpAtoms = "true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__"; + var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage memory_get_peak_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents file_put_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists array_intersect_key array_combine array_column pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count"; + CodeMirror.registerHelper("hintWords", "php", [phpKeywords, phpAtoms, phpBuiltin].join(" ").split(" ")); + CodeMirror.registerHelper("wordChars", "php", /[\w$]/); + + var phpConfig = { + name: "clike", + helperType: "php", + keywords: keywords(phpKeywords), + blockKeywords: keywords("catch do else elseif for foreach if switch try while finally"), + defKeywords: keywords("class enum function interface namespace trait"), + atoms: keywords(phpAtoms), + builtin: keywords(phpBuiltin), + multiLineStrings: true, + hooks: { + "$": function(stream) { + stream.eatWhile(/[\w\$_]/); + return "variable-2"; + }, + "<": function(stream, state) { + var before; + if (before = stream.match(/^<<\s*/)) { + var quoted = stream.eat(/['"]/); + stream.eatWhile(/[\w\.]/); + var delim = stream.current().slice(before[0].length + (quoted ? 2 : 1)); + if (quoted) stream.eat(quoted); + if (delim) { + (state.tokStack || (state.tokStack = [])).push(delim, 0); + state.tokenize = phpString(delim, quoted != "'"); + return "string"; + } + } + return false; + }, + "#": function(stream) { + while (!stream.eol() && !stream.match("?>", false)) stream.next(); + return "comment"; + }, + "/": function(stream) { + if (stream.eat("/")) { + while (!stream.eol() && !stream.match("?>", false)) stream.next(); + return "comment"; + } + return false; + }, + '"': function(_stream, state) { + (state.tokStack || (state.tokStack = [])).push('"', 0); + state.tokenize = phpString('"'); + return "string"; + }, + "{": function(_stream, state) { + if (state.tokStack && state.tokStack.length) + state.tokStack[state.tokStack.length - 1]++; + return false; + }, + "}": function(_stream, state) { + if (state.tokStack && state.tokStack.length > 0 && + !--state.tokStack[state.tokStack.length - 1]) { + state.tokenize = phpString(state.tokStack[state.tokStack.length - 2]); + } + return false; + } + } + }; + + CodeMirror.defineMode("php", function(config, parserConfig) { + var htmlMode = CodeMirror.getMode(config, (parserConfig && parserConfig.htmlMode) || "text/html"); + var phpMode = CodeMirror.getMode(config, phpConfig); + + function dispatch(stream, state) { + var isPHP = state.curMode == phpMode; + if (stream.sol() && state.pending && state.pending != '"' && state.pending != "'") state.pending = null; + if (!isPHP) { + if (stream.match(/^<\?\w*/)) { + state.curMode = phpMode; + if (!state.php) state.php = CodeMirror.startState(phpMode, htmlMode.indent(state.html, "", "")) + state.curState = state.php; + return "meta"; + } + if (state.pending == '"' || state.pending == "'") { + while (!stream.eol() && stream.next() != state.pending) {} + var style = "string"; + } else if (state.pending && stream.pos < state.pending.end) { + stream.pos = state.pending.end; + var style = state.pending.style; + } else { + var style = htmlMode.token(stream, state.curState); + } + if (state.pending) state.pending = null; + var cur = stream.current(), openPHP = cur.search(/<\?/), m; + if (openPHP != -1) { + if (style == "string" && (m = cur.match(/[\'\"]$/)) && !/\?>/.test(cur)) state.pending = m[0]; + else state.pending = {end: stream.pos, style: style}; + stream.backUp(cur.length - openPHP); + } + return style; + } else if (isPHP && state.php.tokenize == null && stream.match("?>")) { + state.curMode = htmlMode; + state.curState = state.html; + if (!state.php.context.prev) state.php = null; + return "meta"; + } else { + return phpMode.token(stream, state.curState); + } + } + + return { + startState: function() { + var html = CodeMirror.startState(htmlMode) + var php = parserConfig.startOpen ? CodeMirror.startState(phpMode) : null + return {html: html, + php: php, + curMode: parserConfig.startOpen ? phpMode : htmlMode, + curState: parserConfig.startOpen ? php : html, + pending: null}; + }, + + copyState: function(state) { + var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html), + php = state.php, phpNew = php && CodeMirror.copyState(phpMode, php), cur; + if (state.curMode == htmlMode) cur = htmlNew; + else cur = phpNew; + return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, + pending: state.pending}; + }, + + token: dispatch, + + indent: function(state, textAfter, line) { + if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) || + (state.curMode == phpMode && /^\?>/.test(textAfter))) + return htmlMode.indent(state.html, textAfter, line); + return state.curMode.indent(state.curState, textAfter, line); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//", + + innerMode: function(state) { return {state: state.curState, mode: state.curMode}; } + }; + }, "htmlmixed", "clike"); + + CodeMirror.defineMIME("application/x-httpd-php", "php"); + CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true}); + CodeMirror.defineMIME("text/x-php", phpConfig); +}); diff --git a/public/codemirror/theme/eclipse.css b/public/codemirror/theme/eclipse.css new file mode 100644 index 0000000..800d603 --- /dev/null +++ b/public/codemirror/theme/eclipse.css @@ -0,0 +1,23 @@ +.cm-s-eclipse span.cm-meta { color: #FF1717; } +.cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } +.cm-s-eclipse span.cm-atom { color: #219; } +.cm-s-eclipse span.cm-number { color: #164; } +.cm-s-eclipse span.cm-def { color: #00f; } +.cm-s-eclipse span.cm-variable { color: black; } +.cm-s-eclipse span.cm-variable-2 { color: #0000C0; } +.cm-s-eclipse span.cm-variable-3, .cm-s-eclipse span.cm-type { color: #0000C0; } +.cm-s-eclipse span.cm-property { color: black; } +.cm-s-eclipse span.cm-operator { color: black; } +.cm-s-eclipse span.cm-comment { color: #3F7F5F; } +.cm-s-eclipse span.cm-string { color: #2A00FF; } +.cm-s-eclipse span.cm-string-2 { color: #f50; } +.cm-s-eclipse span.cm-qualifier { color: #555; } +.cm-s-eclipse span.cm-builtin { color: #30a; } +.cm-s-eclipse span.cm-bracket { color: #cc7; } +.cm-s-eclipse span.cm-tag { color: #170; } +.cm-s-eclipse span.cm-attribute { color: #00c; } +.cm-s-eclipse span.cm-link { color: #219; } +.cm-s-eclipse span.cm-error { color: #f00; } + +.cm-s-eclipse .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-eclipse .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } diff --git a/public/htaccess-apache b/public/htaccess-apache new file mode 100644 index 0000000..475f6fd --- /dev/null +++ b/public/htaccess-apache @@ -0,0 +1,13 @@ +#apache服务器伪静态配置文件,使用时将文件名改为.htaccess并复制到蓝天采集器根目录 + + Options +FollowSymlinks -Multiviews + RewriteEngine on + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^app/(\w+)/(.*)$ app/$1/index.php [QSA,PT,L,E=PATH_INFO:$2] + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php [QSA,PT,L,E=PATH_INFO:$1] + \ No newline at end of file diff --git a/web_config b/public/htaccess-iis similarity index 87% rename from web_config rename to public/htaccess-iis index 422ab9d..f5246d8 100644 --- a/web_config +++ b/public/htaccess-iis @@ -1,5 +1,5 @@ - + @@ -13,7 +13,7 @@ - + diff --git a/public/htaccess-nginx b/public/htaccess-nginx new file mode 100644 index 0000000..6303216 --- /dev/null +++ b/public/htaccess-nginx @@ -0,0 +1,5 @@ +#nginx服务器伪静态配置文件,注意:如果蓝天采集器安装在子目录中,请将“子目录”替换为相应目录的名称,否则删除文字“/子目录” +if (!-e $request_filename) { + rewrite ^/子目录/app/(\w+)/(.*)$ /子目录/app/$1/index.php?s=/$2 last; + rewrite ^/子目录/(.*)$ /子目录/index.php?s=/$1 last; +} \ No newline at end of file diff --git a/public/static/css/admin.css b/public/static/css/admin.css index 6b70c24..5ab0e46 100644 --- a/public/static/css/admin.css +++ b/public/static/css/admin.css @@ -10,7 +10,10 @@ .panel .form-group:last-child{margin-bottom:0;} .panel .form-group .help-block:last-child{margin-bottom:0;} +.label-custom-opened{font-weight:normal;background-color:#dedede;color:#555;} + .brl_0{border-left:0;} +.brr_0{border-right:0;} .h-title{ color: #666; font-weight: bold; @@ -54,14 +57,27 @@ div.in-line-mg{display:inline;margin-right:5px;} .wrapper-not-enable .tab-content{background:#f1f1f1;opacity:0.7;} .wrapper-not-enable .panel-collapse{background:#f1f1f1;opacity:0.7;} +.box-not-enable{background:#f1f1f1;opacity:0.7;} + #confirm_right .cr-msg p:last-child{margin-bottom:0;} +.table-responsive .table:last-child{margin-bottom:0;} +.table-responsive .table input.form-control{min-width:150px;} +.table-responsive .table textarea.form-control{min-width:150px;} +.table-responsive .table select.form-control{min-width:70px;} + +.icon-info-tips{font-size:12px;cursor:pointer;color:#bbb;} +.icon-info-tips:hover{color:#3c8dbc;} + +.echo-msg-clear{width:100%;overflow:hidden;clear:both;} +.echo-msg-lt{float:left;} +.echo-msg-lurl{float:left;margin-right:3px;height:16px;max-width:70%;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;word-break:break-all;} + /*皮肤*/ .skin-blue-light .treeview-skins,.skin-black-light .treeview-skins,.skin-red-light .treeview-skins,.skin-yellow-light .treeview-skins,.skin-purple-light .treeview-skins,.skin-green-light .treeview-skins{ color:#000; } - .skin-black .btn-primary,.skin-black-light .btn-primary{background-color:#5A6E82;border-color:#4A6E82;} .skin-black .btn-primary:hover,.skin-black-light .btn-primary:hover{background-color:#4A6E82;border-color:#1A6E82} .skin-black .pagination .active a,.skin-black-light .pagination .active a{background-color:#5A6E82;border-color:#4A6E82;} @@ -97,11 +113,14 @@ div.in-line-mg{display:inline;margin-right:5px;} /*数据表*/ .datatable .sub{padding-left:50px; background:url('../images/bg_column.gif') no-repeat 15px 10px;} .datatable td .dropdown{display:inline;} -.datatable .chk{text-align:center;} +.datatable th.chk,.datatable td.chk{min-width:50px;width:50px;text-align:center;} +.datatable .chk-all{line-height:14px;margin:0;} +.datatable .chk-all span{display:block;font-size:12px;} .datatable .name{max-width:450px;overflow:hidden;text-overflow:ellipsis;} .datatable .url{max-width:260px;overflow:hidden;text-overflow:ellipsis;} .datatable .desc{max-width:400px;white-space:normal;overflow:hidden;word-wrap:break-word;word-break:break-all;} -.datatable .sort input{height:auto;padding:0 5px;max-width:70px;} +.datatable .sort{width:80px;min-width:80px;} +.datatable .sort input{height:auto;padding:0 5px;min-width:100%!important;} .datatable .bg0{background:#fcfcfc} .datatable .chk-inline{vertical-align:baseline;margin-right:5px;} .datatable .sep{color:#999;text-indent:-9999px;display:inline-block;width:2px;line-height:12px;margin-left:1px;border-left:1px solid #aaa;} @@ -201,9 +220,10 @@ table.table thead .sorting_desc:after { border-bottom:0; } /*请求头信息*/ -.c-p-request-headers .delete-request-header{margin-top:8px;} -.c-p-request-headers-img .delete-request-header-img{margin-top:8px;} - +.c-p-request-headers table,.c-p-request-headers-img table{margin-bottom:0} +.c-p-request-headers .delete-request-header,.c-p-request-headers-img .delete-request-header-img{margin-top:8px;} +.c-p-request-headers td:first-child,.c-p-request-headers-img td:first-child{width:200px;} +.c-p-request-headers td:last-child,.c-p-request-headers-img td:last-child{width:50px;text-align:center;} /*内容标签*/ .c-p-url-content-signs{ width:auto; @@ -211,8 +231,16 @@ table.table thead .sorting_desc:after { } /*页面源码表单和请求头*/ +.c-p-url-web-form table,.c-p-url-web-header table{margin-bottom:0;} .c-p-url-web-form .delete-url-web-form,.c-p-url-web-header .delete-url-web-header{margin-top:8px;} .c-p-url-web-form td:first-child,.c-p-url-web-header td:first-child{width:200px;} +.c-p-url-web-form td:last-child,.c-p-url-web-header td:last-child{width:50px;text-align:center;} +/*页面渲染动作*/ +.c-p-url-renderer-list table{margin-bottom:0;} +.c-p-url-renderer-list .td-renderer-element,.c-p-url-renderer-list .td-renderer-content{display:none;} +.c-p-url-renderer-list .td-renderer-op{width:65px;text-align:center;} +.c-p-url-renderer-list .td-renderer-op .input-group-addon:last-child{border-left:1px solid #d2d6de;} + /*显示标签列表,input-group-btn必须position:static,否则dropdown-menu定位太窄*/ .c-p-url-page-signs>.input-group-btn{position:static;} .c-p-url-page-signs .dropdown-menu{padding:0;box-shadow:none;border:0;} @@ -220,8 +248,9 @@ table.table thead .sorting_desc:after { .c-p-url-page-signs .c-p-url-ps-table .table{margin-bottom:0;} .c-p-url-page-signs .c-p-url-ps-table .table td>a,.c-p-url-page-signs .dropdown-menu .table td>span{margin-right:5px;} .c-p-url-page-signs .c-p-url-ps-table .table td>*:last-child{margin-right:0;} -.c-p-url-page-signs .c-p-url-ps-table .table td{white-space:normal!important;width:auto;} +.c-p-url-page-signs .c-p-url-ps-table .table td{white-space:normal!important;width:auto!important;} .c-p-url-page-signs .c-p-url-ps-table .table td:first-child{white-space:nowrap!important;} +.c-p-url-page-signs .c-p-url-ps-table .table td:last-child{text-align:left!important;} /*分页字段*/ .c-p-url-pagination-fields{ @@ -287,32 +316,49 @@ table.table thead .sorting_desc:after { .nav-save-rule{float:right!important;} .nav-save-rule .dropdown-menu li a{padding-top:5px;padding-bottom:5px;} /*数据处理*/ -.form-control-slt{width:30px;text-align:center;} -.form-control-slt select{width:20px!important;height:30px!important;float:none!important;border:0!important;padding:0!important;margin:0!important;text-indent:-20px!important;} +.form-control-slt{width:30px!important;text-align:center;} +.form-control-slt select.form-control{min-width:20px!important;width:20px!important;height:30px!important;float:none!important;border:0!important;padding:0!important;margin:0!important;text-indent:-20px!important;} .c-p-process-accordion .panel-heading .glyphicon{font-size:12px;} .p-m-func-field{display:inline;width:80px;border:0;cursor:pointer;margin:0;padding:0;height:auto;color:#555;} + .p-m-api-val-field,.p-m-api-header-field{display:none;margin-left:-1px;padding:0;} + +.p-m-api-table table,.p-m-api-header-table table{margin-bottom:0;} +.p-m-api-table td:first-child,.p-m-api-header-table td:first-child{width:200px;} +.p-m-api-table td:last-child,.p-m-api-header-table td:last-child{width:50px;text-align:center;} + .p-m-html-tags{} .p-m-html-tags a{font-size:12px;font-weight:normal;cursor:pointer;margin-right:5px;} .p-m-html-tags a span{margin-right:2px;color:#999;} .p-m-if-val-func{position:relative;} .p-m-if-val-func a.input-group-addon{border-left:0;} .p-m-if-val-func .input-group-btn>select{border-left:0;width:auto;padding:0;padding-left:5px;text-align:center;} -.p-m-if-val-func-info{width:30px;display:block;padding:0;color:#bbb;font-size:12px;} -.p-m-if-op{width:35px;padding:0!important;padding-top:15px!important;font-size:11px;} - +.p-m-if-op{width:70px;padding:15px 0 0 8px!important;font-size:11px;} /*发布设置*/ .rele-db-error{margin-top:10px;color:red;font-weight:bold;} -.table-db-table-bind{padding:0;margin-bottom:0;border:0;} -.table-db-table-bind .table{margin-bottom:0;} -.table-db-table-bind .table thead th{padding-top:13px;padding-bottom:13px;} -.table-db-table-bind .form-control{min-width:150px;} +.db-table-bind-params{padding:0;margin-bottom:0;border:0;} +.db-table-bind-params .table{margin-bottom:0;} +.db-table-bind-params .table tr>th:first-child,.db-table-bind-params .table tr>td:first-child{padding-left:15px;} +.db-table-bind-params .table thead th{padding-top:13px;padding-bottom:13px;} +.db-table-bind-op{width:auto;height:auto;display:inline;padding:0;} +.db-table-bind-where-tpl,.db-table-bind-query-tpl{display:none;} + +.db-table-bind-where,.db-table-bind-query{display:none;} +.db-table-bind-data-seq{display:none;} +.db-table-bind-where,.db-table-bind-query,.db-table-bind-data{border:0;} + +.db-table-bind-signs>.input-group-btn{position:static;} + .dm-db-charset li {padding:5px 15px;} .dm-db-charset li.divider{padding:0;} .dm-db-charset li span{padding:5px; cursor:pointer;color:#555;} .dm-db-charset li span:hover{background:#eee;} -/*sui步骤条*/ + +.toapi-param-table table,.toapi-header-table table{margin-bottom:0;} +.toapi-param-table td:first-child,.toapi-header-table td:first-child{width:200px;} +.toapi-param-table td:last-child,.toapi-header-table td:last-child{width:50px;text-align:center;} +/*步骤条*/ .steps-bar{width:100%;overflow:hidden;color:#999;} .steps-bar a{color:#999;} .steps-bar .step{ @@ -415,12 +461,11 @@ table.table thead .sorting_desc:after { .proxy-ip-box .h-title .glyphicon{font-size:80%;} .proxy-ip-list .delete-proxy-ip{margin-top:8px;} -.proxy-ip-list .p-ip .form-control{min-width:150px;} .proxy-ip-list .p-ip-info{vertical-align:middle;color:#999;} .proxy-ip-list .p-top{padding-top:11px;} .proxy-ip-list .p-top1{padding-top:13px;} -.proxy-api-list .p-api-title{width:90%;float:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;} +.proxy-api-list .p-api-title{max-width:80%;float:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;} /*工具*/ .tool-json-tree{width:100%;overflow:hidden;} @@ -432,6 +477,14 @@ table.table thead .sorting_desc:after { .tool-json-tree .val{} .tool-json-tree .text{width:100%;} .tool-json-tree .text textarea{width:100%;} +/*开发*/ +.deve-editor-left{padding-right:0px;} +.deve-editor-right{padding-left:1px;} + +.deve-editor-apps{padding-left:5px;list-style:none;} +.deve-editor-apps li{padding:2px 0;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;} +.deve-editor-apps li a{color:#333;} +.deve-editor-apps li.cur a{font-weight:bold;} /**/ .table-test-loop thead{background:#fafafa;} .table-test-loop thead th,.table-test-loop thead td{ @@ -454,8 +507,6 @@ table.table thead .sorting_desc:after { .table-test-loop .view-ipt{} .table-test-loop .view-btn{position:absolute;font-size:12px;top:12px;left:5px;} -.icon-delimiter-tips{font-size:12px;cursor:pointer;color:#bbb;} - footer.footer-end{display:none;} .skin-black .nav-tabs-color li.active,.skin-black-light .nav-tabs-color li.active{border-top-color:#5A6E82!important;} @@ -471,10 +522,11 @@ footer.footer-end{display:none;} /*采集窗口*/ .win-cem-process-body{position:relative;} .win-cem-process-body #win_cem_process_box{display:block!important;} -.win-cem-process-body .win-cem-ifr-box{margin-left:80px;border-left:solid 1px #efefef;} +.win-cem-process-body .win-cem-ifr-box{margin-left:80px;} #win_cem_process_box{display:none;} #win_cem_process_nav{ + border-right:solid 1px #efefef; font-size:13px; text-align:center; position:absolute; @@ -505,6 +557,26 @@ footer.footer-end{display:none;} .win-cem-ifr-box{width:auto;height:100%;} .win-cem-ifr-box iframe{width:100%;height:100%;margin:0;border:0;} + +@media (min-width: 1200px) { + /*table-responsive设置overflow:inherit;可正常显示dropdown-menu*/ + .c-p-url-renderer-list{overflow:inherit;} + .db-table-bind-where,.db-table-bind-query,.db-table-bind-data{overflow:inherit;} +} + +@media (max-width: 1199px) { + /*dropdown-menu用fixed避免被table-responsive的overflow:hidden;影响*/ + .c-p-url-renderer-list .c-p-url-page-signs .dropdown-menu{padding:0;border:4px solid #ddd;border-radius:4px;background:transparent;position:fixed;left:10px;right:10px;bottom:10px;top:auto;max-height:80%;overflow-y:scroll;} + .c-p-url-renderer-list .c-p-url-page-signs .dropdown-menu .c-p-url-ps-table{background:#fff;} + + .db-table-bind-signs .dropdown-menu{padding:0;border:4px solid #ddd;border-radius:4px;background:transparent;position:fixed;left:10px;right:10px;bottom:10px;top:auto;max-height:80%;overflow-y:scroll;} + .db-table-bind-signs .dropdown-menu table{margin:0;} +} + +@media (min-width: 768px) { + .c-p-url-web-form,.c-p-url-web-header{overflow:inherit;} /*table-responsive设置overflow:inherit;可正常显示dropdown-menu*/ +} + @media (max-width: 767px){ .main-header .logo{ width:140px; @@ -521,7 +593,17 @@ footer.footer-end{display:none;} .content-wrapper .content{padding-top:8px;} .c-p-url-page-signs .dropdown-menu .table td>a,.c-p-url-page-signs .dropdown-menu .table td>span{display:block;margin-right:0;} + + .deve-editor-left,.deve-editor-right{padding-left:15px;padding-right:15px;margin-bottom:10px;} + + /*dropdown-menu用fixed避免被table-responsive的overflow:hidden;影响*/ + .c-p-url-web-form .c-p-url-page-signs .dropdown-menu,.c-p-url-web-header .c-p-url-page-signs .dropdown-menu{padding:0;border:4px solid #ddd;border-radius:4px;background:transparent;position:fixed;left:10px;right:10px;bottom:10px;top:auto;max-height:80%;overflow-y:scroll;} + .c-p-url-web-form .c-p-url-page-signs .dropdown-menu .c-p-url-ps-table,.c-p-url-web-header .c-p-url-page-signs .dropdown-menu .c-p-url-ps-table{background:#fff;} + /*元素最小宽度,防止太窄无法输入*/ + .table-responsive .table input.form-control{min-width:100px;} + .table-responsive .table textarea.form-control{min-width:100px;} } + @media (max-width: 550px){ /*采集器tab*/ .coll-tab>li{margin-bottom:5px;width:33%;} @@ -536,6 +618,9 @@ footer.footer-end{display:none;} .c-p-source-tab>li.active>a{border-radius:3px;border-bottom-color:#ddd!important;background-color:#efefef!important;} .mobile-input-group>.input-group-addon{display:table-caption;width:100%;background:#fafafa;margin-bottom:5px;border-right:1px solid #ccc;} - - .c-p-url-web-form td:first-child,.c-p-url-web-header td:first-child{width:auto;} + /*自适应第一个td宽度*/ + .c-p-request-headers td:first-child,.c-p-request-headers-img td:first-child{width:auto;} + .c-p-url-web-form td:first-child,.c-p-url-web-header td:first-child{width:auto;} + .p-m-api-table td:first-child,.p-m-api-header-table td:first-child{width:auto;} + .toapi-param-table td:first-child,.toapi-header-table td:first-child{width:auto;} } \ No newline at end of file diff --git a/public/static/css/cpattern_easy.css b/public/static/css/cpattern_easy.css index 7ca0f64..4963182 100644 --- a/public/static/css/cpattern_easy.css +++ b/public/static/css/cpattern_easy.css @@ -9,13 +9,38 @@ body>.container-fluid{padding:0px;margin:0px;} width:35%; padding:0; margin:0; - resize:horizontal; } + #box_guide{ padding:0; margin:0; margin-left:35%; + position:relative; } + +#box_resize{ + border:0; + z-index:9999; + position:absolute; + top:48%; + left:-15.5px; + text-align:center; + font-size:14px; + opacity:0.7; + color:#555; + padding:0; + margin:0; + cursor:w-resize; + -moz-user-select:none; + -webkit-user-select:none; + -ms-user-select:none; + -khtml-user-select:none; + user-select:none; +} +#btn_resize .glyphicon{padding:0;line-height:inherit;} +.btn-resize-mousedown #btn_resize{position:absolute;padding:200px;left:-200px;top:-200px;} +.btn-resize-mousedown #btn_resize .glyphicon{color:#000;} + #ifr_browser_box{position:relative;} #ifr_browser{ width:100%; @@ -25,7 +50,7 @@ body>.container-fluid{padding:0px;margin:0px;} padding:0; margin:0; } -#ifr_loading{position:absolute;z-index:999999;top:15px;left:10px;background:url('../images/loading.gif') no-repeat 5px 0px;width:37px;height:32px;} +#ifr_loading{position:absolute;z-index:999;top:15px;left:10px;background:url('../images/loading.gif') no-repeat 5px 0px;width:37px;height:32px;} @media(max-width:767px) { #wrapper{overflow:auto;} #ifr_collector { @@ -40,5 +65,8 @@ body>.container-fluid{padding:0px;margin:0px;} margin-left: 0; padding: 5px; } + #btn_resize{display:none;} + #ifr_browser{height:auto;} + } diff --git a/public/static/js/admin.js b/public/static/js/admin.js index dd378df..936fa94 100644 --- a/public/static/js/admin.js +++ b/public/static/js/admin.js @@ -22,7 +22,8 @@ window.open(url,'_blank')} function eleExchange(box,move,ele){if(!window.ele_exchange_is_touch){window.ele_exchange_is_touch=1;if('ontouchstart' in window||navigator.maxTouchPoints){window.ele_exchange_is_touch=2}} if(window.ele_exchange_is_touch==2){$(box).on('click',move,function(){var obj=$(this).parents(ele).eq(0);var next=obj.next(ele);if(next.length>0){next.after(obj)}})}else{$(box).sortable({items:ele,handle:move,axis:'y'})}} function showPanelCollapse(id){$(id).parent().find('a[data-toggle][href="'+id+'"]').attr('aria-expanded',!0).removeClass('collapsed');$(id).addClass('in').attr('aria-expanded',!0).attr('style','')} -function inputSelectCustom(sltObj,iptName,onOptions){if(sltObj&&iptName){$(sltObj).bind('change',function(){var ipt=$(this).parents('.input-select-custom').eq(0).find('[name="'+iptName+'"]');if($(this).val()=='custom'){ipt.show()}else{ipt.hide()}})}else if(onOptions&&typeof(onOptions)=='object'){$(onOptions.box).on('change',onOptions.slt,function(){var ipt=$(this).parents('.input-select-custom').eq(0).find(onOptions.ipt);if($(this).val()=='custom'){ipt.show()}else{ipt.hide()}})}} +function inputSelectCustom(sltObj,iptName,onOptions,customName,changeFunc){customName=customName?customName:'custom';var onChangeFunc=function(curObj,iptEle){var ipt=$(curObj).parents('.input-select-custom').eq(0).find(iptEle);if($(curObj).val()==customName){ipt.show()}else{ipt.hide()} +if(changeFunc&&typeof(changeFunc)=='function'){changeFunc()}};if(sltObj&&iptName){$(sltObj).bind('change',function(){onChangeFunc(this,'[name="'+iptName+'"]')})}else if(onOptions&&typeof(onOptions)=='object'){$(onOptions.box).on('change',onOptions.slt,function(){onChangeFunc(this,onOptions.ipt)})}} function visualizeData(data){var cacheData=data;data=isNull(data)?'':data;var options={lg:1,hidden_func:function(){window.win_visualize_data=null}};if(dataIsJson(data)){var jsonId='json_'+generateUUID();modal('JSON解析','
',options);var jsonTreeFunc=function(){window.tool_json_tree.treeId='#'+jsonId;window.tool_json_tree.load(data)};if(window.tool_json_tree){jsonTreeFunc()}else{$.getScript(window.site_config.pub+'/static/js/admin/tool_json_tree.js',jsonTreeFunc)}}else{options.loaded_func=function(){data=data.replace(/]*>[\s\S]*?<\/script>/ig,'');data=data.replace(/]*charset[^<>]*>/i,'');var ifrId='#myModalIframe';$(ifrId).bind('load',function(){if($(ifrId).contents().find('body').html().length<=0){$(ifrId).contents().find('body').html(data)}});$(ifrId).contents().find('body').html(data)};var title='HTML预览';if(data&&data.indexOf('
')===0){title='HTML代码'}
 windowIframe(title,'',options)}(function(data){if(data){$('#myModal .modal-footer .close').addClass('btn btn-default').removeClass('close');$('#myModal .modal-footer').prepend('');$('#myModal .modal-footer .btn-back').bind('click',function(){visualizeData(data)})}})(window.win_visualize_data);window.win_visualize_data=cacheData}
 function cpEasyBrowser(url,pageSource,inputUrls){pageSource=pageSource?pageSource:'';inputUrls=inputUrls?inputUrls:{};var data={type:'browser_url',page_source:pageSource,test_url:url,input_urls:inputUrls};data=JSON.stringify(data);window.top.postMessage(data,'*')}
@@ -38,8 +39,9 @@ paramObj.attr('placeholder',placeholder).attr('rows',rows)})}}
 var setFuncVal=function(){funcObj.val(funcVal).trigger('change')};if(funcObj.attr('data-is-loaded')){setFuncVal()}else{if(params.cache&&window[winCacheName]){funcObj.attr('data-is-loaded',1).append(window[winCacheName]);setFuncVal()}else{ajaxOpen({type:'GET',dataType:'json',url:ulink('collector/plugin_func'),async:params.cache?false:!0,data:{module:params.module},success:function(data){if(funcObj.attr('data-is-loaded')){setFuncVal()}else{funcObj.attr('data-is-loaded',1);if(data.code==1){var html='';var apps=data.data;if(apps&&typeof(apps)=='object'){for(var app in apps){var appData=apps[app];appData=appData?appData:{};var methods=appData.methods;if(methods){html+='';for(var m in methods){var mMethod=methods[m];mMethod=mMethod?mMethod:{};html+=''}
 html+=''}}}
 funcObj.append(html);if(params.cache){window[winCacheName]=html}}}},error:function(xhr,status,error){funcObj.removeAttr('data-is-loaded');toastr.error('函数插件载入失败:'+status+' '+error)},complete:function(xhr,status){setFuncVal()}})}}}
-function pluginFuncTips(module){var tips='';if(module=='process'){tips='

如需扩展系统函数,请在根目录/data/config.php中添加配置:

'+"

'EXTEND_PROCESS_FUNC'=>array('PHP函数名'=>'描述')

"+'

如需扩展插件函数,可创建函数插件

'}else if(module=='processIf'){tips='

选择函数,取反可获取函数结果的相反值

'+'

默认将当前字段作为参数传入,如需传入多个参数,一行一个值,可输入任何内容或调用字段

'+'

请按函数传参,否则运行出错!

'+'

如需扩展系统函数,请在根目录/data/config.php中添加配置:

'+"

'EXTEND_PROCESS_IF'=>array('PHP函数名'=>'描述')

"+'

如需扩展插件函数,可创建函数插件

'}else if(module=='downloadImg'||module=='contentSign'){window.open(ulink('develop/func?module='+module));return!1} +function tipsPluginFunc(module){var tips='';if(module=='process'){tips='

如需扩展系统函数,请在根目录/data/config.php中添加配置:

'+"

'EXTEND_PROCESS_FUNC'=>array('PHP函数名'=>'描述')

"+'

如需扩展插件函数,可创建函数插件

'}else if(module=='processIf'){tips='

选择函数,取反可获取函数结果的相反值

'+'

默认将当前字段作为参数传入,如需传入多个参数,一行一个值,可输入任何内容或调用字段

'+'

请按函数传参,否则运行出错!

'+'

如需扩展系统函数,请在根目录/data/config.php中添加配置:

'+"

'EXTEND_PROCESS_IF'=>array('PHP函数名'=>'描述')

"+'

如需扩展插件函数,可创建函数插件

'}else if(module=='downloadImg'||module=='contentSign'){window.open(ulink('develop/func?module='+module));return!1} confirmRight({msg:tips,yes:'确定',width:500,textAlign:'left'})} +function tipsCurlPost(){var tips='

表单数据:模拟form表单输入的数据

表单上传:模拟form表单输入并上传的数据

'+'

JSON数组:以json格式发送数据,可在“发送数据”的值中直接输入json字符串,根节点名称使用###表示,子节点名称使用.分隔,例如:a.b.c

';confirmRight({msg:tips,yes:'确定',width:500,textAlign:'left'})} function collectorWindow(title,uri,uriVals,options){options=options?options:{};options.backdrop_static=1;title=isNull(title)?'':title;title+='
';window.win_collector_window_params={title:title,uri:uri,uriVals:uriVals,options:options};windowModal(title,ulink('admin/collector/echo_msg?op=run'),{lg:options.lg})} var collectorEchoMsg={config:{},processes:{},close_non_stop:!1,end_set_timeout:null,run:function(config){collectorEchoMsg.config=isObject(config)?config:{};collectorEchoMsg.processes={};collectorEchoMsg.close_non_stop=!1;var winParams=window.win_collector_window_params;winParams=isObject(winParams)?winParams:{};var uri=winParams.uri?winParams.uri:'';var uriVals=isObject(winParams.uriVals)?winParams.uriVals:{};var options=isObject(winParams.options)?winParams.options:{};var winProcessBox=$('#myModal #win_cem_process_box').clone();if(uri){var title=winParams.title+'日志读取间隔';var closeFuncs=new Array();if(!isNull(options.close_func)){if(isObject(options.close_func)){closeFuncs=options.close_func}else{closeFuncs.push(options.close_func)}} closeFuncs.push(function(){if(!collectorEchoMsg.close_non_stop){collectorEchoMsg.stop_all()}});options.close_func=closeFuncs;windowIframe(title,'',options);$('#myModal #myModalIframe').hide();$('#myModal #win_cem_interval_btn').bind('click',function(){collectorEchoMsg.set_interval($('#win_cem_interval').val())});var runUrl=ulink(uri,uriVals);ajaxOpen({type:'get',url:runUrl,dataType:'json',async:!0,success:function(data){if(data.code==1){var processes=data.data?data.data:{};var collectorKey=processes.collector_key?processes.collector_key:'';var processKeys=isObject(processes.process_keys)?processes.process_keys:[];var processNum=processKeys.length;if(!collectorKey||processNum<=0){return} @@ -60,6 +62,8 @@ if(collectorEchoMsg.processes[collectorProcess].read_timeout){window.clearTimeou winBackstageTask.count();winBackstageTask.status()}})},init_tasks:function(taskType){taskType=toInt(taskType);$('#win_bk_tasks_box_'+(taskType==0?1:0)).html('');$('[id^="win_bk_tasks_box_"] a[data-parent^="#win_bk_tasks_box_"]').bind('click',function(){var curTaskId=$($(this).attr('href')).attr('data-task-id');var curTaskType=$(this).parents('[id^="win_bk_tasks_nav_"]').eq(0).attr('data-task-type');winBackstageTask.collected(curTaskId,curTaskType)});$('[id^="win_bk_tasks_box_"] .fa-remove').bind('click',function(){var obj=$(this);var curTaskId=$(this).attr('data-task-id');ajaxOpen({type:'get',dataType:'json',async:!0,url:ulink('admin/task/bkdelete?id='+curTaskId),success:function(data){obj.parents('.panel').remove();var spanObj=$('#win_backstage_task').find('a[href="#win_bk_tasks_nav_'+taskType+'"]').find('span');var spanCount=spanObj.text();spanCount=spanCount?parseInt(spanCount):0;spanCount=spanCount>0?(spanCount-1):0;spanObj.text(spanCount)}})});$('#win_bk_tasks_box_'+taskType+' .pagination').addClass('pagination-sm');$('#win_bk_tasks_box_'+taskType+' .pagination a').bind('click',function(){var curTaskType=$(this).parents('[id^="win_bk_tasks_nav_"]').eq(0).attr('data-task-type');winBackstageTask.tasks(curTaskType,$(this).attr('href'));return!1})},collected:function(taskId,taskType,url){if(!url){url=ulink('admin/backstage/backstageTask?op=collected&tid='+taskId)} ajaxOpen({type:'get',dataType:'html',async:!0,url:url,success:function(data){$('#win_bk_collected_'+taskId).html(data)},complete:function(){if(!isNull(taskType)&&0==toInt(taskType)){winBackstageTask.collected_set_timeout=window.setTimeout(function(){var isEnd=$('#win_bk_tasks_box_0').find('a[href="#win_bk_collected_'+taskId+'"]').attr('data-is-end');var isVisible=$('#win_bk_tasks_box_0 #win_bk_collected_'+taskId).is(':visible');if(!isEnd&&isVisible){winBackstageTask.collected(taskId,taskType,url)}else{window.clearTimeout(winBackstageTask.collected_set_timeout)}},3000)}}})},init_collected:function(taskStatus,taskId){$('[id^="win_bk_collected_"] .pagination').addClass('pagination-sm');$('[id^="win_bk_collected_"] .pagination a').bind('click',function(){var curTaskId=$(this).parents('[id^="win_bk_collected_"]').eq(0).attr('data-task-id');var curTaskType=$(this).parents('[id^="win_bk_tasks_nav_"]').eq(0).attr('data-task-type');winBackstageTask.collected(curTaskId,curTaskType,$(this).attr('href'));return!1});if(taskStatus&&taskId){winBackstageTask.set_task_end(taskId,taskStatus);winBackstageTask.count();winBackstageTask.status()}},set_task_end:function(taskId,status){if(taskId&&status){$('#win_bk_tasks_box_0').find('a[href="#win_bk_collected_'+taskId+'"]').attr('data-is-end','1').find('.is_loading').html(''+status+'')}},status_set_timeout:null,status:function(isLoop){if(!isLoop){window.clearTimeout(winBackstageTask.status_set_timeout)} var taskIds=[];$('#win_bk_tasks_box_0').find('[id^="win_bk_collected_"]').each(function(){var taskId=$(this).attr('data-task-id');taskIds.push(taskId)});if(taskIds.length>0){ajaxOpen({type:'post',dataType:'json',async:!0,data:{tids:taskIds},url:ulink('admin/backstage/backstageTask?op=status'),success:function(data){var statusList=data.data;if(isObject(statusList)){for(var tid in statusList){if(statusList[tid]){winBackstageTask.set_task_end(tid,statusList[tid])}}} -var isVisible=$('#win_bk_tasks_box_0').is(':visible');var isEnd=!0;$('#win_bk_tasks_box_0').find('a[href^="#win_bk_collected_"]').each(function(){if(!$(this).attr('data-is-end')){isEnd=!1;return!1}});if(!isEnd&&isVisible){winBackstageTask.status_set_timeout=window.setTimeout(function(){winBackstageTask.status(!0)},3000)}else{window.clearTimeout(winBackstageTask.status_set_timeout)}}})}}};function ajax_check_userpwd(ajaxSet){var oldSuccess=ajaxSet.success;ajaxSet.success=function(data){if(data.data&&data.data._userpwd_){if(data.msg){toastr.error(data.msg)} -confirmRight({closeAfterFunc:!0,yes:'确定',no:'取消',msg:('
该操作需要验证您的登录密码
')},function(){var ajaxSetData=isNull(ajaxSet.data)?{}:ajaxSet.data;var userpwd=$('#confirm_ipt_userpwd').val();if(typeof(ajaxSetData)=='object'){ajaxSetData._userpwd_=userpwd}else{ajaxSetData=ajaxSetData?(ajaxSetData+'&'):'';ajaxSetData+='_userpwd_='+encodeURIComponent(userpwd)} -ajaxSet.data=ajaxSetData;ajaxSet.success=oldSuccess;ajax_check_userpwd(ajaxSet)});$('body').on('keyup','#confirm_ipt_userpwd',function(event){if(event.keyCode=="13"){$('#confirm_right .cr-btn-yes').trigger("click")}})}else{if(oldSuccess&&typeof(oldSuccess)=='function'){oldSuccess(data)}}};ajaxOpen(ajaxSet)} \ No newline at end of file +var isVisible=$('#win_bk_tasks_box_0').is(':visible');var isEnd=!0;$('#win_bk_tasks_box_0').find('a[href^="#win_bk_collected_"]').each(function(){if(!$(this).attr('data-is-end')){isEnd=!1;return!1}});if(!isEnd&&isVisible){winBackstageTask.status_set_timeout=window.setTimeout(function(){winBackstageTask.status(!0)},3000)}else{window.clearTimeout(winBackstageTask.status_set_timeout)}}})}}};function ajax_check_userpwd(ajaxSet){var oldSuccess=ajaxSet.success;ajaxSet.success=function(data){if(data.data&&data.data._check_pwd_){if(data.msg){toastr.error(data.msg)} +var msg='
该操作需要验证您的登录密码
'+''+'
';confirmRight({closeAfterFunc:!0,yes:'确定',no:'取消',msg:msg},function(){var ajaxSetData=isNull(ajaxSet.data)?{}:ajaxSet.data;var checkPwd=$('#confirm_ipt_check_pwd').val();var checkSkip=$('#confirm_ipt_check_skip').is(':checked')?1:'';if(typeof(ajaxSetData)=='object'){ajaxSetData._check_pwd_=checkPwd;ajaxSetData._check_skip_=checkSkip}else{ajaxSetData=ajaxSetData?(ajaxSetData+'&'):'';ajaxSetData+='_check_pwd_='+encodeURIComponent(checkPwd);ajaxSetData+='&_check_skip_='+encodeURIComponent(checkSkip)} +ajaxSet.data=ajaxSetData;ajaxSet.success=oldSuccess;ajax_check_userpwd(ajaxSet)});$('body').on('keyup','#confirm_ipt_check_pwd',function(event){if(event.keyCode=="13"){$('#confirm_right .cr-btn-yes').trigger("click")}})}else{if(oldSuccess&&typeof(oldSuccess)=='function'){oldSuccess(data)}}};ajaxOpen(ajaxSet)} +function editorCodeIfr(ifrEle,options){options=isObject(options)?options:{};if(options.set_value!=null&&typeof(options.set_value)!='undefined'){$(document).ready(function(){$(ifrEle).attr('src','');$(ifrEle).attr('src',ulink('develop/editor_code'));$(ifrEle).off('load').bind('load',function(){if(options.set_value&&$(ifrEle)[0].contentWindow.set_editor_code){$(ifrEle)[0].contentWindow.set_editor_code(options.set_value)}})})}else if(options.get_value){var ifrEle=$(ifrEle)[0];var val='';if(ifrEle&&ifrEle.contentWindow.get_editor_code){val=ifrEle.contentWindow.get_editor_code()} +return val}} \ No newline at end of file diff --git a/public/static/js/admin/code_editor.js b/public/static/js/admin/code_editor.js new file mode 100644 index 0000000..4ce799d --- /dev/null +++ b/public/static/js/admin/code_editor.js @@ -0,0 +1,13 @@ +/* + |-------------------------------------------------------------------------- + | SkyCaiji (蓝天采集器) + |-------------------------------------------------------------------------- + | Copyright (c) 2018 https://www.skycaiji.com All rights reserved. + |-------------------------------------------------------------------------- + | 使用协议 https://www.skycaiji.com/licenses + |-------------------------------------------------------------------------- + */ +'use strict';function CodeEditorClass(){} +CodeEditorClass.prototype={constructor:CodeEditorClass,init_deve:function(config){var $_o=this;config=config?config:{};$('#btn_editor_save').bind('click',function(){ajax_check_userpwd({type:'POST',dataType:'json',url:ulink('develop/editor_save'),data:{type:config.type,module:config.module,app:config.app,appcode:$_o.editor_get_value()},beforeSend:function(){$('#btn_editor_save').attr('disabled',!0)},success:function(data){ajaxDataMsg(data)},complete:function(){$('#btn_editor_save').removeAttr('disabled')}})});var editorHeight=$(document.body).height()-$('#deve_editor_main').offset().top;editorHeight=parseInt(editorHeight)-60;$('#code_editor_box').height(editorHeight);$_o.editor_iframe($('#code_editor_txt').val());var appsScroll=!1;var deveAppsNav=$('#deve_editor_main .deve-editor-apps-nav');if(deveAppsNav.height()>editorHeight){appsScroll=!0;deveAppsNav.css('overflow-y','scroll')} +deveAppsNav.css('height',editorHeight+'px');if(appsScroll){var curApp=$('.deve-editor-apps .cur');if(curApp.length>0){var curAppTop=curApp.offset().top-deveAppsNav.offset().top;if(curAppTop>editorHeight){deveAppsNav.scrollTop(curAppTop-(editorHeight/2)-20)}}}},editor_iframe:function(appcode){var $_o=this;$('#code_editor_ifr').attr('src',ulink('develop/editor_code'));$('#code_editor_ifr').off('load').bind('load',function(){$_o.editor_set_value(appcode)})},editor_get_value:function(){return $('#code_editor_ifr')[0].contentWindow.get_editor_code()},editor_set_value:function(val){$('#code_editor_ifr')[0].contentWindow.set_editor_code(val)}} +var codeEditorClass=new CodeEditorClass() \ No newline at end of file diff --git a/public/static/js/admin/collector.js b/public/static/js/admin/collector.js index 32832e3..e961816 100644 --- a/public/static/js/admin/collector.js +++ b/public/static/js/admin/collector.js @@ -7,10 +7,10 @@ | 使用协议 https://www.skycaiji.com/licenses |-------------------------------------------------------------------------- */ -'use strict';function CollectorPattern(){this.formid='#form_coll';this.cpContentSign=null;this.cpFrontUrl=null;this.cpLevelUrl=null;this.cpRelationUrl=null;this.cpPagination=null;this.cpField=null;this.cpProcess=null} -CollectorPattern.prototype={constructor:CollectorPattern,init:function(){var $_o=this;$($_o.formid).bind('submit',function(){$('#coll_tab_content').find('.tab-pane[id^="coll_pattern_"]').each(function(){if($(this).hasClass('active')){$($_o.formid+' [name="tab_link"]').val($(this).attr('id'));return}});var settings=getFormAjaxSettings($(this));ajaxOpen(settings);return!1});$_o.cpContentSign=new CpContentSign($_o);$_o.cpFrontUrl=new CpFrontUrl($_o);$_o.cpLevelUrl=new CpLevelUrl($_o);$_o.cpRelationUrl=new CpRelationUrl($_o);$_o.cpPagination=new CpPagination($_o);$_o.cpField=new CpField($_o);$_o.cpProcess=new CpProcess($_o);inputSelectCustom($_o.formid+' select[name="config[charset]"]','config[charset_custom]');$($_o.formid+' #coll_pattern_request_headers .dm-useragent li a').bind('click',function(){$($_o.formid+' [name="config[request_headers][useragent]"]').val($(this).attr('data-useragent'))});$($_o.formid+' #coll_pattern_request_headers .add-request-header').bind('click',function(){$_o.add_request_header('','')});$($_o.formid+' #coll_pattern_request_headers .add-request-header-img').bind('click',function(){$_o.add_request_header_img('','')});$($_o.formid+' .c-p-request-headers').on('click','.delete-request-header',function(){$(this).parents('tr').eq(0).remove()});$($_o.formid+' .c-p-request-headers-img').on('click','.delete-request-header-img',function(){$(this).parents('tr').eq(0).remove()});$_o.init_page_list_op('front_url');$(this.formid+' #coll_pattern_source_url .add-source-url').bind('click',function(){windowModal('添加起始网址',ulink("cpattern/source"),{lg:1})});$(this.formid+' #coll_pattern_source_url .clear-source-url').bind('click',function(){$_o.source_op('clear_all')});$(this.formid+' #coll_pattern_source_url').on('click','.edit-source-url',function(){var parent=$(this).parents('[id^="source_url_"]').eq(0);var objid=parent.attr('id');var sourceUrl=parent.find('[name="config[source_url][]"]').val();sourceUrl=sourceUrl?sourceUrl:'';var url=ulink("cpattern/source");var options={lg:1};if(objid||sourceUrl){options.ajax={type:'post',data:{'source_url':sourceUrl,'objid':objid}}} -windowModal('添加起始网址',url,options)});$(this.formid+' #coll_pattern_source_url').on('click','.delete-source-url',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){obj.parents('[id^="source_url_"]').eq(0).remove()})});eleExchange(this.formid+' #coll_pattern_source_url','.icon-drag-move','[id^="source_url_"]');$(this.formid+' [name="config[source_is_url]"]').bind('click',function(){if($_o.source_is_url()){$('#alert_coll_pattern_link').show();$('#panel_coll_pattern_source_url_web').hide();$('#panel_coll_pattern_source_url_content_sign').hide();$('#panel_coll_pattern_source_url_pagination').hide();$('#panel_coll_pattern_level_url').hide();$('#panel_coll_pattern_url_content_sign').siblings('.panel').hide();$('#panel_coll_pattern_url_web').show()}else{$('#alert_coll_pattern_link').hide();$('#panel_coll_pattern_source_url_web').show();$('#panel_coll_pattern_source_url_content_sign').show();$('#panel_coll_pattern_source_url_pagination').show();$('#panel_coll_pattern_level_url').show();$('#panel_coll_pattern_url_content_sign').siblings('.panel').show()}});$_o.init_page('source_url');$_o.init_page('url');$($_o.formid).on('click','.c-p-url-page-signs .btn-page-signs',function(){$_o.parent_page_signs(this)});$_o.init_page_list_op('level_url');$_o.init_page_list_op('relation_url');$(this.formid+' #coll_pattern_field').on('click','.add-field',function(){$_o.field_editor(null,null)});$(this.formid+' #coll_pattern_field').on('click','.field-name',function(){$_o.field_editor($(this),null)});$(this.formid+' #coll_pattern_field').on('click','.add-field-default',function(){$_o.cpField.add_default()});$(this.formid+' #coll_pattern_field').on('click','.sort-field',function(){var fieldNames=$_o.get_field_names(!0);for(var i in fieldNames){var fieldTr=$($_o.formid+' #coll_pattern_field').find('.field-name[data-val="'+fieldNames[i]+'"]').parents('tr[id^="field_"]').eq(0);if(fieldTr.length>0){$($_o.formid+' #coll_pattern_field .c-p-field-list tbody').append(fieldTr)}} -toastr.success('调整完成')});$(this.formid+' #coll_pattern_field').on('click','.field-del',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){$_o.field_delete_tr(obj)})});$(this.formid+' #coll_pattern_field').on('click','.field-clone',function(){var tr=$(this).parents('tr[id^="field_"]').eq(0);var field=tr.find('[name="config[field_list][]"]').val();var process=tr.find('[name="config[field_process][]"]').val();confirmRight('确定复制字段?',function(){ajaxOpen({type:'POST',dataType:'json',url:ulink("cpattern/clone_field"),data:{field:field,process:process},success:function(data){if(data.code==1){data=data.data;var hasField=!1;do{data.field.name+='_1';hasField=$('#coll_pattern_field .c-p-field-list').find('.field-name[data-val="'+data.field.name+'"]');if(hasField&&hasField.length>0){hasField=!0}else{hasField=!1}}while(hasField);$_o.cpField.add(null,data.field,data.process);toastr.success('字段复制成功:'+data.field.name)}else{toastr.error(data.msg)}}})})});$(this.formid+' #coll_pattern_field').on('click','.field-process',function(){var process=$(this).parent().find('input[name="config[field_process][]"]').val();var prt=$(this).parents('tr[id^="field_"]').eq(0);var objid=prt.attr('id');var field=prt.find('.field-name').attr('data-val');var url=ulink("cpattern/process?field=_field_",{'_field_':field});windowModal('数据处理:'+field+'',url,{lg:1,ajax:{type:'post',data:{objid:objid,process:process}}});$_o.process_paste()});$(this.formid+' #coll_pattern_process').on('click','.add-process',function(){var url=ulink("cpattern/process?type=common");windowModal('数据处理(通用)',url,{lg:1});$_o.process_paste()});eleExchange(this.formid+' #coll_pattern_field','.icon-drag-move','tr[id^="field_"]');$(this.formid+' [name="effective"]').val(1)},load:function(config){var $_o=this;if(config){$(this.formid+' [name="config[charset]"]').val(config.charset).trigger('change');$(this.formid+' [name="config[charset_custom]"]').val(config.charset_custom);$(this.formid+' [name="config[url_complete]"][value="'+parseInt(config.url_complete)+'"]').prop('checked','checked');$(this.formid+' [name="config[url_reverse]"][value="'+parseInt(config.url_reverse)+'"]').prop('checked','checked');$(this.formid+' [name="config[page_render]"][value="'+parseInt(config.page_render)+'"]').prop('checked','checked');$(this.formid+' [name="config[url_repeat]"][value="'+parseInt(config.url_repeat)+'"]').prop('checked','checked');if(config.regexp_flags){for(var i in config.regexp_flags){$(this.formid).find('[name="config[regexp_flags][]"][value="'+config.regexp_flags[i]+'"]').prop('checked',!0)}} +'use strict';function CollectorPattern(){this.formid='#form_coll';this.cpFrontUrl=null;this.cpLevelUrl=null;this.cpRelationUrl=null;this.cpUrlWeb=null;this.cpRenderer=null;this.cpContentSign=null;this.cpPagination=null;this.cpField=null;this.cpProcess=null} +CollectorPattern.prototype={constructor:CollectorPattern,init_test:function(){var $_o=this;$_o.formid='#win_form_cache';$_o.cpUrlWeb=new CpUrlWeb($_o);$_o.cpRenderer=new CpRenderer($_o);$_o.cpUrlWeb.page_init('test');$_o.cpRenderer.page_init('test')},init:function(){var $_o=this;$($_o.formid).bind('submit',function(){$('#coll_tab_content').find('.tab-pane[id^="coll_pattern_"]').each(function(){if($(this).hasClass('active')){$($_o.formid+' [name="tab_link"]').val($(this).attr('id'));return}});var settings=getFormAjaxSettings($(this));ajaxOpen(settings);return!1});$_o.cpFrontUrl=new CpFrontUrl($_o);$_o.cpLevelUrl=new CpLevelUrl($_o);$_o.cpRelationUrl=new CpRelationUrl($_o);$_o.cpUrlWeb=new CpUrlWeb($_o);$_o.cpRenderer=new CpRenderer($_o);$_o.cpContentSign=new CpContentSign($_o);$_o.cpPagination=new CpPagination($_o);$_o.cpField=new CpField($_o);$_o.cpProcess=new CpProcess($_o);inputSelectCustom($_o.formid+' select[name="config[charset]"]','config[charset_custom]',null,null,function(){$_o.cpUrlWeb.def_config_charset('')});inputSelectCustom($_o.formid+' select[name="config[encode]"]','config[encode_custom]',null,null,function(){$_o.cpUrlWeb.def_config_encode('')});$($_o.formid+' [name="config[page_render]"]').bind('click',function(){$_o.cpRenderer.def_config_renderer_open('')});$($_o.formid+' [name="config[request_headers][open]"]').bind('click',function(){$_o.cpUrlWeb.def_config_header_global('')});$($_o.formid+' #coll_pattern_request_headers .dm-useragent li a').bind('click',function(){$($_o.formid+' [name="config[request_headers][useragent]"]').val($(this).attr('data-useragent'))});$($_o.formid+' #coll_pattern_request_headers .add-request-header').bind('click',function(){$_o.add_request_header('','')});$($_o.formid+' #coll_pattern_request_headers .add-request-header-img').bind('click',function(){$_o.add_request_header_img('','')});$($_o.formid+' .c-p-request-headers').on('click','.delete-request-header',function(){$(this).parents('tr').eq(0).remove()});$($_o.formid+' .c-p-request-headers-img').on('click','.delete-request-header-img',function(){$(this).parents('tr').eq(0).remove()});$_o.init_page_list_op('front_url');$(this.formid+' #coll_pattern_source_url .add-source-url').bind('click',function(){windowModal('添加起始网址',ulink("cpattern/source"),{lg:1})});$(this.formid+' #coll_pattern_source_url .clear-source-url').bind('click',function(){$_o.source_op('clear_all')});$(this.formid+' #coll_pattern_source_url').on('click','.edit-source-url',function(){var parent=$(this).parents('[id^="source_url_"]').eq(0);var objid=parent.attr('id');var sourceUrl=parent.find('[name="config[source_url][]"]').val();sourceUrl=sourceUrl?sourceUrl:'';var url=ulink("cpattern/source");var options={lg:1};if(objid||sourceUrl){options.ajax={type:'post',data:{'source_url':sourceUrl,'objid':objid}}} +windowModal('添加起始网址',url,options)});$(this.formid+' #coll_pattern_source_url').on('click','.delete-source-url',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){obj.parents('[id^="source_url_"]').eq(0).remove()})});eleExchange(this.formid+' #coll_pattern_source_url','.icon-drag-move','[id^="source_url_"]');$(this.formid+' [name="config[source_is_url]"]').bind('click',function(){if($_o.source_is_url()){$('#alert_coll_pattern_link').show();$('#panel_coll_pattern_source_url_web').hide();$('#panel_coll_pattern_source_url_renderer').hide();$('#panel_coll_pattern_source_url_content_sign').hide();$('#panel_coll_pattern_source_url_pagination').hide();$('#panel_coll_pattern_level_url').hide();$('#panel_coll_pattern_url_content_sign').siblings('.panel').hide();$('#panel_coll_pattern_url_web').show();$('#panel_coll_pattern_url_renderer').show()}else{$('#alert_coll_pattern_link').hide();$('#panel_coll_pattern_source_url_web').show();$('#panel_coll_pattern_source_url_renderer').show();$('#panel_coll_pattern_source_url_content_sign').show();$('#panel_coll_pattern_source_url_pagination').show();$('#panel_coll_pattern_level_url').show();$('#panel_coll_pattern_url_content_sign').siblings('.panel').show()}});$_o.init_page('source_url');$_o.init_page('url');$($_o.formid).on('click','.c-p-url-page-signs .btn-page-signs',function(){$_o.parent_page_signs(this)});$_o.init_page_list_op('level_url');$_o.init_page_list_op('relation_url');$(this.formid+' #coll_pattern_field').on('click','.add-field',function(){$_o.field_editor(null,null)});$(this.formid+' #coll_pattern_field').on('click','.field-name',function(){$_o.field_editor($(this),null)});$(this.formid+' #coll_pattern_field').on('click','.add-field-default',function(){$_o.cpField.add_default()});$(this.formid+' #coll_pattern_field').on('click','.sort-field',function(){var fieldNames=$_o.get_field_names(!0);for(var i in fieldNames){var fieldTr=$($_o.formid+' #coll_pattern_field').find('.field-name[data-val="'+fieldNames[i]+'"]').parents('tr[id^="field_"]').eq(0);if(fieldTr.length>0){$($_o.formid+' #coll_pattern_field .c-p-field-list tbody').append(fieldTr)}} +toastr.success('调整完成')});$(this.formid+' #coll_pattern_field').on('click','.field-del',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){$_o.field_delete_tr(obj)})});$(this.formid+' #coll_pattern_field').on('click','.field-clone',function(){var tr=$(this).parents('tr[id^="field_"]').eq(0);var field=tr.find('[name="config[field_list][]"]').val();var process=tr.find('[name="config[field_process][]"]').val();confirmRight('确定复制字段?',function(){ajaxOpen({type:'POST',dataType:'json',url:ulink("cpattern/clone_field"),data:{field:field,process:process},success:function(data){if(data.code==1){data=data.data;var hasField=!1;do{data.field.name+='_1';hasField=$('#coll_pattern_field .c-p-field-list').find('.field-name[data-val="'+data.field.name+'"]');if(hasField&&hasField.length>0){hasField=!0}else{hasField=!1}}while(hasField);$_o.cpField.add(null,data.field,data.process);toastr.success('字段复制成功:'+data.field.name)}else{toastr.error(data.msg)}}})})});$(this.formid+' #coll_pattern_field').on('click','.field-process',function(){var process=$(this).parent().find('input[name="config[field_process][]"]').val();var prt=$(this).parents('tr[id^="field_"]').eq(0);var objid=prt.attr('id');var field=prt.find('.field-name').attr('data-val');var url=ulink("cpattern/process?field=_field_",{'_field_':field});windowModal('数据处理:'+field+'',url,{lg:1,ajax:{type:'post',data:{objid:objid,process:process}}});$_o.process_paste()});$(this.formid+' #coll_pattern_process').on('click','.add-process',function(){var url=ulink("cpattern/process?type=common");windowModal('数据处理(通用)',url,{lg:1});$_o.process_paste()});eleExchange(this.formid+' #coll_pattern_field','.icon-drag-move','tr[id^="field_"]');$(this.formid+' [name="effective"]').val(1)},load:function(config){var $_o=this;if(config){$(this.formid+' [name="config[charset_custom]"]').val(config.charset_custom);$(this.formid+' [name="config[charset]"]').val(config.charset).trigger('change');$(this.formid+' [name="config[encode_custom]"]').val(config.encode_custom);$(this.formid+' [name="config[encode]"]').val(config.encode).trigger('change');$(this.formid+' [name="config[url_complete]"][value="'+toInt(config.url_complete)+'"]').prop('checked',!0);$(this.formid+' [name="config[url_reverse]"][value="'+toInt(config.url_reverse)+'"]').prop('checked',!0);$(this.formid+' [name="config[page_render]"][value="'+toInt(config.page_render)+'"]').prop('checked',!0).trigger('click');$(this.formid+' [name="config[url_repeat]"][value="'+toInt(config.url_repeat)+'"]').prop('checked',!0);$(this.formid+' [name="config[url_no_name]"][value="'+toInt(config.url_no_name)+'"]').prop('checked',!0);if(config.regexp_flags){for(var i in config.regexp_flags){$(this.formid).find('[name="config[regexp_flags][]"][value="'+config.regexp_flags[i]+'"]').prop('checked',!0)}} if(isObject(config.front_urls)){for(var i in config.front_urls){$_o.cpFrontUrl.add(null,config.front_urls[i])} showPanelCollapse('#coll_pattern_front_url')} if(config.source_url){var source_url_html_list='';var sourceParams={get:1};for(var i in config.source_url){sourceParams.source_url=config.source_url[i];source_url_html_list+=this.source_op('add',sourceParams)} @@ -23,20 +23,17 @@ if(isObject(config.relation_urls)){for(var i in config.relation_urls){$_o.cpRela showPanelCollapse('#coll_pattern_relation_url')} if(config.field_list&&config.field_list.length>0){this.cpField.clearall();for(var i in config.field_list){var fieldProcess=null;if(config.field_process){fieldProcess=config.field_process[i]} this.cpField.add(null,config.field_list[i],fieldProcess)}} -if(config.field_title){$(this.formid+' [name="config[field_title]"]').each(function(){if($(this).val()==config.field_title){$(this).prop('checked','checked')}})} +if(config.field_title){$(this.formid+' [name="config[field_title]"]').each(function(){if($(this).val()==config.field_title){$(this).prop('checked',!0)}})} if(config.common_process&&config.common_process.length>0){showPanelCollapse('#coll_pattern_process');ajaxOpen({type:'post',url:ulink("cpattern/process?type=common&op=load"),data:{process:config.common_process},dataType:'html',beforeSend:function(){$($_o.cpProcess.processForm+' .c-p-process-accordion').append('
')},success:function(data){$('body').append(data)},complete:function(){$($_o.cpProcess.processForm+' .c-p-process-accordion').find('.loading').remove()}})} -if(config.request_headers){var r_h_params=new Array('useragent','cookie','referer');for(var i in r_h_params){$(this.formid+' [name="config[request_headers]['+r_h_params[i]+']"]').val(config.request_headers[r_h_params[i]])} -if(config.request_headers.custom_names){var r_h_vals=config.request_headers.custom_vals?config.request_headers.custom_vals:{};for(var i in config.request_headers.custom_names){$_o.add_request_header(config.request_headers.custom_names[i],r_h_vals[i])}} -var r_h_radios=new Array('open','img','img_use_page');for(var i in r_h_radios){var rhr_v=config.request_headers[r_h_radios[i]];if(r_h_radios[i]!='img_use_page'){rhr_v=parseInt(rhr_v)} -$(this.formid+' [name="config[request_headers]['+r_h_radios[i]+']"][value="'+rhr_v+'"]').trigger('click')} -if(toInt(config.request_headers.open)>0){showPanelCollapse('#coll_pattern_request_headers');showPanelCollapse('#c_p_request_headers_open')} -if(toInt(config.request_headers.img)>0){showPanelCollapse('#coll_pattern_request_headers');showPanelCollapse('#c_p_request_headers_img')} +if(config.request_headers){$(this.formid+' [name="config[request_headers][useragent]"]').val(config.request_headers.useragent);$(this.formid+' [name="config[request_headers][cookie]"]').val(config.request_headers.cookie);$(this.formid+' [name="config[request_headers][referer]"]').val(config.request_headers.referer);if(config.request_headers.custom_names){var r_h_vals=config.request_headers.custom_vals?config.request_headers.custom_vals:{};for(var i in config.request_headers.custom_names){$_o.add_request_header(config.request_headers.custom_names[i],r_h_vals[i])}} +config.request_headers.open=toInt(config.request_headers.open);config.request_headers.img=toInt(config.request_headers.img);$(this.formid+' [name="config[request_headers][open]"][value="'+config.request_headers.open+'"]').prop('checked',!0).trigger('click');$(this.formid+' [name="config[request_headers][img]"][value="'+config.request_headers.img+'"]').prop('checked',!0);$(this.formid+' [name="config[request_headers][img_use_page]"][value="'+config.request_headers.img_use_page+'"]').prop('checked',!0);if(config.request_headers.open>0){showPanelCollapse('#coll_pattern_request_headers');showPanelCollapse('#c_p_request_headers_open')} +if(config.request_headers.img>0){showPanelCollapse('#coll_pattern_request_headers');showPanelCollapse('#c_p_request_headers_img')} if(config.request_headers.img_names){var r_h_img_vals=config.request_headers.img_vals?config.request_headers.img_vals:{};for(var i in config.request_headers.img_names){$_o.add_request_header_img(config.request_headers.img_names[i],r_h_img_vals[i])}}}} -$(this.formid+' [name="effective_edit"]').val(1)},get_page_vars:function(pageType,returnKey){var title='';var boxId='';var namePre='';var formId='';if('front_url'==pageType){title='前置页';boxId='#c_p_front_url';namePre='front_url';formId=this.cpFrontUrl.formObj}else if('source_url'==pageType){title='起始页';boxId='#coll_pattern_source_url';namePre='config[source_config]';formId=this.formid}else if('level_url'==pageType){title='多级页';boxId='#c_p_level_url';namePre='level_url';formId=this.cpLevelUrl.formObj}else if('relation_url'==pageType){title='关联页';boxId='#c_p_relation_url';namePre='relation_url';formId=this.cpRelationUrl.formObj}else if('url'==pageType){title='内容页';boxId='#coll_pattern_url';namePre='config';formId=this.formid} +$(this.formid+' [name="effective_edit"]').val(1)},get_page_vars:function(pageType,returnKey){var title='';var boxId='';var namePre='';var formId='';if('front_url'==pageType){title='前置页';boxId='#c_p_front_url';namePre='front_url';formId=this.cpFrontUrl.formObj}else if('source_url'==pageType){title='起始页';boxId='#coll_pattern_source_url';namePre='config[source_config]';formId=this.formid}else if('level_url'==pageType){title='多级页';boxId='#c_p_level_url';namePre='level_url';formId=this.cpLevelUrl.formObj}else if('relation_url'==pageType){title='关联页';boxId='#c_p_relation_url';namePre='relation_url';formId=this.cpRelationUrl.formObj}else if('url'==pageType){title='内容页';boxId='#coll_pattern_url';namePre='config';formId=this.formid}else if('test'==pageType){title='';boxId='#win_coll_pattern_test';namePre='config';formId='#win_form_test'} var data={title:title,boxId:boxId,namePre:namePre,formId:formId};if(returnKey){return data[returnKey]}else{return data}},page_is_list:function(pageType){if(pageType=='front_url'||pageType=='level_url'||pageType=='relation_url'){return!0}else{return!1}},init_page_list_op:function(pageType){var $_o=this;if(!$_o.page_is_list(pageType)){return} var pageVars=$_o.get_page_vars(pageType);var listObj='#c_p_'+pageType+'s';var parentObj='[id^="'+pageType+'_"]';$($_o.formid+' #coll_pattern_'+pageType+' .add-'+pageType.replace('_','-')).bind('click',function(){var url=ulink('cpattern/'+pageType);windowModal('添加'+pageVars.title+'规则',url,{lg:1})});$($_o.formid+' '+listObj).on('click','.name',function(){var parent=$(this).parents(parentObj).eq(0);var options={lg:1,ajax:{type:'post',data:{}}};options.ajax.data.objid=parent.attr('id');options.ajax.data[pageType]=parent.find('[name="config['+pageType+'s][]"]').val();windowModal('编辑'+pageVars.title+'规则',ulink('cpattern/'+pageType),options)});$($_o.formid+' '+listObj).on('click','.clone',function(){var parent=$(this).parents(parentObj).eq(0);var pageConfig=parent.find('[name="config['+pageType+'s][]"]').val();var postData={};postData[pageType]=pageConfig;confirmRight('确定复制'+pageVars.title+'?',function(){ajaxOpen({type:'POST',dataType:'json',url:ulink('cpattern/clone_'+pageType),data:postData,success:function(data){if(data.code==1){data=data.data;var hasName=!1;do{data[pageType].name+='_1';hasName=$($_o.formid+' '+listObj).find('.name[data-val="'+data[pageType].name+'"]');if(hasName&&hasName.length>0){hasName=!0}else{hasName=!1}}while(hasName);if(pageType=='front_url'){$_o.cpFrontUrl.add(null,data[pageType])}else if(pageType=='level_url'){$_o.cpLevelUrl.add(null,data[pageType])}else if(pageType=='relation_url'){$_o.cpRelationUrl.add(null,data[pageType])} toastr.success(pageVars.title+'复制成功:'+data[pageType].name)}else{toastr.error(data.msg)}}})})});$($_o.formid+' '+listObj).on('click','.delete',function(){var curObj=$(this);confirmRight('确定删除?',function(){curObj.parents(parentObj).eq(0).remove()})});eleExchange($_o.formid+' '+listObj,'.icon-drag-move','tbody tr')},init_page:function(pageType){var $_o=this;var pageVars=$_o.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!formId||!boxId||!namePre){return} -$(formId+' select[name="'+namePre+'[area_module]"],select[name="'+namePre+'[url_rule_module]"]').bind('click',function(){$_o.rule_module_slt(this)});var boxUrlWebId=boxId+'_web';$(boxUrlWebId+' [name="'+namePre+'[url_web][open]"]').bind('click',function(){if($(this).val()==1){$(boxUrlWebId+'_open').show()}else{$(boxUrlWebId+'_open').hide()}});inputSelectCustom(boxUrlWebId+' select[name="'+namePre+'[url_web][charset]"]',namePre+'[url_web][charset_custom]');$(boxUrlWebId+' select[name="'+namePre+'[url_web][form_method]"]').bind('change',function(){var obj=$(boxUrlWebId+' .c-p-url-web-content-type');if($(this).val()=='post'){obj.show()}else{obj.hide()}});$(boxUrlWebId+' .add-url-web-form').bind('click',function(){$_o.add_page_url_web(pageType,'form','','')});$(boxUrlWebId+' .c-p-url-web-form').on('click','.delete-url-web-form',function(){$(this).parents('tr').eq(0).remove()});$(boxUrlWebId+' .add-url-web-header').bind('click',function(){$_o.add_page_url_web(pageType,'header','','')});$(boxUrlWebId+' .c-p-url-web-header').on('click','.delete-url-web-header',function(){$(this).parents('tr').eq(0).remove()});$_o.cpContentSign.page_init(pageType);$_o.cpPagination.page_init(pageType)},load_page_rule:function(pageType,config,isPagination){var $_o=this;var pageVars=$_o.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!config||!formId||!boxId||!namePre){return} +$(formId+' select[name="'+namePre+'[area_module]"],select[name="'+namePre+'[url_rule_module]"]').bind('click',function(){$_o.rule_module_slt(this)});$_o.cpUrlWeb.page_init(pageType);$_o.cpRenderer.page_init(pageType);$_o.cpContentSign.page_init(pageType);$_o.cpPagination.page_init(pageType)},load_page_rule:function(pageType,config,isPagination){var $_o=this;var pageVars=$_o.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!config||!formId||!boxId||!namePre){return} if(isPagination){boxId+='_pagination';namePre+='[pagination]'} if(config.area||config.area_merge){$(formId+' [name="'+namePre+'[area]"]').val(config.area);$(formId+' [name="'+namePre+'[area_merge]"]').val(config.area_merge);showPanelCollapse(boxId+'_area')} if(config.area_module){$(formId+' select[name="'+namePre+'[area_module]"]').val(config.area_module).trigger('click').trigger('change')} @@ -44,18 +41,9 @@ if(config.url_rule||config.url_merge){$(formId+' [name="'+namePre+'[url_rule]"]' if(config.url_rule_module){$(formId+' select[name="'+namePre+'[url_rule_module]"]').val(config.url_rule_module).trigger('click').trigger('change')} if(config.url_must||config.url_ban){$(formId+' [name="'+namePre+'[url_must]"]').val(config.url_must);$(formId+' [name="'+namePre+'[url_ban]"]').val(config.url_ban);showPanelCollapse(boxId+'_filter')}},load_page:function(pageType,config){var $_o=this;var pageVars=$_o.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!config||!formId||!boxId||!namePre){return} if(pageType=='level_url'||pageType=='url'||pageType=='relation_url'){$_o.load_page_rule(pageType,config,!1)} -var urlWebConfig=config.url_web?config.url_web:{};var urlWebNamePre=namePre+'[url_web]';urlWebConfig.open=toInt(urlWebConfig.open);if(urlWebConfig.open){$(formId+' [name="'+urlWebNamePre+'[open]"][value="'+urlWebConfig.open+'"]').trigger('click');if(urlWebConfig.open>0){showPanelCollapse(boxId+'_web')}} -if(urlWebConfig.charset){$(formId+' select[name="'+urlWebNamePre+'[charset]"]').val(urlWebConfig.charset).trigger('change')} -if(urlWebConfig.charset_custom){$(formId+' [name="'+urlWebNamePre+'[charset_custom]"]').val(urlWebConfig.charset_custom)} -if(urlWebConfig.form_method){$(formId+' [name="'+urlWebNamePre+'[form_method]"]').val(urlWebConfig.form_method).trigger('change')} -if(urlWebConfig.content_type){$(formId+' [name="'+urlWebNamePre+'[content_type]"]').val(urlWebConfig.content_type)} -if(urlWebConfig.form_names){var urlWebFormVals=urlWebConfig.form_vals?urlWebConfig.form_vals:{};for(var i in urlWebConfig.form_names){$_o.add_page_url_web(pageType,'form',urlWebConfig.form_names[i],urlWebFormVals[i])}} -if(urlWebConfig.header_global){$(formId+' [name="'+urlWebNamePre+'[header_global]"]').val(urlWebConfig.header_global)} -if(urlWebConfig.header_names){var urlWebHeaderVals=urlWebConfig.header_vals?urlWebConfig.header_vals:{};for(var i in urlWebConfig.header_names){$_o.add_page_url_web(pageType,'header',urlWebConfig.header_names[i],urlWebHeaderVals[i])}} -$_o.cpContentSign.page_load(pageType,config.content_signs);$_o.cpPagination.page_load(pageType,config.pagination)},add_page_url_web:function(pageType,type,name,val){var $_o=this;var pageVars=$_o.get_page_vars(pageType);var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!boxId||!namePre||!type){return} -boxId+='_web';name=name?name:'';val=val?val:'';var tr=$_o.clone_tpl('#coll_tpl_url_web_'+type,namePre);tr.find('[name="'+namePre+'[url_web]['+type+'_names][]"]').val(name);tr.find('[name="'+namePre+'[url_web]['+type+'_vals][]"]').val(val);tr.find('.c-p-url-page-signs').each(function(){$(this).attr('data-page-type',pageType).attr('data-input-name',namePre+$(this).attr('data-input-name'))});$(boxId).find('.c-p-url-web-'+type).append(tr)},source_is_url:function(){var isUrl=$(this.formid+' [name="config[source_is_url]"]').is(':checked');isUrl=isUrl?1:'';return isUrl},source_op:function(op,params){var $_o=this;params=params?params:{};var formObj=params.formObj?params.formObj:'#form_source';if(op=='init'){$(formObj+' #source_url_sign').bind('click',function(){var sign=window.tpl_lang.sign_match.replace('{:id}','');var ipt=$('#source_url');if(ipt.val().indexOf(sign)<0){insertAtCaret(ipt,sign)}else{toastr.error('存在'+sign)}});$(formObj).find('.nav-tabs li a').bind('click',function(){$(formObj).find('input[name="source[type]"]').val($(this).attr('source-type'))});$(formObj).find('div[source-param]').find('input,textarea').bind('change',function(){if($(this).attr("type")=='radio'){return!1} -$(this).parents('div[source-param]').find('input[name="source[param]"]').prop('checked',!0)});if(params.source){$(formObj+' .nav-tabs').find('a[source-type="'+params.source.type+'"]').click();if(params.source.type=='custom'){$(formObj+' textarea[name="source[urls]"]').val(params.source.urls)}else if(params.source.type=='batch'){$(formObj+' input[name="source[url]"]').val(params.source.url);var param_type=params.source.param;if(param_type=='num'){$(formObj+' input[name="source[param_num_start]"]').val(params.source.param_num_start);$(formObj+' input[name="source[param_num_end]"]').val(params.source.param_num_end);$(formObj+' input[name="source[param_num_inc]"]').val(params.source.param_num_inc);if(params.source.param_num_desc){$(formObj+' input[name="source[param_num_desc]"]').prop('checked','checked')}}else if(param_type=='letter'){$(formObj+' input[name="source[param_letter_start]"]').val(params.source.param_letter_start);$(formObj+' input[name="source[param_letter_end]"]').val(params.source.param_letter_end);if(params.source.param_letter_desc){$(formObj+' input[name="source[param_letter_desc]"]').prop('checked','checked')}}else if(param_type=='custom'){$(formObj+' textarea[name="source[param_custom]"]').val(params.source.param_custom)} -$(formObj+' input[name="source[param]"][value="'+param_type+'"]').prop('checked','checked')}else if(params.source.type=='large'){$(formObj+' textarea[name="source[large_urls]"]').val(params.source.large_urls)}else if(params.source.type=='api'){$(formObj+' input[name="source[api]"]').val(params.source.api);$(formObj+' input[name="source[api_json]"]').val(params.source.api_json)}}}else if(op=='add'){var html='';if(params.html){html=params.html}else{var htmlObj=$_o.clone_tpl('#coll_tpl_source_url');htmlObj.find('[data-id="source_url_"]').attr('id','source_url_'+generateUUID()).removeAttr('data-id');var regLarge=/[\r\n]/;if(regLarge.test(params.source_url)){htmlObj.find('input[name="config[source_url][]"]').remove()}else{htmlObj.find('textarea[name="config[source_url][]"]').remove()} +$_o.cpUrlWeb.page_load(pageType,config.url_web);$_o.cpRenderer.page_load(pageType,config.renderer);$_o.cpContentSign.page_load(pageType,config.content_signs);$_o.cpPagination.page_load(pageType,config.pagination)},source_is_url:function(){var isUrl=$(this.formid+' [name="config[source_is_url]"]').is(':checked');isUrl=isUrl?1:'';return isUrl},source_op:function(op,params){var $_o=this;params=params?params:{};var formObj=params.formObj?params.formObj:'#form_source';if(op=='init'){$(formObj+' #source_url_sign').bind('click',function(){var sign=window.tpl_lang.sign_match.replace('{:id}','');var ipt=$('#source_url');if(ipt.val().indexOf(sign)<0){insertAtCaret(ipt,sign)}else{toastr.error('存在'+sign)}});$(formObj).find('.nav-tabs li a').bind('click',function(){$(formObj).find('input[name="source[type]"]').val($(this).attr('source-type'))});$(formObj).find('div[source-param]').find('input,textarea').bind('change',function(){if($(this).attr("type")=='radio'){return!1} +$(this).parents('div[source-param]').find('input[name="source[param]"]').prop('checked',!0)});if(params.source){$(formObj+' .nav-tabs').find('a[source-type="'+params.source.type+'"]').click();if(params.source.type=='custom'){$(formObj+' textarea[name="source[urls]"]').val(params.source.urls)}else if(params.source.type=='batch'){$(formObj+' input[name="source[url]"]').val(params.source.url);var param_type=params.source.param;if(param_type=='num'){$(formObj+' input[name="source[param_num_start]"]').val(params.source.param_num_start);$(formObj+' input[name="source[param_num_end]"]').val(params.source.param_num_end);$(formObj+' input[name="source[param_num_inc]"]').val(params.source.param_num_inc);if(params.source.param_num_desc){$(formObj+' input[name="source[param_num_desc]"]').prop('checked',!0)}}else if(param_type=='letter'){$(formObj+' input[name="source[param_letter_start]"]').val(params.source.param_letter_start);$(formObj+' input[name="source[param_letter_end]"]').val(params.source.param_letter_end);if(params.source.param_letter_desc){$(formObj+' input[name="source[param_letter_desc]"]').prop('checked',!0)}}else if(param_type=='custom'){$(formObj+' textarea[name="source[param_custom]"]').val(params.source.param_custom)} +$(formObj+' input[name="source[param]"][value="'+param_type+'"]').prop('checked',!0)}else if(params.source.type=='large'){$(formObj+' textarea[name="source[large_urls]"]').val(params.source.large_urls)}else if(params.source.type=='api'){$(formObj+' input[name="source[api]"]').val(params.source.api);$(formObj+' input[name="source[api_json]"]').val(params.source.api_json)}}}else if(op=='add'){var html='';if(params.html){html=params.html}else{var htmlObj=$_o.clone_tpl('#coll_tpl_source_url');htmlObj.find('[data-id="source_url_"]').attr('id','source_url_'+generateUUID()).removeAttr('data-id');var regLarge=/[\r\n]/;if(regLarge.test(params.source_url)){htmlObj.find('input[name="config[source_url][]"]').remove()}else{htmlObj.find('textarea[name="config[source_url][]"]').remove()} html=htmlObj.html();html=html?html:'';html=html.replace('[_source_url_]',htmlspecialchars(params.source_url))} if(params.get){return html}else{this.source_op('clear_null');$($_o.formid+' #coll_pattern_source_url .c-p-source-urls').append(html)}}else if(op=='add_sub'){ajaxOpen({type:'POST',dataType:'json',url:$(formObj).attr('action'),data:$(formObj).serialize(),success:function(data){var dataList=data.data?data.data:{};var objid=dataList.objid?dataList.objid:'';var addParams={};if(data.code==1){var source_type=$(formObj).find('input[name="source[type]"]').val();if(source_type=='custom'){$('#myModal').modal('hide');var urls=dataList.urls;var ix=0;var url_html_list='';for(var i in urls){addParams.get=1;addParams.source_url=urls[i];ix++;if(ix==1){if(objid){var newSource=$_o.source_op('add',addParams);$('#'+objid).replaceWith(newSource);continue}} url_html_list+=$_o.source_op('add',addParams)} @@ -67,7 +55,7 @@ $('#myModal').modal('hide')}}else{toastr.error(data.msg)}},error:function(data){ if(!$_o.source_is_url()){options+='';var level_urls=new Array();$('#c_p_level_urls [id^="level_url_"]').each(function(){var levelName=$(this).find('.name').attr('data-val');level_urls.push('')});if(level_urls.length>0){options+=level_urls.join('')}} options+='';var relation_urls=new Array();$('#c_p_relation_urls [id^="relation_url_"]').each(function(){var relationName=$(this).find('.name').attr('data-val');relation_urls.push('')});if(relation_urls.length>0){options+=relation_urls.join('')} return options},field_delete_tr:function(subEle){$(subEle).parents('tr[id^="field_"]').eq(0).remove()},field_editor:function(subEle,hiddenFunc){var field=null;var objid=null;var title='添加字段';if(subEle){objid=$(subEle).parents('tr[id^="field_"]').eq(0).attr('id');if(objid){field=$('#'+objid).find('input[name="config[field_list][]"]').val();title='编辑字段'}} -var options={hidden_func:hiddenFunc};options.ajax={type:'post',data:{objid:objid,field:field}};windowModal(title,ulink('cpattern/field'),options)},process_paste:function(){var $_o=this;$('body').off('click','#window_process_paste').on('click','#window_process_paste',function(){ajaxOpen({type:'get',dataType:'json',url:ulink('cpattern/clone_process?op=paste'),success:function(data){if(data.code==1){$_o.cpProcess.add(data.data);toastr.success(data.msg)}else{toastr.error(data.msg)}}})})},add_request_header:function(name,val){var $_o=this;name=name?name:'';val=val?val:'';var tr=$_o.clone_tpl('#coll_tpl_request_headers');tr.find('[name="config[request_headers][custom_names][]"]').val(name);tr.find('[name="config[request_headers][custom_vals][]"]').val(val);$($_o.formid+' #coll_pattern_request_headers table.c-p-request-headers tbody').append(tr)},add_request_header_img:function(name,val){var $_o=this;name=name?name:'';val=val?val:'';var tr=$_o.clone_tpl('#coll_tpl_request_headers_img');tr.find('[name="config[request_headers][img_names][]"]').val(name);tr.find('[name="config[request_headers][img_vals][]"]').val(val);$($_o.formid+' #coll_pattern_request_headers table.c-p-request-headers-img tbody').append(tr)},get_field_names:function(sortField){var fields=new Array();var trs=$(this.formid+' #coll_pattern_field .c-p-field-list').find('tr[id^="field_"]');if(sortField){var fieldsNormal=new Array();var fieldsExtract=new Array();var fieldsMerge=new Array();trs.each(function(){var fmodule=$(this).find('.field-module').attr('data-val');var fname=$(this).find('.field-name').attr('data-val');if(fname){if(fmodule=='extract'){fieldsExtract.push(fname)}else if(fmodule=='merge'){fieldsMerge.push(fname)}else{fieldsNormal.push(fname)}}});for(var i in fieldsNormal){fields.push(fieldsNormal[i])} +var options={hidden_func:hiddenFunc};options.ajax={type:'post',data:{objid:objid,field:field}};windowModal(title,ulink('cpattern/field'),options)},process_paste:function(){var $_o=this;$('body').off('click','#window_process_paste').on('click','#window_process_paste',function(){ajaxOpen({type:'get',dataType:'json',url:ulink('cpattern/clone_process?op=paste'),success:function(data){if(data.code==1){$_o.cpProcess.add(data.data);toastr.success(data.msg)}else{toastr.error(data.msg)}}})})},add_request_header:function(name,val){var $_o=this;name=name?name:'';val=val?val:'';var tr=$_o.clone_tpl('#coll_tpl_request_headers');tr.find('[name="config[request_headers][custom_names][]"]').val(name);tr.find('[name="config[request_headers][custom_vals][]"]').val(val);$($_o.formid+' #coll_pattern_request_headers .c-p-request-headers tbody').append(tr)},add_request_header_img:function(name,val){var $_o=this;name=name?name:'';val=val?val:'';var tr=$_o.clone_tpl('#coll_tpl_request_headers_img');tr.find('[name="config[request_headers][img_names][]"]').val(name);tr.find('[name="config[request_headers][img_vals][]"]').val(val);$($_o.formid+' #coll_pattern_request_headers .c-p-request-headers-img tbody').append(tr)},get_field_names:function(sortField){var fields=new Array();var trs=$(this.formid+' #coll_pattern_field .c-p-field-list').find('tr[id^="field_"]');if(sortField){var fieldsNormal=new Array();var fieldsExtract=new Array();var fieldsMerge=new Array();trs.each(function(){var fmodule=$(this).find('.field-module').attr('data-val');var fname=$(this).find('.field-name').attr('data-val');if(fname){if(fmodule=='extract'){fieldsExtract.push(fname)}else if(fmodule=='merge'){fieldsMerge.push(fname)}else{fieldsNormal.push(fname)}}});for(var i in fieldsNormal){fields.push(fieldsNormal[i])} for(var i in fieldsExtract){fields.push(fieldsExtract[i])} for(var i in fieldsMerge){fields.push(fieldsMerge[i])}}else{trs.each(function(){var fname=$(this).find('.field-name').attr('data-val');if(fname){fields.push(fname)}})} return fields},rule_module_slt:function(curObj){curObj=$(curObj);var module=curObj.val();if(curObj.attr('data-module-input')){var ipt=$('[name="'+curObj.attr('data-module-input')+'"]');ipt.attr('data-val-'+module,ipt.val())} @@ -95,7 +83,7 @@ var nullTips='';if(sourceIsUrl){nullTips='起始页已设为内容页'} for(var asi in allSigns){var pageSigns=allSigns[asi]?allSigns[asi]:{};var pageName=pageSigns.name;var signs=pageSigns.signs;signs=signs?signs:{};html+=''+pageName+'';var signKeys=['area','url','content'];for(var ski in signKeys){html+='';var signKey=signKeys[ski];var keySigns=signs[signKey];if(keySigns&&keySigns.length>0){var keyGlobalSigns=signs[signKey+'_global'];for(var ksi in keySigns){var sign=keySigns[ksi];var signHtml=htmlspecialchars(sign);if(keyGlobalSigns&&keyGlobalSigns.indexOf(sign)>-1){var color=valSigns.indexOf(sign)>-1?'color:green;':'';html+=''+signHtml+''}else{html+=''+signHtml+''}}}else{html+=''} html+=''} html+=''} -if(dropdownMenu.length>0){var signsObj=$_o.clone_tpl('#coll_tpl_page_signs');$(dropdownMenu).append(signsObj);$(dropdownMenu).find('table tbody').html(html);$(dropdownMenu).find('a[data-val]').bind('click',function(){if(iptObj){insertAtCaret(iptObj,$(this).attr('data-val'))}});var sortObj=$(dropdownMenu).find('th.sorting');sortObj.removeClass('sorting');sortObj.addClass(pageSort=='asc'?'sorting_asc':'sorting_desc');sortObj.attr('title','页面'+(pageSort=='asc'?'升序':'倒序')+'排列');sortObj.bind('click',function(){ajaxOpen({type:'get',dataType:'json',url:ulink("cpattern/page_signs_sort"),success:function(data){toastr.success(data.msg)}})})}}}})},field_is_loop:function(){var hasLoop=$(this.formid+' #coll_pattern_field .c-p-field-list').find('.field-module[data-is-loop]');if(hasLoop&&hasLoop.length>0){hasLoop=1}else{hasLoop=''} +if(dropdownMenu.length>0){var signsObj=$_o.clone_tpl('#coll_tpl_page_signs');$(dropdownMenu).append(signsObj);$(dropdownMenu).find('table tbody').html(html);$(dropdownMenu).find('a[data-val]').bind('click',function(){if(iptObj){insertAtCaret(iptObj,$(this).attr('data-val'))}});var sortObj=$(dropdownMenu).find('th.sorting');sortObj.removeClass('sorting');sortObj.addClass(pageSort=='asc'?'sorting_asc':'sorting_desc');sortObj.attr('title','页面'+(pageSort=='asc'?'升序':'降序')+'排列');sortObj.bind('click',function(){ajaxOpen({type:'get',dataType:'json',url:ulink("cpattern/page_signs_sort"),success:function(data){toastr.success(data.msg)}})})}}}})},field_is_loop:function(){var hasLoop=$(this.formid+' #coll_pattern_field .c-p-field-list').find('.field-module[data-is-loop]');if(hasLoop&&hasLoop.length>0){hasLoop=1}else{hasLoop=''} return hasLoop}} function CpFrontUrl(cpClass){this.$_cp=cpClass;this.formObj='#form_front_url'} CpFrontUrl.prototype={constructor:CpFrontUrl,init:function(front_url){var $_o=this;$($_o.formObj).bind('submit',function(){$_o.add_sub();return!1});$_o.$_cp.init_page('front_url');$($_o.formObj).on('click','.c-p-url-page-signs .btn-page-signs',function(){$_o.$_cp.parent_page_signs(this)});if(front_url){var loadParams=['name','url'];for(var i in loadParams){if(front_url[loadParams[i]]){$($_o.formObj+' [name="front_url['+loadParams[i]+']"]').val(front_url[loadParams[i]])}} @@ -119,10 +107,46 @@ if(objid){objEle=$($_o.$_cp.formid+' #'+objid)}else{objEle=$_o.$_cp.clone_tpl('# objEle.find('.name').attr('data-val',relation_url.name).text(relation_url.name);objEle.find('.page').text(relationPage);objEle.find('[name="config[relation_urls][]"]').val(encode_json2urlbase(relation_url));objEle.find('.signs').val(pageSigns.join(' '))},add_sub:function(){var $_o=this;var objid=$($_o.formObj+' input[name="objid"]').val();var checkName=!0;if(objid){var name=$($_o.$_cp.formid+' #'+objid).find('.name').attr('data-val');if(name==$($_o.formObj+' [name="relation_url[name]"]').val()){checkName=!1}} if(checkName){var hasName=!1;$('#c_p_relation_urls [id^="relation_url_"] .name').each(function(){if($($_o.formObj+' [name="relation_url[name]"]').val()==$(this).attr('data-val')){hasName=!0;return!1}});if(hasName){toastr.error('该名称已存在!');return!1}} ajaxOpen({type:'POST',dataType:'json',url:$($_o.formObj).attr('action'),data:$($_o.formObj).serialize(),success:function(data){if(data.code==1){$('#myModal').modal('hide');data=data.data;if(data){$_o.add(data.objid,data.relation_url)}}else{toastr.error(data.msg)}},error:function(data){toastr.error(data)}});return!1}} +function CpUrlWeb(cpClass){this.$_cp=cpClass} +CpUrlWeb.prototype={constructor:CpUrlWeb,page_init:function(pageType){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!boxId||!namePre){return} +var boxUrlWebId=boxId+'_web';$(boxUrlWebId+' [name="'+namePre+'[url_web][open]"]').bind('click',function(){if($(this).val()==1){$(boxUrlWebId+'_open').show()}else{$(boxUrlWebId+'_open').hide()} +$_o.def_config_use_url_web(pageType)});inputSelectCustom(boxUrlWebId+' select[name="'+namePre+'[url_web][charset]"]',namePre+'[url_web][charset_custom]');inputSelectCustom(boxUrlWebId+' select[name="'+namePre+'[url_web][encode]"]',namePre+'[url_web][encode_custom]');$_o.def_config_charset(pageType);$_o.def_config_encode(pageType);$(boxUrlWebId+' select[name="'+namePre+'[url_web][form_method]"]').bind('change',function(){var obj=$(boxUrlWebId+' .c-p-url-web-content-type');if($(this).val()=='post'){obj.show()}else{obj.hide()}});$_o.def_config_header_global(pageType);$(boxUrlWebId+' .add-url-web-form').bind('click',function(){$_o.add_page_url_web(pageType,'form','','')});$(boxUrlWebId+' .c-p-url-web-form').on('click','.delete-url-web-form',function(){$(this).parents('tr').eq(0).remove()});$(boxUrlWebId+' .add-url-web-header').bind('click',function(){$_o.add_page_url_web(pageType,'header','','')});$(boxUrlWebId+' .c-p-url-web-header').on('click','.delete-url-web-header',function(){$(this).parents('tr').eq(0).remove()})},page_load:function(pageType,urlWebConfig){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!formId||!boxId||!namePre){return} +if(!isObject(urlWebConfig)){urlWebConfig={}} +var urlWebNamePre=namePre+'[url_web]';urlWebConfig.open=toInt(urlWebConfig.open);$(formId+' [name="'+urlWebNamePre+'[open]"][value="'+urlWebConfig.open+'"]').trigger('click');if(urlWebConfig.open>0){showPanelCollapse(boxId+'_web')} +if(urlWebConfig.charset){$(formId+' select[name="'+urlWebNamePre+'[charset]"]').val(urlWebConfig.charset).trigger('change')} +if(urlWebConfig.charset_custom){$(formId+' [name="'+urlWebNamePre+'[charset_custom]"]').val(urlWebConfig.charset_custom)} +if(urlWebConfig.encode){$(formId+' select[name="'+urlWebNamePre+'[encode]"]').val(urlWebConfig.encode).trigger('change')} +if(urlWebConfig.encode_custom){$(formId+' [name="'+urlWebNamePre+'[encode_custom]"]').val(urlWebConfig.encode_custom)} +if(urlWebConfig.form_method){$(formId+' [name="'+urlWebNamePre+'[form_method]"]').val(urlWebConfig.form_method).trigger('change')} +if(urlWebConfig.content_type){$(formId+' [name="'+urlWebNamePre+'[content_type]"]').val(urlWebConfig.content_type)} +if(urlWebConfig.form_names){var urlWebFormVals=urlWebConfig.form_vals?urlWebConfig.form_vals:{};for(var i in urlWebConfig.form_names){$_o.add_page_url_web(pageType,'form',urlWebConfig.form_names[i],urlWebFormVals[i])}} +if(urlWebConfig.header_global){$(formId+' [name="'+urlWebNamePre+'[header_global]"][value="'+urlWebConfig.header_global+'"]').prop('checked',!0)} +if(urlWebConfig.header_names){var urlWebHeaderVals=urlWebConfig.header_vals?urlWebConfig.header_vals:{};for(var i in urlWebConfig.header_names){$_o.add_page_url_web(pageType,'header',urlWebConfig.header_names[i],urlWebHeaderVals[i])}}},add_page_url_web:function(pageType,type,name,val){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!boxId||!namePre||!type){return} +boxId+='_web';name=name?name:'';val=val?val:'';var tr=$_o.$_cp.clone_tpl('#coll_tpl_url_web_'+type,namePre);tr.find('[name="'+namePre+'[url_web]['+type+'_names][]"]').val(name);tr.find('[name="'+namePre+'[url_web]['+type+'_vals][]"]').val(val);tr.find('.c-p-url-page-signs').each(function(){$(this).attr('data-page-type',pageType).attr('data-input-name',namePre+$(this).attr('data-input-name'))});$(boxId).find('.c-p-url-web-'+type+' tbody').append(tr)},def_config_charset:function(pageType){var $_o=this;var val=$($_o.$_cp.formid+' [name="config[charset]"]').val();var formid=$_o.$_cp.formid;if(pageType){formid=$_o.$_cp.get_page_vars(pageType,'formId')} +if(!val||val=='auto'){val='自动检测'}else if(val=='custom'){if($_o.$_cp.page_is_list(pageType)){val=$($_o.$_cp.formid+' [name="config[charset_custom]"]').val();val=val?val:''}else{val=''} +val='自定义'+(val?('»'+htmlspecialchars(val)):'')} +$(formid).find('.def-config-charset').html('默认:'+val)},def_config_encode:function(pageType){var $_o=this;var val=$($_o.$_cp.formid+' [name="config[encode]"]').val();var formid=$_o.$_cp.formid;if(pageType){formid=$_o.$_cp.get_page_vars(pageType,'formId')} +if(!val){val='系统默认'}else if(val=='custom'){if($_o.$_cp.page_is_list(pageType)){val=$($_o.$_cp.formid+' [name="config[encode_custom]"]').val();val=val?val:''}else{val=''} +val='自定义'+(val?('»'+htmlspecialchars(val)):'')} +$(formid).find('.def-config-encode').html('默认:'+val)},def_config_header_global:function(pageType){var $_o=this;var val=$($_o.$_cp.formid+' [name="config[request_headers][open]"]:checked').val();val=toInt(val);var formid=$_o.$_cp.formid;if(pageType){formid=$_o.$_cp.get_page_vars(pageType,'formId')} +$(formid).find('.def-config-request-headers-open').html(val>0?'是':'否')},def_config_use_url_web:function(pageType){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!formId||!boxId||!namePre){return} +var urlWebOpen=$(formId).find('[name="'+namePre+'[url_web][open]"]:checked').val();urlWebOpen=toInt(urlWebOpen);$(boxId+'_pagination').find('.def-config-url-web-open').html(urlWebOpen>0?'是':'否')}} +function CpRenderer(cpClass){this.$_cp=cpClass} +CpRenderer.prototype={constructor:CpRenderer,page_init:function(pageType){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!boxId||!namePre){return} +$_o.def_config_renderer_open(pageType);var rdBoxId=boxId+'_renderer';$(rdBoxId+' .add-url-renderer').bind('click',function(){$_o.add(pageType)});$(rdBoxId+' [name="'+namePre+'[renderer][open]"]').bind('click',function(){$_o.def_config_use_renderer(pageType)});$(rdBoxId+' .c-p-url-renderer-list').on('change','select[name="'+namePre+'[renderer][types][]"]',function(){var type=$(this).val();var tr=$(this).parents('tr').eq(0);var types={'wait_time':{content:1,content_tips:'输入数字'},'scroll_top':{content:1,content_tips:'输入数字'},'click':{element:1},'val':{element:1,content:1,content_tips:'输入值'}};var tdTypeCols=3;tr.find('.td-renderer-element,.td-renderer-content').hide();tr.find('input[name="'+namePre+'[renderer][contents][]"]').attr('placeholder','');var curType=types[type];if(isObject(curType)){if(curType.element){tr.find('.td-renderer-element').show();tdTypeCols-=1} +if(curType.content){tr.find('.td-renderer-content').show();tdTypeCols-=1;if(curType.content_tips){tr.find('input[name="'+namePre+'[renderer][contents][]"]').attr('placeholder',curType.content_tips)}}} +tr.find('.td-renderer-type').attr('colspan',tdTypeCols)});$(rdBoxId+' .c-p-url-renderer-list').on('click','.delete-url-renderer',function(){$(this).parents('tr').eq(0).remove()});eleExchange(rdBoxId+' .c-p-url-renderer-list','.icon-drag-move','tr')},page_load:function(pageType,rdConfig){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!boxId||!namePre){return} +var rdBoxId=boxId+'_renderer';if(isObject(rdConfig)){$(rdBoxId).find('[name="'+namePre+'[renderer][open]"][value="'+rdConfig.open+'"]').prop('checked',!0).trigger('click');var rdTypes=isObject(rdConfig.types)?rdConfig.types:[];var rdElements=isObject(rdConfig.elements)?rdConfig.elements:[];var rdContents=isObject(rdConfig.contents)?rdConfig.contents:[];var showPanel=!1;var openVal=rdConfig.open=='y'?true:!1;var openDef=$($_o.$_cp.formid+' [name="config[page_render]"]:checked').val();openDef=toInt(openDef);openDef=openDef>0?true:!1;if(rdTypes&&rdTypes.length>0){for(var i in rdTypes){$_o.add(pageType,rdTypes[i],rdElements[i],rdContents[i])} +if((!rdConfig.open&&openDef)||openVal){showPanel=!0}}else{if(rdConfig.open){if(openVal!=openDef){showPanel=!0}}} +if(showPanel){showPanelCollapse(rdBoxId)}}},add:function(pageType,rdType,rdElement,rdContent){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!boxId||!namePre){return} +var rdBoxId=boxId+'_renderer';var tr=$_o.$_cp.clone_tpl('#coll_tpl_url_renderer',namePre);if(rdType){tr.find('[name*="[renderer][types]"]').val(rdType);tr.find('[name*="[renderer][elements]"]').val(rdElement);tr.find('[name*="[renderer][contents]"]').val(rdContent)} +tr.find('.c-p-url-page-signs').each(function(){$(this).attr('data-page-type',pageType).attr('data-input-name',namePre+$(this).attr('data-input-name'))});$(rdBoxId+' .c-p-url-renderer-list table').append(tr);if(rdType){$(rdBoxId+' .c-p-url-renderer-list').find('[name*="[renderer][types]"]:last-child').trigger('change')}},def_config_renderer_open:function(pageType){var $_o=this;var val=$($_o.$_cp.formid+' [name="config[page_render]"]:checked').val();val=toInt(val);var formid=$_o.$_cp.formid;if(pageType){formid=$_o.$_cp.get_page_vars(pageType,'formId')} +$(formid).find('.def-config-page-render').html(val>0?'是':'否')},def_config_use_renderer:function(pageType){var $_o=this;var pageVars=$_o.$_cp.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!formId||!boxId||!namePre){return} +var defConfigObj=$(boxId+'_pagination').find('.def-config-renderer-open');var rendererOpen=$(formId).find('[name="'+namePre+'[renderer][open]"]:checked').val();if(rendererOpen){defConfigObj.html(rendererOpen=='y'?'是':'否').show();defConfigObj.parent().find('.def-config-page-render').hide()}else{defConfigObj.parent().find('.def-config-page-render').show();defConfigObj.hide()}},} function CpPagination(cpClass){this.$_cp=cpClass} CpPagination.prototype={constructor:CpPagination,page_init:function(pageType){var $_o=this;if(pageType=='source_url'||pageType=='level_url'||pageType=='url'){var pageVars=$_o.$_cp.get_page_vars(pageType);var formId=pageVars.formId;var boxId=pageVars.boxId;var namePre=pageVars.namePre;if(!formId||!boxId||!namePre){return} var pnBoxId=boxId+'_pagination';var pnNamePre=namePre+'[pagination]';$(formId+' select[name="'+pnNamePre+'[area_module]"],select[name="'+pnNamePre+'[url_rule_module]"]').bind('click',function(){$_o.$_cp.rule_module_slt(this)});$(pnBoxId+' [name="'+pnNamePre+'[open]"]').bind('click',function(){if($(this).val()==1){$(pnBoxId+'_open').show()}else{$(pnBoxId+'_open').hide()}});if(pageType=='url'){$(pnBoxId).on('click','.add-url-pagination-field',function(){var url=ulink("cpattern/pagination_field?is_loop=_is_loop_",{'_is_loop_':$_o.$_cp.field_is_loop()});windowModal('分页字段',url)});$(pnBoxId+' .c-p-url-pagination-fields').on('click','.field',function(){var parent=$(this).parents('[id^="pagination_field_"]').eq(0);var objid=parent.attr('id');var pnField=parent.find('[name="'+pnNamePre+'[fields][]"]').val();var url=ulink("cpattern/pagination_field?objid=_objid_&pagination_field=_pnfield_&is_loop=_is_loop_",{'_objid_':objid,'_pnfield_':pnField,'_is_loop_':$_o.$_cp.field_is_loop()});windowModal('分页字段',url)});$(pnBoxId+' .c-p-url-pagination-fields').on('click','.delete',function(){var parent=$(this).parents('[id^="pagination_field_"]').eq(0);confirmRight('确定删除?',function(){parent.remove()})})}}},page_load:function(pageType,pnConfig){var $_o=this;if(pageType=='source_url'||pageType=='level_url'||pageType=='url'){var pageVars=$_o.$_cp.get_page_vars(pageType);if(!pageVars.boxId||!pageVars.namePre){return} -var pnBoxId=pageVars.boxId+'_pagination';var pnNamePre=pageVars.namePre+'[pagination]';if(isObject(pnConfig)){$_o.$_cp.load_page_rule(pageType,pnConfig,!0);$(pnBoxId+' [name="'+pnNamePre+'[max]"]').val(toInt(pnConfig.max));pnConfig.open=toInt(pnConfig.open);if(pnConfig.open){$(pnBoxId+' [name="'+pnNamePre+'[open]"][value="'+pnConfig.open+'"]').trigger('click');if(pnConfig.open>0){showPanelCollapse(pnBoxId)}} +var pnBoxId=pageVars.boxId+'_pagination';var pnNamePre=pageVars.namePre+'[pagination]';if(isObject(pnConfig)){$_o.$_cp.load_page_rule(pageType,pnConfig,!0);$(pnBoxId+' [name="'+pnNamePre+'[max]"]').val(toInt(pnConfig.max));$(pnBoxId+' [name="'+pnNamePre+'[use_url_web]"][value="'+pnConfig.use_url_web+'"]').prop('checked',!0);$(pnBoxId+' [name="'+pnNamePre+'[use_renderer]"][value="'+pnConfig.use_renderer+'"]').prop('checked',!0);pnConfig.open=toInt(pnConfig.open);if(pnConfig.open){$(pnBoxId+' [name="'+pnNamePre+'[open]"][value="'+pnConfig.open+'"]').trigger('click');if(pnConfig.open>0){showPanelCollapse(pnBoxId)}} if(pageType=='url'){if(pnConfig.fields){for(var i in pnConfig.fields){$_o.field_op(pageType,'add',{pagination_field:pnConfig.fields[i]})}}}}}},field_op:function(pageType,op,params){var $_o=this;if(pageType!='url'){return} params=params?params:{};var formObj=params.formObj?params.formObj:'#form_pagination_field';var pageVars=$_o.$_cp.get_page_vars(pageType);if(!pageVars.boxId||!pageVars.namePre){return} var pnBoxId=pageVars.boxId+'_pagination';var pnNamePre=pageVars.namePre+'[pagination]';if(op=='init'){var fieldNames=$_o.$_cp.get_field_names();var fieldOptions='';for(var i in fieldNames){fieldOptions+=''} @@ -130,7 +154,7 @@ $(formObj+' select[name="pagination_field[field]"]').html(fieldOptions);$(formOb $('#'+objid).html(tpl)}else if(op=='add_sub'){var objid=$(formObj+' input[name="objid"]').val();var checkField=!0;if(objid){var field=$(pnBoxId+' #'+objid).find('.field').attr('data-field');if(field==$(formObj+' select[name="pagination_field[field]"]').val()){checkField=!1}} if(checkField){var hasField=!1;var fieldList=new Array();$(pnBoxId+' .c-p-url-pagination-fields [id^="pagination_field_"] .field').each(function(){fieldList.push($(this).attr('data-field'))});for(var i in fieldList){if($(formObj+' select[name="pagination_field[field]"]').val()==fieldList[i]){hasField=!0;break}} if(hasField){toastr.error('该字段已存在!');return!1}} -ajaxOpen({type:'POST',dataType:'json',url:$(formObj).attr('action'),data:$(formObj).serialize(),success:function(data){if(data.code==1){$('#myModal').modal('hide');$_o.field_op(pageType,'add',data.data)}else{toastr.error(data.msg)}},error:function(data){toastr.error(data)}});return!1}},} +ajaxOpen({type:'POST',dataType:'json',url:$(formObj).attr('action'),data:$(formObj).serialize(),success:function(data){if(data.code==1){$('#myModal').modal('hide');$_o.field_op(pageType,'add',data.data)}else{toastr.error(data.msg)}},error:function(data){toastr.error(data)}});return!1}}} function CpField(cpClass){this.$_cp=cpClass;this.formObj='#form_field'} CpField.prototype={constructor:CpField,init:function(fieldData){var $_o=this;var sourceOptions=$_o.$_cp.page_source_options(!1);if(sourceOptions){$($_o.formObj+' select[name="field[source]"]').html(sourceOptions)} $($_o.formObj+' select[name="field[source]"]').bind('change',function(){var fsource=$(this).val();fsource=fsource.split(':');var pageType=fsource[0]?fsource[0]:'url';var pageName=fsource[1]?fsource[1]:'';var pageVars=$_o.$_cp.get_page_vars(pageType);var boxId=pageVars.boxId;var namePre=pageVars.namePre;var formId=pageVars.formId;var pageSigns=[];if(pageType=='source_url'||pageType=='url'){var urlConfig={};var noDefSign=!1;if(pageType=='source_url'){urlConfig.area='';urlConfig.url_rule='';noDefSign=!0}else{urlConfig.area=$(formId+' [name="'+namePre+'[area]"]').val();urlConfig.url_rule=$(formId+' [name="'+namePre+'[url_rule]"]').val()} @@ -150,7 +174,7 @@ if(objid){var eleObj=$($_o.$_cp.formid+' #'+objid);eleObj.find('.field-name').at eleObj.find('input[name="config[field_list][]"]').val(encode_json2urlbase(fieldData))}else{var ptitle='';if(processData){ptitle=[];for(var i in processData){ptitle.push(window.tpl_lang['process_module_'+processData[i].module]+(processData[i].title?(':'+processData[i].title):''))} ptitle=ptitle.join(' / ')} var html=$_o.$_cp.clone_tpl('#coll_tpl_field');html.attr('id','field_'+generateUUID());html.find('.field-name').attr('data-val',fieldData.name).text(fieldData.name);html.find('.field-source').attr('data-val',fieldData.source).text(fieldSource);html.find('.field-module').attr('data-val',fieldData.module).text(window.tpl_lang['field_module_'+fieldData.module]+isLoop);if(isLoop){html.find('.field-module').attr('data-is-loop',1)}else{html.find('.field-module').removeAttr('data-is-loop')} -html.find('[name="config[field_list][]"]').val(encode_json2urlbase(fieldData));html.find('[name="config[field_process][]"]').val(processData?encode_json2urlbase(processData):'');if(processData){html.find('.field-process').addClass('exist-process')} +html.find('[name="config[field_list][]"]').val(encode_json2urlbase(fieldData));html.find('[name="config[field_process][]"]').val(processData?encode_json2urlbase(processData):'');if(processData&&ptitle){html.find('.field-process').addClass('exist-process')} html.find('.field-process').attr('title',ptitle);html.find('[name="config[field_title]"]').val(fieldData.name);$($_o.$_cp.formid+' #coll_pattern_field .c-p-field-list tbody').append(html)}},add_sub:function(){var $_o=this;var objid=$($_o.formObj+' input[name="objid"]').val();var checkName=!0;if(objid){var fname=$($_o.$_cp.formid+' #'+objid).find('.field-name').attr('data-val');if(fname==$($_o.formObj+' input[name="field[name]"]').val()){checkName=!1}} if(checkName){var hasName=!1;var fieldNames=$_o.$_cp.get_field_names();for(var i in fieldNames){if($($_o.formObj+' input[name="field[name]"]').val()==fieldNames[i]){hasName=!0;break}} if(hasName){toastr.error('字段名称已存在!');return!1}} @@ -161,11 +185,12 @@ CpProcess.prototype={constructor:CpProcess,init:function(processData,isCommon,is if($($_o.processForm).is('form')){$($_o.processForm).bind('submit',function(){$_o.add_sub();return!1})} $($_o.processBox+' .process-add').bind('click',function(){var module=$($_o.processBox+' select[name="process[module]"]').val();$_o.add({'add_new':1,'module':module})});if($($_o.processForm).prop('inited')==1){return!0} $($_o.processForm).on('click','.p-m-html-tags a[data-val]',function(){var tag=$(this).attr('data-val');var moduleHtml=$(this).parents('.p-m-html-tags').eq(0).attr('module-html');var tagsObj=$(this).parents('section').eq(0).find('input[data-process="html:'+moduleHtml+'"]');var tags=tagsObj.val()+','+tag;tags=tags.replace(/(^,+)|(,+$)/,'');tagsObj.val(tags)});$($_o.processForm).on('change','[data-process="insert:insert_loc"]',function(){var helpEle=$(this).siblings('.help-block');if($(this).val()=='rand'){helpEle.show()}else{helpEle.hide()}});inputSelectCustom(null,null,{box:$_o.processForm,slt:'[data-process="translate:translate_from"]',ipt:'[data-process="translate:translate_from_custom"]'});inputSelectCustom(null,null,{box:$_o.processForm,slt:'[data-process="translate:translate_to"]',ipt:'[data-process="translate:translate_to_custom"]'});$_o.txt_insert_field($_o.processForm,'.p-m-func-field',function(sltObj){return $(sltObj).parents('section').eq(0).find('[data-process="func:func_param"]')});$($_o.processForm).on('click','.p-m-if-add',function(){var ifTable=$(this).parents('section').eq(0).find('.p-m-if-table');ifTable.append(''+ifTable.attr('data-tpl')+'')});$($_o.processForm).on('click','.p-m-if-del',function(){var tr=$(this).parents('tr').eq(0);confirmRight('确定删除?',function(){tr.remove()})});$($_o.processForm).on('change','[data-process="if:if_cond:"]',function(){var ifCond=$(this).val();var ifTr=$(this).parents('tr').eq(0);var ifTd=ifTr.find('.p-m-if-val').eq(0);var ifVal=ifTd.find('[data-process="if:if_val:"]').eq(0);var ifValInfo={name:ifVal.attr('name'),val:ifVal.val(),process:ifVal.attr('data-process')};var ifValType='def';if(ifCond=='func'){ifValType='func'}else if(ifCond.indexOf('time_')>-1){ifValType='time'} -ifTd.find('.p-m-if-val-def,.p-m-if-val-time,.p-m-if-val-func').hide();ifTd.find('[data-process="if:if_val:"]').removeAttr('name').removeAttr('data-process');var ifValBox=ifTd.find('.p-m-if-val-'+ifValType);var ifValEle=ifValBox.find('.p-m-if-val-ele');ifValEle.attr('name',ifValInfo.name).attr('data-process',ifValInfo.process).val(ifValInfo.val);ifValBox.show();if(ifValType=='func'){$_o.load_if_func(ifTd,null)}});$($_o.processForm).on('change','.p-m-if-val-time-date select',function(){$(this).parents('tr').eq(0).find('[data-process="if:if_val:"]').val($(this).val())});$($_o.processForm).on('click','.p-m-if-val-func-info',function(){pluginFuncTips('processIf')});$_o.txt_insert_field($_o.processForm,'.p-m-if-val-func-field select',function(sltObj){return $(sltObj).parents('td').eq(0).find('[data-process="if:if_val:"]')});$($_o.processForm).on('click','.p-m-if-info',function(){var tips='

执行顺序:从上至下判断,逻辑符“并且”的优先级高于“或者”

'+'

例如(字母表示条件):

'+'

a && b || c && d && e || f || g && h && i && j 等同于

'+'

(a && b) || (c && d && e) || f || (g && h && i && j)

'+'

括号中的条件都为真时才是真否则为假,整条语句中任意一个括号的结果为真最终结果为真,都为假最终结果为假

';confirmRight({msg:tips,yes:'确定',width:500,textAlign:'left'})});$($_o.processForm).on('click','.p-m-api-add',function(){var apiTable=$(this).parents('section').eq(0).find('.p-m-api-table');apiTable.append(''+apiTable.attr('data-tpl')+'')});$($_o.processForm).on('click','.p-m-api-del',function(){var tr=$(this).parents('tr').eq(0);tr.remove()});inputSelectCustom(null,null,{box:$_o.processForm,slt:'[data-process="api:api_charset"]',ipt:'[data-process="api:api_charset_custom"]'});$($_o.processForm).on('change','[data-process="api:api_params:val:"],[data-process="api:api_headers:val:"]',function(){var isHeader=!1;if($(this).attr('data-process')=='api:api_headers:val:'){isHeader=!0} +ifTd.find('.p-m-if-val-def,.p-m-if-val-time,.p-m-if-val-func').hide();ifTd.find('[data-process="if:if_val:"]').removeAttr('name').removeAttr('data-process');var ifValBox=ifTd.find('.p-m-if-val-'+ifValType);var ifValEle=ifValBox.find('.p-m-if-val-ele');ifValEle.attr('name',ifValInfo.name).attr('data-process',ifValInfo.process).val(ifValInfo.val);ifValBox.show();if(ifValType=='func'){$_o.load_if_func(ifTd,null)}});$($_o.processForm).on('change','.p-m-if-val-time-date select',function(){$(this).parents('tr').eq(0).find('[data-process="if:if_val:"]').val($(this).val())});$($_o.processForm).on('click','.p-m-if-val-func-info',function(){tipsPluginFunc('processIf')});$_o.txt_insert_field($_o.processForm,'.p-m-if-val-func-field select',function(sltObj){return $(sltObj).parents('td').eq(0).find('[data-process="if:if_val:"]')});$($_o.processForm).on('click','.p-m-if-info',function(){var tips='

执行顺序:从上至下判断,逻辑符“并且”的优先级高于“或者”

'+'

例如(字母表示条件):

'+'

a && b || c && d && e || f || g && h && i && j 等同于

'+'

(a && b) || (c && d && e) || f || (g && h && i && j)

'+'

括号中的条件都为真时才是真否则为假,整条语句中任意一个括号的结果为真最终结果为真,都为假最终结果为假

';confirmRight({msg:tips,yes:'确定',width:500,textAlign:'left'})});$($_o.processForm).on('click','.p-m-api-add',function(){var apiTable=$(this).parents('section').eq(0).find('.p-m-api-table table');apiTable.find('tbody').append(''+apiTable.attr('data-tpl')+'')});$($_o.processForm).on('click','.p-m-api-del',function(){var tr=$(this).parents('tr').eq(0);tr.remove()});inputSelectCustom(null,null,{box:$_o.processForm,slt:'[data-process="api:api_charset"]',ipt:'[data-process="api:api_charset_custom"]'});inputSelectCustom(null,null,{box:$_o.processForm,slt:'[data-process="api:api_encode"]',ipt:'[data-process="api:api_encode_custom"]'});$($_o.processForm).on('change','[data-process="api:api_params:val:"],[data-process="api:api_headers:val:"]',function(){var isHeader=!1;if($(this).attr('data-process')=='api:api_headers:val:'){isHeader=!0} var val=$(this).val();var tdObj=$(this).parents('td').eq(0);var iptObj=tdObj.find('[data-process="api:'+(isHeader?'api_headers':'api_params')+':addon:"]');var sltObj=tdObj.find('.p-m-api-'+(isHeader?'header':'val')+'-field');iptObj.hide();sltObj.hide();if(val=='time'||val=='custom'){if(val=='time'){iptObj.attr('placeholder','默认格式:Y-m-d H:i:s')}else if(val=='custom'){iptObj.attr('placeholder','输入任何内容');sltObj.css('display','table-cell')} -iptObj.show()}});$_o.txt_insert_field($_o.processForm,'.p-m-api-val-field select',function(sltObj){return $(sltObj).parents('td').eq(0).find('[data-process="api:api_params:addon:"]')});$_o.txt_insert_field($_o.processForm,'.p-m-api-header-field select',function(sltObj){return $(sltObj).parents('td').eq(0).find('[data-process="api:api_headers:addon:"]')});$($_o.processForm).on('click','.p-m-api-header-add',function(){var apiHdTable=$(this).parents('section').eq(0).find('.p-m-api-header-table');apiHdTable.append(''+apiHdTable.attr('data-tpl')+'')});$($_o.processForm).on('click','.p-m-api-header-del',function(){var tr=$(this).parents('tr').eq(0);tr.remove()});$($_o.processForm).on('change','[data-process="api:api_type"]',function(){var obj=$(this).parents('section').eq(0).find('.p-m-api-content-type');if($(this).val()=='post'){obj.show()}else{obj.hide()}});$($_o.processForm).on('change','[data-process="api:api_json_arr"]',function(){var ipt=$(this).parent().find('[data-process="api:api_json_implode"]');if($(this).val()=='implode'){ipt.show()}else{ipt.hide()}});$($_o.processForm).on('click','.sign-wildcard',function(){var toObj=$(this).parent().siblings('[data-process="replace:replace_from"]');cpWildcard(toObj)});$($_o.processForm).on('click','.c-p-process-title',function(){var panelTitle=$(this).parents('.panel').eq(0).find('.panel-title-title');if(panelTitle.find('input').is(':visible')){panelTitle.find('*').show();panelTitle.find('input').hide()}else{panelTitle.find('*').hide();panelTitle.find('input').show()}});(function(processForm,processBox){$(processForm).on('click','.c-p-process-clone',function(){var panelObj=$(this).parents('.panel[data-name^="process"]').eq(0);var formEle=document.createElement('form');$(formEle).append(panelObj.clone());$(panelObj).find('[name^="process"]').each(function(index){var processEle=$(formEle).find('[name^="process"]').eq(index);if($(this).is('input:radio')||$(this).is('input:checkbox')){processEle.prop('checked',$(this).is(':checked'))}else{processEle.val($(this).val())}});confirmRight({msg:'拷贝或复制数据处理',yes:'复制',no:'拷贝',close:!0},function(){ajaxOpen({type:'POST',dataType:'json',url:ulink('cpattern/clone_process'),data:$(formEle).serialize(),success:function(data){if(data.code==1){$_o.processForm=processForm;$_o.processBox=processBox;$_o.add(data.data);toastr.success(data.msg)}}})},function(){ajaxOpen({type:'POST',dataType:'json',url:ulink('cpattern/clone_process?op=copy'),data:$(formEle).serialize(),success:function(data){if(data.code==1){toastr.success(data.msg)}}})})})})($_o.processForm,$_o.processBox);$($_o.processForm).on('click','.c-p-process-del',function(){$_o.del(this)});eleExchange($_o.processForm+' .c-p-process-accordion','.panel-title-ops .icon-drag-move','.panel');if(processData){for(var i in processData){if(processData[i]){$_o.add(processData[i])}}} -$($_o.processForm).prop('inited',1)},add_sub:function(){var $_o=this;ajaxOpen({type:'POST',dataType:'json',url:$($_o.processForm).attr('action'),data:$($_o.processForm).serialize(),success:function(data){if(data.code==1){$('#myModal').modal('hide');if(data.data&&data.data.objid){$($_o.$_cp.formid+' #'+data.data.objid).find('input[name="config[field_process][]"]').val(data.data.process_json?url_base64encode(data.data.process_json):'');if(data.data.process){var process=data.data.process;var ptitle=[];for(var i in process){ptitle.push(window.tpl_lang['process_module_'+process[i].module]+(process[i].title?(':'+process[i].title):''))} -ptitle=ptitle.join(' / ');$($_o.$_cp.formid+' #'+data.data.objid).find('.field-process').addClass('exist-process').attr('title',ptitle)}else{$($_o.$_cp.formid+' #'+data.data.objid).find('.field-process').removeClass('exist-process').attr('title','')}}}else{toastr.error(data.msg)}},error:function(data){toastr.error(data)}});return!1},del:function(obj){var $_o=this;confirmRight('是否删除?',function(){$(obj).parents('.panel').eq(0).remove()})},load_if_func:function(box,setVal){var $_o=this;loadPluginFunc({module:'processIf',boxObj:box,funcObj:'[data-process="if:if_addon:func:"]',paramObj:'[data-process="if:if_val:"]',funcVal:setVal,cache:!0})},add:function(params){var $_o=this;params=params?params:{};if(!params.module){toastr.error('请选择处理方式');return!1} +iptObj.show()}});$_o.txt_insert_field($_o.processForm,'.p-m-api-val-field select',function(sltObj){return $(sltObj).parents('td').eq(0).find('[data-process="api:api_params:addon:"]')});$_o.txt_insert_field($_o.processForm,'.p-m-api-header-field select',function(sltObj){return $(sltObj).parents('td').eq(0).find('[data-process="api:api_headers:addon:"]')});$($_o.processForm).on('click','.p-m-api-header-add',function(){var apiHdTable=$(this).parents('section').eq(0).find('.p-m-api-header-table table');apiHdTable.find('tbody').append(''+apiHdTable.attr('data-tpl')+'')});$($_o.processForm).on('click','.p-m-api-header-del',function(){var tr=$(this).parents('tr').eq(0);tr.remove()});$($_o.processForm).on('change','[data-process="api:api_type"]',function(){var obj=$(this).parents('section').eq(0).find('.p-m-api-content-type');if($(this).val()=='post'){obj.show()}else{obj.hide()}});$($_o.processForm).on('change','[data-process="api:api_json_arr"]',function(){var ipt=$(this).parent().find('[data-process="api:api_json_implode"]');if($(this).val()=='implode'){ipt.show()}else{ipt.hide()}});$($_o.processForm).on('click','.sign-wildcard',function(){var toObj=$(this).parent().siblings('[data-process="replace:replace_from"]');cpWildcard(toObj)});$($_o.processForm).on('click','.c-p-process-title',function(){var panelTitle=$(this).parents('.panel').eq(0).find('.panel-title-title');if(panelTitle.find('input').is(':visible')){panelTitle.find('*').show();panelTitle.find('input').hide()}else{panelTitle.find('*').hide();panelTitle.find('input').show()}});(function(processForm,processBox){$(processForm).on('click','.c-p-process-clone',function(){var panelObj=$(this).parents('.panel[data-name^="process"]').eq(0);var formEle=document.createElement('form');$(formEle).append(panelObj.clone());$(panelObj).find('[name^="process"]').each(function(index){var processEle=$(formEle).find('[name^="process"]').eq(index);if($(this).is('input:radio')||$(this).is('input:checkbox')){processEle.prop('checked',$(this).is(':checked'))}else{processEle.val($(this).val())}});confirmRight({msg:'拷贝或复制数据处理',yes:'复制',no:'拷贝',close:!0},function(){ajaxOpen({type:'POST',dataType:'json',url:ulink('cpattern/clone_process'),data:$(formEle).serialize(),success:function(data){if(data.code==1){$_o.processForm=processForm;$_o.processBox=processBox;$_o.add(data.data);toastr.success(data.msg)}}})},function(){ajaxOpen({type:'POST',dataType:'json',url:ulink('cpattern/clone_process?op=copy'),data:$(formEle).serialize(),success:function(data){if(data.code==1){toastr.success(data.msg)}}})})})})($_o.processForm,$_o.processBox);$($_o.processForm).on('click','.c-p-process-del',function(){$_o.del(this)});eleExchange($_o.processForm+' .c-p-process-accordion','.panel-title-ops .icon-drag-move','.panel');if(processData){for(var i in processData){if(processData[i]){$_o.add(processData[i])}}} +$($_o.processForm).prop('inited',1)},add_sub:function(){var $_o=this;ajaxOpen({type:'POST',dataType:'json',url:$($_o.processForm).attr('action'),data:$($_o.processForm).serialize(),success:function(data){if(data.code==1){$('#myModal').modal('hide');if(data.data&&data.data.objid){$($_o.$_cp.formid+' #'+data.data.objid).find('input[name="config[field_process][]"]').val(data.data.process_json?url_base64encode(data.data.process_json):'');var ptitle='';var processData=data.data.process;if(processData){ptitle=[];for(var i in processData){ptitle.push(window.tpl_lang['process_module_'+processData[i].module]+(processData[i].title?(':'+processData[i].title):''))} +ptitle=ptitle.join(' / ')} +if(processData&&ptitle){$($_o.$_cp.formid+' #'+data.data.objid).find('.field-process').addClass('exist-process').attr('title',ptitle)}else{$($_o.$_cp.formid+' #'+data.data.objid).find('.field-process').removeClass('exist-process').attr('title','')}}}else{toastr.error(data.msg)}},error:function(data){toastr.error(data)}});return!1},del:function(obj){var $_o=this;confirmRight('是否删除?',function(){$(obj).parents('.panel').eq(0).remove()})},load_if_func:function(box,setVal){var $_o=this;loadPluginFunc({module:'processIf',boxObj:box,funcObj:'[data-process="if:if_addon:func:"]',paramObj:'[data-process="if:if_val:"]',funcVal:setVal,cache:!0})},add:function(params){var $_o=this;params=params?params:{};if(!params.module){toastr.error('请选择处理方式');return!1} params.module=htmlspecialchars(params.module);params.title=params.title?htmlspecialchars(params.title):'';var parentid=$($_o.processForm+' .c-p-process-accordion').attr('id');if(!parentid){parentid='p_accordion_'+generateUUID();$($_o.processForm+' .c-p-process-accordion').attr('id',parentid)} var dataParent=parentid?('data-parent="#'+parentid+'"'):'';var moduleHtml=$($_o.processBox+' .c-p-process-module[module="'+params.module+'"]').html();if(params.module=='html'){moduleHtml=moduleHtml.replace(/p_m_html_allow/ig,'p_m_html_allow_'+generateUUID());moduleHtml=moduleHtml.replace(/p_m_html_filter/ig,'p_m_html_filter_'+generateUUID())} var processName='process[i_'+generateUUID()+']';moduleHtml=''+moduleHtml;var collapseId='p_collapse_'+generateUUID();var html=$_o.$_cp.clone_tpl('#coll_tpl_process');html.attr('data-name',processName);html.find('a[data-toggle="collapse"]').attr('data-parent','#'+parentid).attr('href','#'+collapseId);if(params.title){html.find('.panel-title > a[data-toggle="collapse"]').text(window.tpl_lang['process_module_'+params.module]+':');html.find('.panel-title-title > a[data-toggle="collapse"]').text(params.title).show()}else{html.find('.panel-title > a[data-toggle="collapse"]').text(window.tpl_lang['process_module_'+params.module]);html.find('.panel-title-title > a[data-toggle="collapse"]').hide()} @@ -175,7 +200,7 @@ if(eleName.length>=4){eleName[1]+=eleName[3]?('['+eleName[3]+']'):'[]'} $(this).attr('name',eleName[1])});if(params.module=='html'){$(curCollapse).find('[data-process="html:html_allow"]').val(params.html_allow?params.html_allow:'');$(curCollapse).find('[data-process="html:html_filter"]').val(params.html_filter?params.html_filter:'');if(params.html_filter){$(curCollapse).find('a[href^="#p_m_html_filter"]').tab('show')}}else if(params.module=='insert'){$(curCollapse).find('[data-process="insert:insert_loc"]').val(params.insert_loc?params.insert_loc:'').trigger('change');$(curCollapse).find('[data-process="insert:insert_txt"]').val(params.insert_txt?params.insert_txt:'')}else if(params.module=='replace'){$(curCollapse).find('[data-process="replace:replace_from"]').val(params.replace_from?params.replace_from:'');$(curCollapse).find('[data-process="replace:replace_to"]').val(params.replace_to?params.replace_to:'')}else if(params.module=='filter'){$(curCollapse).find('[data-process="filter:filter_list"]').val(params.filter_list?params.filter_list:'');$(curCollapse).find('[data-process="filter:filter_replace"]').val(params.filter_replace?params.filter_replace:'');$(curCollapse).find('[data-process="filter:filter_pass"][value="'+params.filter_pass+'"]').prop('checked',!0)}else if(params.module=='tool'){$(curCollapse).find('[data-process="tool:tool_list"]').attr('name',processName+'[tool_list][]');if(params.tool_list){for(var ti in params.tool_list){$(curCollapse).find('[data-process="tool:tool_list"][value="'+params.tool_list[ti]+'"]').prop('checked',!0)}}}else if(params.module=='translate'){$(curCollapse).find('[data-process="translate:translate_from"]').val(params.translate_from?params.translate_from:'').trigger('change');$(curCollapse).find('[data-process="translate:translate_to"]').val(params.translate_to?params.translate_to:'').trigger('change');$(curCollapse).find('[data-process="translate:translate_from_custom"]').val(params.translate_from_custom?params.translate_from_custom:'');$(curCollapse).find('[data-process="translate:translate_to_custom"]').val(params.translate_to_custom?params.translate_to_custom:'')}else if(params.module=='batch'){$(curCollapse).find('[data-process="batch:batch_list"]').val(params.batch_list?params.batch_list:'')}else if(params.module=='substr'){$(curCollapse).find('[data-process="substr:substr_len"]').val(params.substr_len?params.substr_len:'');$(curCollapse).find('[data-process="substr:substr_end"]').val(params.substr_end?params.substr_end:'')}else if(params.module=='func'){$(curCollapse).find('[data-process="func:func_param"]').val(params.func_param?params.func_param:'');loadPluginFunc({module:'process',boxObj:$(curCollapse),funcObj:'[data-process="func:func_name"]',paramObj:'[data-process="func:func_param"]',funcVal:params.func_name,cache:!0})}else if(params.module=='if'){var ifTrTpl=$(curCollapse).find('.p-m-if-table-tpl');var ifTable=$(curCollapse).find('.p-m-if-table');ifTable.attr('data-tpl',ifTrTpl.html());ifTrTpl.remove();if(params.if_type){$(curCollapse).find('[data-process="if:if_type"]').val(params.if_type)} if(params.if_logic&¶ms.if_cond&¶ms.if_val){params.if_addon=params.if_addon?params.if_addon:{};for(var i in params.if_logic){ifTable.find('tbody').append(''+ifTable.attr('data-tpl')+'');var curIfTr=ifTable.find('tr[data-if-id="'+i+'"]');curIfTr.find('[data-process="if:if_logic:"]').val(params.if_logic[i]);curIfTr.find('[data-process="if:if_cond:"]').val(params.if_cond[i]).trigger('change');curIfTr.find('[data-process="if:if_val:"]').val(params.if_val[i]);if(params.if_cond[i]=='func'){var ifFuncVal='';if(params.if_addon.func){ifFuncVal=params.if_addon.func[i]} $_o.load_if_func(curIfTr,ifFuncVal);if(params.if_addon.turn){curIfTr.find('[data-process="if:if_addon:turn:"]').val(params.if_addon.turn[i])}}}} -eleExchange(curCollapse+' .p-m-if-table','.icon-drag-move','tbody tr')}else if(params.module=='api'){var apiTrTpl=$(curCollapse).find('.p-m-api-table-tpl');var apiTable=$(curCollapse).find('.p-m-api-table');apiTable.attr('data-tpl',apiTrTpl.html());apiTrTpl.remove();var apiHdTrTpl=$(curCollapse).find('.p-m-api-header-table-tpl');var apiHdTable=$(curCollapse).find('.p-m-api-header-table');apiHdTable.attr('data-tpl',apiHdTrTpl.html());apiHdTrTpl.remove();$(curCollapse).find('[data-process="api:api_url"]').val(params.api_url?params.api_url:'');$(curCollapse).find('[data-process="api:api_type"]').val(params.api_type?params.api_type:'').trigger('change');$(curCollapse).find('[data-process="api:api_content_type"]').val(params.api_content_type?params.api_content_type:'');$(curCollapse).find('[data-process="api:api_charset"]').val(params.api_charset?params.api_charset:'').trigger('change');$(curCollapse).find('[data-process="api:api_charset_custom"]').val(params.api_charset_custom?params.api_charset_custom:'');if(params.api_params){params.api_params.name=params.api_params.name?params.api_params.name:{};params.api_params.val=params.api_params.val?params.api_params.val:{};params.api_params.addon=params.api_params.addon?params.api_params.addon:{};for(var i in params.api_params.name){var trId='p-m-api-param_'+generateUUID();var trTpl=''+apiTable.attr('data-tpl')+'';apiTable.find('tbody').append(trTpl);apiTable.find('#'+trId+' [data-process="api:api_params:name:"]').val(params.api_params.name[i]);apiTable.find('#'+trId+' [data-process="api:api_params:val:"]').val(params.api_params.val[i]?params.api_params.val[i]:'').trigger('change');apiTable.find('#'+trId+' [data-process="api:api_params:addon:"]').val(params.api_params.addon[i]?params.api_params.addon[i]:'')}} +eleExchange(curCollapse+' .p-m-if-table','.icon-drag-move','tbody tr')}else if(params.module=='api'){var apiTrTpl=$(curCollapse).find('.p-m-api-table-tpl');var apiTable=$(curCollapse).find('.p-m-api-table table');apiTable.attr('data-tpl',apiTrTpl.html());apiTrTpl.remove();var apiHdTrTpl=$(curCollapse).find('.p-m-api-header-table-tpl');var apiHdTable=$(curCollapse).find('.p-m-api-header-table table');apiHdTable.attr('data-tpl',apiHdTrTpl.html());apiHdTrTpl.remove();$(curCollapse).find('[data-process="api:api_url"]').val(params.api_url?params.api_url:'');$(curCollapse).find('[data-process="api:api_type"]').val(params.api_type?params.api_type:'').trigger('change');$(curCollapse).find('[data-process="api:api_content_type"]').val(params.api_content_type?params.api_content_type:'');$(curCollapse).find('[data-process="api:api_charset"]').val(params.api_charset?params.api_charset:'').trigger('change');$(curCollapse).find('[data-process="api:api_charset_custom"]').val(params.api_charset_custom?params.api_charset_custom:'');$(curCollapse).find('[data-process="api:api_encode"]').val(params.api_encode?params.api_encode:'').trigger('change');$(curCollapse).find('[data-process="api:api_encode_custom"]').val(params.api_encode_custom?params.api_encode_custom:'');if(params.api_params){params.api_params.name=params.api_params.name?params.api_params.name:{};params.api_params.val=params.api_params.val?params.api_params.val:{};params.api_params.addon=params.api_params.addon?params.api_params.addon:{};for(var i in params.api_params.name){var trId='p-m-api-param_'+generateUUID();var trTpl=''+apiTable.attr('data-tpl')+'';apiTable.find('tbody').append(trTpl);apiTable.find('#'+trId+' [data-process="api:api_params:name:"]').val(params.api_params.name[i]);apiTable.find('#'+trId+' [data-process="api:api_params:val:"]').val(params.api_params.val[i]?params.api_params.val[i]:'').trigger('change');apiTable.find('#'+trId+' [data-process="api:api_params:addon:"]').val(params.api_params.addon[i]?params.api_params.addon[i]:'')}} if(params.api_headers){params.api_headers.name=params.api_headers.name?params.api_headers.name:{};params.api_headers.val=params.api_headers.val?params.api_headers.val:{};params.api_headers.addon=params.api_headers.addon?params.api_headers.addon:{};for(var i in params.api_headers.name){var trId='p-m-api-header_'+generateUUID();var trTpl=''+apiHdTable.attr('data-tpl')+'';apiHdTable.find('tbody').append(trTpl);apiHdTable.find('#'+trId+' [data-process="api:api_headers:name:"]').val(params.api_headers.name[i]);apiHdTable.find('#'+trId+' [data-process="api:api_headers:val:"]').val(params.api_headers.val[i]?params.api_headers.val[i]:'').trigger('change');apiHdTable.find('#'+trId+' [data-process="api:api_headers:addon:"]').val(params.api_headers.addon[i]?params.api_headers.addon[i]:'')}} $(curCollapse).find('[data-process="api:api_json"]').val(params.api_json?params.api_json:'');$(curCollapse).find('[data-process="api:api_json_arr"]').val(params.api_json_arr?params.api_json_arr:'implode').trigger('change');$(curCollapse).find('[data-process="api:api_json_implode"]').val(params.api_json_implode?params.api_json_implode:'');$(curCollapse).find('[data-process="api:api_interval"]').val(params.api_interval?params.api_interval:'');$(curCollapse).find('[data-process="api:api_wait"]').val(params.api_wait?params.api_wait:'');$(curCollapse).find('[data-process="api:api_retry"]').val(params.api_retry?params.api_retry:'')} if($_o.processForm!=$_o.processFormField){$('#myModal').modal('hide')}},txt_insert_field:function(boxEle,sltEle,txtObjFunc){var $_o=this;$(boxEle).on('mouseover',sltEle,function(){if(!$(this).attr('data-loaded')){var fieldNames=$_o.$_cp.get_field_names(!0);var html='';if(fieldNames){var formField=$(this).parents($_o.processFormField).eq(0).find('[name="field"]').val();if(formField){for(var i=0;i'} @@ -203,7 +228,7 @@ if(contentSignFunc){showPanelCollapse('#panel_content_sign_func')} loadPluginFunc({module:'contentSign',boxObj:$_o.formObj,funcObj:'[name="content_sign[func]"]',funcVal:contentSignFunc,paramObj:'[name="content_sign[func_param]"]'});$($_o.formObj+' .c-p-content-sign-add-cur').bind('click',function(){insertAtCaret($($_o.formObj).find('[name="content_sign[func_param]"]'),'###')});$($_o.formObj).on('click','.c-p-url-page-signs .btn-page-signs',function(){$_o.$_cp.parent_page_signs(this)})},add_sub:function(){var $_o=this;ajaxOpen({type:'POST',dataType:'json',url:$($_o.formObj).attr('action'),data:$($_o.formObj).serialize(),success:function(data){if(data.code==1){data=data.data;if(data&&data.content_sign){var contentSign=data.content_sign;if($_o.$_cp.page_is_list($_o.curPageType)){var namePre=$_o.$_cp.get_page_vars($_o.curPageType,'namePre');var pageTypeConfig=$_o.curPageConfig[namePre];if(!isObject(pageTypeConfig.content_signs)){pageTypeConfig.content_signs=[]} contentSign._objid=data.objid;pageTypeConfig.content_signs.push(contentSign);$_o.curPageConfig[namePre]=pageTypeConfig}else{$_o.page_add($_o.curPageType,data.objid,contentSign)}} $('#myModal').modal('hide')}else{toastr.error(data.msg)}},error:function(data){toastr.error(data)}});return!1}} -function cpRuleModuleInit(boxId,name,namePre){namePre=namePre?namePre:'';$(boxId+' [name="'+name+'['+namePre+'rule_multi]"]').bind('change',function(){if($(this).is(':checked')){$(boxId+' #c_p_'+name+'_'+namePre+'rule_multi_str').show()}else{$(boxId+' #c_p_'+name+'_'+namePre+'rule_multi_str').hide()}});$(boxId+' select[name="'+name+'['+namePre+'xpath_attr]"]').bind('change',function(){if($(this).val()=='custom'){$(boxId+' [name="'+name+'['+namePre+'xpath_attr_custom]"]').show()}else{$(boxId+' [name="'+name+'['+namePre+'xpath_attr_custom]"]').hide()}});$(boxId+' [name="'+name+'['+namePre+'xpath_multi]"]').bind('change',function(){if($(this).is(':checked')){$(boxId+' #c_p_'+name+'_'+namePre+'xpath_multi_str').show()}else{$(boxId+' #c_p_'+name+'_'+namePre+'xpath_multi_str').hide()}});$(boxId+' select[name="'+name+'['+namePre+'json_arr]"]').bind('change',function(){if($(this).val()=='implode'){$(boxId+' #c_p_'+name+'_'+namePre+'json_arr_implode').show()}else{$(boxId+' #c_p_'+name+'_'+namePre+'json_arr_implode').hide()}})} +function cpRuleModuleInit(boxId,name,namePre){namePre=namePre?namePre:'';$(boxId+' [name="'+name+'['+namePre+'rule_multi]"]').bind('change',function(){if($(this).is(':checked')){$(boxId+' #c_p_'+name+'_'+namePre+'rule_multi_str').show()}else{$(boxId+' #c_p_'+name+'_'+namePre+'rule_multi_str').hide()}});inputSelectCustom(boxId+' select[name="'+name+'['+namePre+'xpath_attr]"]',name+'['+namePre+'xpath_attr_custom]');$(boxId+' [name="'+name+'['+namePre+'xpath_multi]"]').bind('change',function(){if($(this).is(':checked')){$(boxId+' #c_p_'+name+'_'+namePre+'xpath_multi_str').show()}else{$(boxId+' #c_p_'+name+'_'+namePre+'xpath_multi_str').hide()}});$(boxId+' select[name="'+name+'['+namePre+'json_arr]"]').bind('change',function(){if($(this).val()=='implode'){$(boxId+' #c_p_'+name+'_'+namePre+'json_arr_implode').show()}else{$(boxId+' #c_p_'+name+'_'+namePre+'json_arr_implode').hide()}})} function cpRuleModuleLoad(boxId,name,namePre,config){namePre=namePre?namePre:'';if(config){$(boxId+' [name="'+name+'['+namePre+'rule]"]').val(config[namePre+'rule']);$(boxId+' [name="'+name+'['+namePre+'rule_merge]"]').val(config[namePre+'rule_merge']);if(config[namePre+'rule_multi']){$(boxId+' [name="'+name+'['+namePre+'rule_multi]"]').prop('checked',!0).trigger('change')} $(boxId+' [name="'+name+'['+namePre+'rule_multi_type]"][value="'+(config[namePre+'rule_multi_type']?config[namePre+'rule_multi_type']:'')+'"]').prop('checked',!0);$(boxId+' [name="'+name+'['+namePre+'xpath]"]').val(config[namePre+'xpath']);if(config[namePre+'xpath_multi']){$(boxId+' [name="'+name+'['+namePre+'xpath_multi]"]').prop('checked',!0).trigger('change')} $(boxId+' [name="'+name+'['+namePre+'xpath_multi_type]"][value="'+(config[namePre+'xpath_multi_type']?config[namePre+'xpath_multi_type']:'')+'"]').prop('checked',!0);if(config[namePre+'json_loop']){$(boxId+' [name="'+name+'['+namePre+'json_loop]"]').prop('checked',!0)} diff --git a/public/static/js/admin/cpattern_browser.js b/public/static/js/admin/cpattern_browser.js index 996f916..f24f0de 100644 --- a/public/static/js/admin/cpattern_browser.js +++ b/public/static/js/admin/cpattern_browser.js @@ -7,8 +7,8 @@ | 使用协议 https://www.skycaiji.com/licenses |-------------------------------------------------------------------------- */ -'use strict';function SkycaijiCpatternBrowser(){this.consoleId='#skycaiji_console';this.config=null;this.bdNum=0;this.bdNumMax=11} -SkycaijiCpatternBrowser.prototype={constructor:SkycaijiCpatternBrowser,init:function(config){var $_o=this;$_o.config=config?config:{};$('#skycaiji_wrapper').attr('skycaiji-console',$($_o.consoleId).prop('outerHTML'));$('#skycaiji_tpl').remove();$('body').append('');$('#skycaiji_tpl').val($('#skycaiji_wrapper').prop("outerHTML"));$('#skycaiji_wrapper').remove();$('*').removeAttr('onclick').unbind('click').bind('click',function(){var tagName=$(this).prop('tagName').toLowerCase();if(tagName=='body'||tagName=='html'){return!1} +'use strict';function SkycaijiCpatternBrowser(){this.consoleId='#skycaiji_console';this.bdNum=0;this.bdNumMax=11} +SkycaijiCpatternBrowser.prototype={constructor:SkycaijiCpatternBrowser,init:function(configTips){var $_o=this;$('#skycaiji_wrapper').attr('skycaiji-console',$($_o.consoleId).prop('outerHTML'));$('#skycaiji_tpl').remove();$('body').append('');$('#skycaiji_tpl').val($('#skycaiji_wrapper').prop("outerHTML"));$('#skycaiji_wrapper').remove();$('*').removeAttr('onclick').unbind('click').bind('click',function(){var tagName=$(this).prop('tagName').toLowerCase();if(tagName=='body'||tagName=='html'){return!1} $(this).data('skycaiji-click',$(this).data('skycaiji-click')?0:1);var xpaths=$_o.get_xpaths(this,!1);if(!xpaths.listXpath){var xpaths1=$_o.get_xpaths(this,!0);xpaths1.xpath=xpaths.xpath;xpaths=xpaths1} $($_o.consoleId).remove();$('#skycaiji_wrapper').append($('#skycaiji_wrapper').attr('skycaiji-console'));$($_o.consoleId).show();if(xpaths.xpath){$($_o.consoleId).find('[skycaiji-id="xpath"]').val(xpaths.xpath);if(!xpaths.listXpath){$_o.show_xpath_ele($($_o.consoleId+' [skycaiji-id="show-xpath"]'),xpaths.xpath,$(this).data('skycaiji-click'))}} if(xpaths.listXpath){$($_o.consoleId+' [skycaiji-id="listXpath"]').val(xpaths.listXpath);$($_o.consoleId+' [skycaiji-id="listXpath"]').parents('.skycaiji-block').eq(0).show();$($_o.consoleId+' [skycaiji-id="box-listXpath"]').removeClass('skycaiji-b-c-i');if(xpaths.listXpaths&&xpaths.listXpaths.length>1){var lxSlt=$($_o.consoleId+' [skycaiji-id="listXpaths"]');var sltOptions='';for(var i=xpaths.listXpaths.length-1;i>=0;i--){var lxOption=document.createElement('option');lxOption.value=xpaths.listXpaths[i];lxOption.text=xpaths.listXpaths[i];lxSlt.append(lxOption)} @@ -20,14 +20,12 @@ propVal=propVal.replace(/\s*class\s*=\s*[\'\"]\s*[\'\"]/ig,'')}else if(prop=='te propVal=propVal?propVal:'';propVals[prop]=propVal} $.each($(csspath)[0].attributes,function(){if(typeof(propVals[this.name])=='undefined'||propVals[this.name]==null){propVals[this.name]=this.value?this.value:''}});if(propVals){var html='';for(var i in propVals){if(propVals[i]){html+=''+i+''}} if(html){html=''+html+'
'} -if(html){$_o.tips(html,3000,'skycaiji_tips_html',1)}else{$_o.tips('没有属性',1500)}}}});$('body').append($('#skycaiji_tpl').val());$('#skycaiji_wrapper').show();var configSetting={'charset':{name:'网页编码',val:'',loc:'采集器设置»网页编码'},'url_complete':{name:'自动补全网址',val:'',loc:'采集器设置»自动补全网址'},'page_render':{name:'页面渲染',val:'',loc:'采集器设置»页面渲染'},'useragent':{name:'浏览器标识',val:'',loc:'采集器设置»请求头信息»浏览器标识'},'cookie':{name:'cookie数据',val:'',loc:'采集器设置»请求头信息»Cookie 缓存数据'},};var configSetted={};var configUnset={};var cCharset={name:'网页编码',val:'',loc:'采集器设置»网页编码',} -if(!config.url_complete){configUnset.url_complete=configSetting.url_complete} -if(config.charset!='auto'&&config.charset!=''){configSetting.charset.val=config.charset;configSetted.charset=configSetting.charset} -if(config.request_headers&&config.request_headers.open==1){if(config.request_headers.useragent){configSetting.useragent.val=config.request_headers.useragent;configSetted.useragent=configSetting.useragent} -if(config.request_headers.cookie){configSetting.cookie.val=config.request_headers.cookie;configSetted.cookie=configSetting.cookie}} -var html='所见即所得,已过滤所有脚本!
';var htmlUnset='';for(var i in configUnset){htmlUnset+='
  • '+configUnset[i].name+'
  • '} -htmlUnset=htmlUnset?('建议设置:
      '+htmlUnset+'
    '):'';var htmlSetted='';for(var i in configSetted){htmlSetted+='
  • '+configSetted[i].name+'
  • '} -htmlSetted=htmlSetted?('已设置:
      '+htmlSetted+'
    '):'';html=html+htmlUnset+htmlSetted;$_o.tips(html,3000,'skycaiji_tips_list')},get_xpaths:function(element,noId){var $_o=this;var listXpath='';var maxEleNum=1;var xpath=$_o.ele_xpath(element,noId);xpath=xpath.split('/');var listXpaths=[];for(var i=(xpath.length-1);i>=0;i--){if(!xpath[i]){continue} +if(html){$_o.tips(html,3000,'skycaiji_tips_html',1)}else{$_o.tips('没有属性',1500)}}}});$('body').append($('#skycaiji_tpl').val());$('#skycaiji_wrapper').show();var html='所见即所得,已过滤所有脚本!
    ';if(configTips&&typeof(configTips)=='object'){var htmlUnset='';if(configTips.unset&&typeof(configTips.unset)=='object'){for(var i in configTips.unset){htmlUnset+='
  • '+configTips.unset[i]+'
  • '} +htmlUnset=htmlUnset?('建议设置:
      '+htmlUnset+'
    '):''} +var htmlSetted='';if(configTips.setted&&typeof(configTips.setted)=='object'){for(var i in configTips.setted){htmlSetted+='
  • '+configTips.setted[i]+'
  • '} +htmlSetted=htmlSetted?('已设置:
      '+htmlSetted+'
    '):''} +html=html+htmlUnset+htmlSetted} +$_o.tips(html,3000,'skycaiji_tips_list')},get_xpaths:function(element,noId){var $_o=this;var listXpath='';var maxEleNum=1;var xpath=$_o.ele_xpath(element,noId);xpath=xpath.split('/');var listXpaths=[];for(var i=(xpath.length-1);i>=0;i--){if(!xpath[i]){continue} var parentXpath=xpath.slice(0,i+1);parentXpath[i]=parentXpath[i].replace(/\[\d+\]/,'');parentXpath=parentXpath.join('/');var subXpath=xpath.slice(i+1);subXpath=subXpath.join('/');var parentCsspath=$_o.xpath2csspath(parentXpath);var subCsspath=$_o.xpath2csspath(subXpath);var eleNum=0;if(subCsspath){var curIndex=-1;$(parentCsspath).each(function(){curIndex++;var curCsspath=parentCsspath+':eq('+curIndex+')>'+subCsspath;eleNum+=parseInt($(curCsspath).length)})}else{eleNum+=parseInt($(parentCsspath).length)} if(eleNum>maxEleNum){maxEleNum=eleNum;listXpath=parentXpath+(subXpath?('/'+subXpath):'');listXpaths.push(listXpath)}} return{'xpath':xpath.join('/'),'listXpath':listXpath,'listXpaths':listXpaths}},ele_xpath:function(ele,noId){if(!noId&&$(ele).prop('id')){return'//*[@id="'+$(ele).prop('id')+'"]'} diff --git a/public/static/js/admin/cpattern_easy.js b/public/static/js/admin/cpattern_easy.js index 019c3aa..a0ba20f 100644 --- a/public/static/js/admin/cpattern_easy.js +++ b/public/static/js/admin/cpattern_easy.js @@ -8,10 +8,12 @@ |-------------------------------------------------------------------------- */ 'use strict';function CpatternEasy(){this.collIfrId='#ifr_collector';this.browserIfrId='#ifr_browser';this.guideId='#box_guide';this.timer=null;this.timerCount=0;this.intro=null;this.step={isNext:!1,from:null,to:null,last:null};this.getToStep=null;this.eleList=[];this.eleListBackup=[{element:'#coll_tab [href="#coll_pattern_source"]',intro:'采集网页先配置抓取入口',is_tab:1},{element:'#coll_pattern_source_url .add-source-url',intro:'添加列表页网址',is_modal:1},{element:'#form_source [href="#tab_custom"]',intro:'切换到手工指定',in_modal:1},{element:'#form_source [name="source[urls]"]',intro:'输入网址,一行一条列表页网址(http://或https://开头)',in_modal:1},{element:'#form_source [type="submit"]',intro:'保存网址',in_modal:1,is_submit:1},{element:'#coll_pattern_source_url .c-p-source-urls [id^="source_url_"]:last',intro:'刚才输入的列表页网址 分析网页'},{element:'#coll_tab [href="#coll_pattern_link"]',intro:'从列表页中抓取内容页网址',is_tab:1},{element:'[href="#coll_pattern_level_url"]',intro:'如需从起始页中抓取列表页网址可添加多级网址(选填)',no_click:1,in_source_url:1},{element:'#panel_coll_pattern_url',intro:'内容页网址获取',in_source_url:1},{element:'#panel_coll_pattern_url [href="#coll_pattern_url_filter"]',intro:'过滤得到最终的内容页网址(选填)',no_click:1,in_source_url:1},{element:'#panel_coll_pattern_url [href="#coll_pattern_url_area"]',intro:'仅从页面某块区域中提取网址',is_accordion:1,in_source_url:1},{element:'#panel_coll_pattern_url [name="config[area_module]"]',intro:'可选规则类型:正则、xpath、json',in_source_url:1},{element:'#panel_coll_pattern_url [name="config[area]"]',intro:'输入获取网址区域的规则',in_source_url:1},{element:'#panel_coll_pattern_url [href="#coll_pattern_url_url"]',intro:'精准抓取某种格式的网址',is_accordion:1,in_source_url:1},{element:'#panel_coll_pattern_url [name="config[url_rule]"]',intro:'输入提取网址规则',in_source_url:1},{element:'#panel_coll_pattern_url [name="config[url_merge]"]',intro:'拼接成最终网址',in_source_url:1},{element:'[href="#coll_pattern_relation_url"]',intro:'如需从其他页面中抓取数据可添加关联页网址(选填)',no_click:1},{element:'#coll_tab [href="#coll_pattern_field"]',intro:'从内容页中抓取数据',is_tab:1},{element:'#coll_pattern_field .add-field',intro:'添加一个字段',is_modal:1},{element:'#form_field [name="field[name]"]',intro:'字段名称',in_modal:1},{element:'#form_field [name="field[source]"]',intro:'选择数据来源(默认内容页),从选中的页面里获取数据',in_modal:1},{element:'#form_field [name="field[module]"]',intro:'获取数据的方式',in_modal:1},{element:'#c_p_field_module',intro:'编辑字段',in_modal:1},{element:'#form_field [type="submit"]',intro:'保存字段',in_modal:1,is_submit:1},{element:'#coll_pattern_field .c-p-field-list tr:last',intro:'刚才保存的字段'},{element:'[href="#coll_pattern_process"]',intro:'将采集到的字段数据进行处理(选填)',no_click:1},{element:'[href="#coll_pattern_url_pagination"]',intro:'从分页中抓取数据(选填)',no_click:1},{element:'#form_coll [type="submit"]',intro:'保存规则'},]} -CpatternEasy.prototype={constructor:CpatternEasy,init:function(){var $_o=this;var wHeight=$(window).height();var wWidth=$(window).width();var ifrWin=$($_o.collIfrId).get(0).contentWindow;if(wWidth>767){$($_o.collIfrId).height(wHeight+'px');$('#ifr_browser_box').height((wHeight-$('#ifr_browser_box').offset().top)+'px');$(ifrWin).resize(function(){$($_o.guideId).css('margin-left',$($_o.collIfrId).width()+'px')})}else{$($_o.collIfrId).height((wHeight-50)+'px');$($_o.browserIfrId).height(wHeight+'px')} +CpatternEasy.prototype={constructor:CpatternEasy,init:function(resizeWidth){var $_o=this;var wHeight=$(window).height();var wWidth=$(window).width();var ifrWin=$($_o.collIfrId).get(0).contentWindow;if(wWidth>767){$($_o.collIfrId).height(wHeight+'px');$('#ifr_browser_box').height((wHeight-$('#ifr_browser_box').offset().top)+'px');$_o.resize_width(resizeWidth);var boxResize=$('#box_resize');$('#btn_resize').bind('mousedown',function(e){boxResize.addClass('btn-resize-mousedown');var distenceX=boxResize.offset().left-e.pageX;$(document).off('mousemove').bind('mousemove',function(e){var xWidth=e.pageX-distenceX;$_o.resize_width(xWidth)});$(document).off('mouseup').bind('mouseup',function(){boxResize.removeClass('btn-resize-mousedown');$(document).off('mousemove');$(document).off('mouseup');var ifrWidth=$($_o.collIfrId).width();ifrWidth=toInt(ifrWidth);ajaxOpen({type:"GET",url:ulink('cpattern/easymode_resize?width='+ifrWidth),dataType:"json",success:function(data){}})})})}else{$($_o.collIfrId).height((wHeight-50)+'px');$($_o.browserIfrId).height(wHeight+'px')} window.addEventListener("message",function(event){var json=event.data;if(dataIsJson(json)){json=JSON.parse(json);if(json.type=='browser_url'){$_o.browser_url(json)}}},!1);$('#btn_browser').on('click',function(){var pageSource=$('#browser_source').val();var url=$('#browser_url').val();var urls=$('#browser_urls').val();urls=dataIsJson(urls)?JSON.parse(urls):{};$('#browser_urls').val('');if(url){$('#ifr_loading').remove();$('#ifr_browser_box').append('
    ');var browserUrl=cpBrowserUrl($('#coll_id').val(),pageSource,url,urls);$($_o.browserIfrId).hide().attr('src',browserUrl)}});$('#browser_url').on('keyup',function(event){  if(event.keyCode=="13"){    $('#btn_browser').trigger('click');  }});$($_o.collIfrId).bind('load',function(){var ifr=$($_o.collIfrId).contents();var wrapper=ifr.find('body').children('.wrapper');wrapper.children('.main-header').hide();wrapper.children('.main-sidebar').hide();wrapper.children('.content-wrapper').css('margin-left','0px').find('.content-header>.breadcrumb').hide();var link=window.document.createElement('link');link.setAttribute('rel','stylesheet');link.setAttribute('type','text/css');link.setAttribute('href',window.site_config.pub+'/static/css/introjs.css?'+new Date().getTime());var script=window.document.createElement('script');script.setAttribute('type','text/javascript');script.setAttribute('src',window.site_config.pub+'/static/js/intro.js?'+new Date().getTime());var style=window.document.createElement('style');style.type='text/css';style.innerHTML='.intro-form-coll-zindex *{z-index:auto!important;} '+' .intro-hide .introjs-helperLayer,.intro-hide .introjs-tooltipReferenceLayer{display:none!important;}';ifr.find('head')[0].appendChild(link);ifr.find('head')[0].appendChild(script);ifr.find('head')[0].appendChild(style);$_o.intro=null;if(ifrWin.c_pattern){var curVal=$('#browser_source').val();var sourceOptions=ifrWin.c_pattern.page_source_options(!0);if(sourceOptions){$('#browser_source').html(''+sourceOptions).val(curVal)}} $($_o.collIfrId).show();$($_o.collIfrId).get(0).contentWindow.onunload=function(){$($_o.collIfrId).hide()}});$($_o.browserIfrId).bind('load',function(){$('#ifr_loading').remove();var ifr=$($_o.browserIfrId).contents();var consoleEle=ifr.find('#skycaiji_console');if(!consoleEle||consoleEle.length<=0){var wrapper=ifr.find('body').children('.wrapper');wrapper.children('.main-header').hide();wrapper.children('.main-sidebar').hide();wrapper.children('.content-wrapper').css('margin-left','0px').children('.content-header').hide()} -$($_o.browserIfrId).show();$($_o.browserIfrId).get(0).contentWindow.onunload=function(){$($_o.browserIfrId).hide()}})},coll_guide:function(){var $_o=this;var ifrWin=$(this.collIfrId)[0].contentWindow;var ifrJq=ifrWin.$;ifrJq('body').on('shown.bs.modal','#myModal',function(){ifrJq('#form_coll').addClass('intro-form-coll-zindex');ifrJq('#form_coll').find('.introjs-showElement,.introjs-relativePosition').removeClass('introjs-showElement introjs-relativePosition')});ifrJq('body').on('hidden.bs.modal','#myModal',function(){ifrJq('#form_coll').removeClass('intro-form-coll-zindex')});$_o.eleList=$_o.get_ele_list();var stepList=[];for(var i in $_o.eleList){var eleParams=$_o.eleList[i]?$_o.eleList[i]:{};stepList.push({element:eleParams.element?eleParams.element:'',intro:eleParams.intro?eleParams.intro:'',position:eleParams.position?eleParams.position:null})} +$($_o.browserIfrId).show();$($_o.browserIfrId).get(0).contentWindow.onunload=function(){$($_o.browserIfrId).hide()}})},resize_width:function(width){width=toInt(width);var minWidth=100;var maxWidth=$(window).width()-100;if(widthmaxWidth){width=maxWidth} +$(this.collIfrId).width(width+'px');$(this.guideId).css('margin-left',width+'px')},coll_guide:function(){var $_o=this;var ifrWin=$(this.collIfrId)[0].contentWindow;var ifrJq=ifrWin.$;ifrJq('body').on('shown.bs.modal','#myModal',function(){ifrJq('#form_coll').addClass('intro-form-coll-zindex');ifrJq('#form_coll').find('.introjs-showElement,.introjs-relativePosition').removeClass('introjs-showElement introjs-relativePosition')});ifrJq('body').on('hidden.bs.modal','#myModal',function(){ifrJq('#form_coll').removeClass('intro-form-coll-zindex')});$_o.eleList=$_o.get_ele_list();var stepList=[];for(var i in $_o.eleList){var eleParams=$_o.eleList[i]?$_o.eleList[i]:{};stepList.push({element:eleParams.element?eleParams.element:'',intro:eleParams.intro?eleParams.intro:'',position:eleParams.position?eleParams.position:null})} $_o.intro=ifrWin.introJs();$_o.intro.setOptions({prevLabel:'上一步',nextLabel:'下一步',skipLabel:'跳过',doneLabel:'结束',showBullets:!1,steps:stepList}).onbeforechange(function(targetElement){var toStep=$_o.intro._currentStep;if(ifrJq($_o.eleList[toStep].element).length<=0){ifrJq('body').addClass('intro-hide')}}).onchange(function(targetElement){var toStep=$_o.intro._currentStep;toStep=parseInt(toStep);var curStep=0;var isNext=!1;var isJump=!1;if($_o.getToStep){isNext=$_o.getToStep.isNext?true:!1;$_o.getToStep=null;isJump=1}else{if($_o.intro._direction=='backward'){isNext=!1}else{isNext=!0}} if(isNext){curStep=toStep-1}else{curStep=toStep+1} if(curStep>=0){var curStepEle=$_o.eleList[curStep];var toStepEle=$_o.eleList[toStep];var canClick=!1;if(isNext){if(toStepEle.in_modal){if(ifrJq('#myModal').length<=0||ifrJq('#myModal').is(':hidden')){canClick=!0}}else{canClick=!0}}else if(curStepEle.prev){canClick=!0} @@ -35,5 +37,4 @@ if(step<=0){$_o.coll_guide()}else{if(!$_o.intro){$_o.coll_guide()} $_o.getToStep={isNext:isNext};$_o.intro.exit();var ifrJq=$($_o.collIfrId)[0].contentWindow.$;ifrJq('.introjs-overlay').remove();$_o.intro._targetElement=ifrJq('body')[0];$_o.intro.refresh();$_o.intro.goToStep(step).start()}},timer_open:function(func){if(this.timer){window.clearInterval(this.timer)} if(this.timerCount>10){window.clearInterval(this.timer)} this.timer=window.setInterval(func,500)},timer_close:function(){if(this.timer){window.clearInterval(this.timer)}},start_coll_guide:function(){var eleList=this.get_ele_list();if(JSON.stringify(eleList)!=JSON.stringify(this.eleList)){this.intro=null;this.step.last=0} -this.coll_goto_step()},browser_url:function(data){$('#browser_source').val(data.page_source);$('#browser_url').val(data.test_url);$('#browser_urls').val(JSON.stringify(data.input_urls));$('#btn_browser').click()}} -var cpatternEasy=null;$(document).ready(function(){window.cpatternEasy=new CpatternEasy();window.cpatternEasy.init()}) \ No newline at end of file +this.coll_goto_step()},browser_url:function(data){$('#browser_source').val(data.page_source);$('#browser_url').val(data.test_url);$('#browser_urls').val(JSON.stringify(data.input_urls));$('#btn_browser').click()}} \ No newline at end of file diff --git a/public/static/js/admin/cpattern_test.js b/public/static/js/admin/cpattern_test.js index cfc3daf..460d2f2 100644 --- a/public/static/js/admin/cpattern_test.js +++ b/public/static/js/admin/cpattern_test.js @@ -9,14 +9,14 @@ */ 'use strict';function CpTestSourceUrls(collId,maxNum,sourceIsUrl,hasLevels){this.coll_id=collId;this.max_num=maxNum;this.source_is_url=sourceIsUrl;this.has_levels=hasLevels;this.box_id='#win_test_source_urls';this.url_ajax_requests=new Array()} CpTestSourceUrls.prototype={constructor:CpTestSourceUrls,init:function(){var $_o=this;$('#myModal').on('hide.bs.modal',function(e){if($_o&&$_o.url_ajax_requests){for(var i in $_o.url_ajax_requests){$_o.url_ajax_requests[i].abort()}}});$($_o.box_id).on('click','.set-test-num .set-num-btn',function(){var testNum=$($_o.box_id+' .set-test-num').find('.set-num').val();ajaxOpen({type:'get',dataType:'json',url:ulink('cpattern_test/level_num'),data:{num:testNum},async:!0,success:function(data){toastr.success(data.msg)}})});$($_o.box_id).on('click','.cont_ceshi',function(){var curUrl=$(this).attr('data-url');var test_url=ulink('cpattern_test/test_url?coll_id=_collid_&test_url=_url_',{'_collid_':$_o.coll_id,'_url_':curUrl});var urls={};$(this).parents('.source_url').each(function(){var surl=$(this).attr('data-url');if(surl){if($(this).attr('data-level')){urls['level_'+$(this).attr('data-level')]=surl}else{urls.source_url=surl}}});if(urls){for(var i in urls){test_url+='&'+i+'='+encodeURIComponent(urls[i])}} -window.open(test_url,'_blank')});$($_o.box_id).on('click','.cont_element',function(){var urls={};$(this).parents('.source_url').each(function(){var surl=$(this).attr('data-url');if(surl){if($(this).attr('data-level')){urls['level_'+$(this).attr('data-level')]=surl}else{urls.source_url=surl}}});if((/\beasymode\s*\=/i).test(window.self.location.href)&&window.top){cpEasyBrowser($(this).attr('data-url'),'url',urls)}else{var testUrl=$(this).attr('data-url');var browserUrl=cpBrowserUrl($_o.coll_id,'url',testUrl,urls);window.open(browserUrl,'_blank')}});$($_o.box_id).on('click','.cont_urls_num a',function(){var status=$(this).attr('status');if(status==1){$(this).html('[展开]');$(this).attr('status',0);$(this).parents('.cont_urls_num').eq(0).siblings('.cont_urls_list').hide()}else{$(this).html('[收起]');$(this).attr('status',1);$(this).parents('.cont_urls_num').eq(0).siblings('.cont_urls_list').show()}});$($_o.box_id+' .source_url').each(function(index){if(!$_o.source_is_url){var source_url=$(this).attr('data-url');if($_o.has_levels){$($_o.box_id+' .set-test-num').show().find('.set-num').val($_o.max_num);if(index<$_o.max_num){$_o.get_cont_urls(source_url,$(this),1)}}else{$_o.get_cont_urls(source_url,$(this),0)}}})},get_cont_urls:function(source_url,curObj,level,parentUrl,parentLevel){parentUrl=parentUrl?parentUrl:'';parentLevel=parentLevel?parentLevel:'';var $_o=this;if(source_url){curObj.children('.cont_urls_list').html('
    ').show();var url=ulink('cpattern_test/cont_urls?coll_id=_collid_',{'_collid_':$_o.coll_id});var url_ajax_request=ajaxOpen({type:'post',url:url,data:{source_url:source_url,level:level,parent_url:parentUrl,parent_level:parentLevel,},dataType:'json',async:!0,success:function(data){if(data.code==1){data=data.data?data.data:{};var urls=data.urls;var list='
      ';if(level>0){for(var i in urls){urls[i]=htmlspecialchars(urls[i]);if(i<$_o.max_num){list+='
    • 抓取第'+level+'级页面 “'+data.levelName+'”:'+data.levelIsPost+urls[i]+'

    • '}} -list+='
    ';curObj.children('.cont_urls_list').html(list).show();curObj.children('.cont_urls_num').html('获取到'+urls.length+'条网址'+(urls.length>$_o.max_num?'(只测试前'+$_o.max_num+'条)':'')+' [收起]');curObj.children('.cont_urls_list').find('.source_url').each(function(){var obj=$(this);$_o.get_cont_urls(obj.attr('data-url'),obj,data.nextLevel,data.sourceUrl,data.level)})}else{for(var i in urls){urls[i]=htmlspecialchars(urls[i]);list+='
  • [测试] '+'[分析] '+data.urlIsPost+urls[i]+'
  • '} +window.open(test_url,'_blank')});$($_o.box_id).on('click','.cont_element',function(){var urls={};$(this).parents('.source_url').each(function(){var surl=$(this).attr('data-url');if(surl){if($(this).attr('data-level')){urls['level_'+$(this).attr('data-level')]=surl}else{urls.source_url=surl}}});if((/\beasymode\s*\=/i).test(window.self.location.href)&&window.top){cpEasyBrowser($(this).attr('data-url'),'url',urls)}else{var testUrl=$(this).attr('data-url');var browserUrl=cpBrowserUrl($_o.coll_id,'url',testUrl,urls);window.open(browserUrl,'_blank')}});$($_o.box_id).on('click','.cont_urls_num a',function(){var status=$(this).attr('status');if(status==1){$(this).html('[展开]');$(this).attr('status',0);$(this).parents('.cont_urls_num').eq(0).siblings('.cont_urls_list').hide()}else{$(this).html('[收起]');$(this).attr('status',1);$(this).parents('.cont_urls_num').eq(0).siblings('.cont_urls_list').show()}});$($_o.box_id+' .source_url').each(function(index){if(!$_o.source_is_url){var source_url=$(this).attr('data-url');if($_o.has_levels){$($_o.box_id+' .set-test-num').show().find('.set-num').val($_o.max_num);if(index<$_o.max_num){$_o.get_cont_urls(source_url,$(this),1)}}else{$_o.get_cont_urls(source_url,$(this),0)}}})},get_cont_urls:function(source_url,curObj,level,parentUrl,parentLevel){parentUrl=parentUrl?parentUrl:'';parentLevel=parentLevel?parentLevel:'';var $_o=this;if(source_url){curObj.children('.cont_urls_list').html('
    ').show();var url=ulink('cpattern_test/cont_urls?coll_id=_collid_',{'_collid_':$_o.coll_id});var url_ajax_request=ajaxOpen({type:'post',url:url,data:{source_url:source_url,level:level,parent_url:parentUrl,parent_level:parentLevel,},dataType:'json',async:!0,success:function(data){if(data.code==1){data=data.data?data.data:{};var urls=data.urls;var list='
      ';if(level>0){for(var i in urls){urls[i]=htmlspecialchars(urls[i]);if(i<$_o.max_num){list+='
    • 抓取第'+level+'级页面 “'+data.levelName+'”:'+data.levelOpened+urls[i]+'

    • '}} +list+='
    ';curObj.children('.cont_urls_list').html(list).show();curObj.children('.cont_urls_num').html('获取到'+urls.length+'条网址'+(urls.length>$_o.max_num?'(只测试前'+$_o.max_num+'条)':'')+' [收起]');curObj.children('.cont_urls_list').find('.source_url').each(function(){var obj=$(this);$_o.get_cont_urls(obj.attr('data-url'),obj,data.nextLevel,data.sourceUrl,data.level)})}else{for(var i in urls){urls[i]=htmlspecialchars(urls[i]);list+='
  • [测试] '+'[分析] '+data.urlOpened+urls[i]+'
  • '} list+='';curObj.children('.cont_urls_list').html(list).hide();curObj.children('.cont_urls_num').html('获取到'+urls.length+'条网址 [展开]')}}else{curObj.children('.cont_urls_list').html(''+data.msg+'').show()}}});$_o.url_ajax_requests.push(url_ajax_request)}}} function CpTestUrl(collId,testType,pageSource){this.coll_id=collId;this.test=testType?testType:'get_fields';this.test_async=!0;this.page_source=pageSource?pageSource:'';this.box_id='#win_test_url';this.test_ajax_requests=new Array()} CpTestUrl.prototype={constructor:CpTestUrl,ajax:function(params){var ajax_request=ajaxOpen(params);this.test_ajax_requests.push(ajax_request)},abort_test:function(){if(this.test_ajax_requests&&this.test_ajax_requests.length>0){for(var i in this.test_ajax_requests){this.test_ajax_requests[i].abort()} this.test_ajax_requests=new Array()}},load_input_url:function(func){var $_o=this;var urlParams=$($_o.box_id+' [name="url_params"]').val();var inputedUrls={};$('#win_input_urls').find('[name]').each(function(){inputedUrls[$(this).attr('name')]=$(this).val()});var pageSource=$($_o.box_id+' [name="page_source"]').val();var url=ulink('cpattern_test/input_url?test=_test_&coll_id=_collid_&page_source=_source_',{'_test_':$_o.test,'_collid_':$_o.coll_id,'_source_':pageSource});if($($_o.box_id+' button.dropdown-toggle').attr('data-test')=='get_signs'){if($('#win_test_signs').find('[name="signs_cur_all"]').is(':checked')){url+='&signs_cur_all=1'}} $_o.ajax({type:'post',url:url,dataType:'html',async:$_o.test_async,data:{url_params:urlParams,inputed_urls:inputedUrls},success:function(html){if(html){$('#win_input_urls').html(html).show()}else{$('#win_input_urls').html('').hide()} -if($('#win_input_urls').find('[name="page_source_post"]').length>0){$($_o.box_id+' .test-page-source-post').show()}else{$($_o.box_id+' .test-page-source-post').hide()} +var pageOpendObj=$('#win_input_urls').find('#input_url_page_opened');if(pageOpendObj.length>0){$($_o.box_id+' .test-page-opened').html(pageOpendObj.html()).show()}else{$($_o.box_id+' .test-page-opened').hide()} execVarFuncs(func)}})},init:function(){var $_o=this;$($_o.box_id+' .dropdown-menu a[data-test]').bind('click',function(){$_o.abort_test();var btnObj=$(this).parents('.dropdown-menu').eq(0).siblings('button.dropdown-toggle').eq(0);var testName=$(this).attr('data-test');btnObj.attr('data-test',testName);btnObj.find('.test-tips').text($(this).attr('data-title'));if(testName=='get_html'||testName=='get_browser'||testName=='get_signs'||testName=='get_pagination'){$($_o.box_id+' [name="test_url"]').attr('placeholder','输入网址');$($_o.box_id+' .test-page-sources').show()}else{$($_o.box_id+' [name="test_url"]').attr('placeholder','输入内容页网址');$($_o.box_id+' .test-page-sources').hide()} $($_o.box_id+' .field-vals').html('');$_o.test=testName;if(testName=='get_signs'){$('#win_test_signs').show()}else{$('#win_test_signs').hide()} if(testName=='get_pagination'){$('#win_test_pagination').show()}else{$('#win_test_pagination').hide()} @@ -41,5 +41,5 @@ val=val.replace(/\/g,'>');signsHtml+='
    '+tr+'')});$(boxId+' .header .add').bind('click',function(){var tr=$(boxId+' #tpl_tr_header').html();$(boxId+' .header .table tbody').append(''+tr+'')});$(boxId).on('click','.form .table .delete',function(){$(this).parents('tr').eq(0).remove()});$(boxId).on('click','.header .table .delete',function(){$(this).parents('tr').eq(0).remove()});$(boxId+' .test-match-val .visualize').bind('click',function(){visualizeData($('#test_match_val').val())});cpRuleModuleInit(boxId,'field','');$(boxId+' form').bind('submit',function(){$(boxId).append('
    ');ajaxOpen({type:'post',url:$(this).attr('action'),dataType:'json',data:$(this).serialize(),success:function(data){$(boxId).find('.test-loading').remove();if(data.code==1){var valObj=$(boxId+' .test-match-val');valObj.show();if(dataIsHtml(data.msg)){valObj.find('.visualize').show()}else{valObj.find('.visualize').hide()} +function CpTestMatch(){var boxId='#win_test_match';$(boxId+' [name="input_type"]').bind('change',function(){$(boxId+' #input_type_url').hide();$(boxId+' #input_type_content').hide();$(boxId+' #input_type_'+$(this).val()).show()});$(boxId+' [name="type"]').bind('change',function(){$(boxId+' #type_rule').hide();$(boxId+' #type_xpath').hide();$(boxId+' #type_json').hide();$(boxId+' #type_'+$(this).val()).show()});$(boxId+' .test-match-val .visualize').bind('click',function(){visualizeData($('#test_match_val').val())});cpRuleModuleInit(boxId,'field','');$(boxId+' form').bind('submit',function(){$(boxId).append('
    ');ajaxOpen({type:'post',url:$(this).attr('action'),dataType:'json',data:$(this).serialize(),success:function(data){$(boxId).find('.test-loading').remove();if(data.code==1){var valObj=$(boxId+' .test-match-val');valObj.show();if(dataIsHtml(data.msg)){valObj.find('.visualize').show()}else{valObj.find('.visualize').hide()} valObj.find('#test_match_val').val(data.msg)}else{toastr.error(data.msg)}}});return!1})} \ No newline at end of file diff --git a/public/static/js/admin/develop.js b/public/static/js/admin/develop.js index c4d3ca3..4b9193e 100644 --- a/public/static/js/admin/develop.js +++ b/public/static/js/admin/develop.js @@ -8,7 +8,7 @@ |-------------------------------------------------------------------------- */ 'use strict';function DevelopClass(){this.packTypes={};this.downFrameworkSize=0;this.downFrameworkNum=0} -DevelopClass.prototype={constructor:DevelopClass,release_cms:function(config){var $_o=this;$('#form_cms select[name="cms_name"]').bind('change',function(){if($(this).val()=='custom'){$('#cms_name_custom').show()}else{$('#cms_name_custom').hide()}});$('#add_param').bind('click',function(){windowModal('参数',ulink('develop/cmsAddParam'))});$('#param_list').on('click','.param-key',function(){var parentObj=$(this).parents('tr[id^="param_"]').eq(0);var paramval=parentObj.find('input[name="params[]"]').val();var objid=parentObj.attr('id');windowModal('参数',ulink('develop/cmsAddParam?objid=_objid_¶m=_param_',{'_objid_':objid,'_param_':paramval}))});$('#param_list').on('click','.delete-param',function(){$(this).parents('tr').eq(0).remove()});if(config&&!$.isEmptyObject(config)){$('#form_cms [name="name"]').val(config.name);var cmsnameOpt=$('#form_cms [name="cms_name"] option[value="'+config.cms_name+'"]');if(cmsnameOpt.length>0){$('#form_cms [name="cms_name"]').val(config.cms_name)}else{$('#form_cms [name="cms_name"]').val('custom').trigger('change');$('#form_cms [name="cms_name_custom"]').val(config.cms_name)} +DevelopClass.prototype={constructor:DevelopClass,release_cms:function(config){var $_o=this;inputSelectCustom('#form_cms select[name="cms_name"]','cms_name_custom');$('#add_param').bind('click',function(){windowModal('参数',ulink('develop/cmsAddParam'))});$('#param_list').on('click','.param-key',function(){var parentObj=$(this).parents('tr[id^="param_"]').eq(0);var paramval=parentObj.find('input[name="params[]"]').val();var objid=parentObj.attr('id');windowModal('参数',ulink('develop/cmsAddParam?objid=_objid_¶m=_param_',{'_objid_':objid,'_param_':paramval}))});$('#param_list').on('click','.delete-param',function(){$(this).parents('tr').eq(0).remove()});if(config&&!$.isEmptyObject(config)){$('#form_cms [name="name"]').val(config.name);var cmsnameOpt=$('#form_cms [name="cms_name"] option[value="'+config.cms_name+'"]');if(cmsnameOpt.length>0){$('#form_cms [name="cms_name"]').val(config.cms_name)}else{$('#form_cms [name="cms_name"]').val('custom').trigger('change');$('#form_cms [name="cms_name_custom"]').val(config.cms_name)} $('#form_cms [name="identifier"]').val(config.identifier);$('#form_cms [name="copyright"]').val(config.copyright);if(config.is_edit){$('#form_cms [name="cms_name"]').attr("disabled","disabled");$('#form_cms [name="cms_name_custom"]').attr("disabled","disabled");$('#form_cms [name="identifier"]').attr("disabled","disabled");$('#form_cms [name="copyright"]').attr("disabled","disabled")} if(config.params){for(var i in config.params){$_o.add_cms_param(config.params[i])}}}},init_cms_param:function(){var $_o=this;$('#win_form_param select[name="param[type]"]').bind('change',function(){$('#win_form_param .param-type-select').hide();var curType=$(this).val();if(curType=='select_val'||curType=='select_func'){$('#win_form_param .param-type-select[data-select="'+curType+'"]').show()}});$('#win_form_param').submit(function(){var checkKey=!0;var curKey=$('#win_form_param [name="param[key]"]').val();var objid=$('#win_form_param input[name="objid"]').val();if(objid){if(curKey==$('#'+objid).find('.param-key').attr('data-val')){checkKey=!1}} if(checkKey){var hasKey=!1;$('#param_list .param-key').each(function(){if(curKey==$(this).attr('data-val')){hasKey=!0;return!1}});if(hasKey){toastr.error('变量名已存在!');return!1}} @@ -19,5 +19,5 @@ if(checkName){var hasName=!1;$('#pack_list .pack-name').each(function(){if(curNa ajaxOpen({type:'POST',dataType:'json',url:$(this).attr('action'),data:$(this).serialize(),success:function(data){if(data.code==1){$_o.add_app_pack(data.data,objid);$('#myModal').modal('hide')}else{toastr.error(data.msg)}},error:function(data){toastr.error(data)}});return!1});$('#win_form_pack [name="pack[type]"]').bind('change',function(){var type=$(this).val();$(this).siblings('.help-block').each(function(){if($(this).hasClass('type-'+type)){$(this).show()}else{$(this).hide()}})})},load_app_pack:function(pack){if(pack){$('#win_form_pack [name="pack[name]"]').val(pack.name);$('#win_form_pack [name="pack[type]"]').val(pack.type).trigger('change');$('#win_form_pack [name="pack[nav_link]"]').val(pack.nav_link);$('#win_form_pack [name="pack[target]"][value="'+parseInt(pack.target)+'"]').prop('checked','checked')}},down_framework:function(params){var $_o=this;params=params?params:{};var url='develop/installFramework?app='+$('#form_app [name="app"]').val();if(params.block_no){url+='&block_no='+params.block_no} url=ulink(url);ajaxOpen({type:'get',dataType:'json',url:url,success:function(data){if(data.code==1){var dataData=data.data;dataData=dataData?dataData:{};if(dataData.next_block_no>0){var per=parseInt(parseFloat(dataData.next_block_no/dataData.blocks)*100);$('#install_framework').find('.perct').text(per);$_o.down_framework({'block_no':dataData.next_block_no})}else{$('#install_framework').find('.perct').text('100');ajaxDataMsg(data)}}else{$('#install_framework').attr('disabled',!1).html('下载失败');if(data.msg){toastr.error(data.msg)}}},error:function(){$('#install_framework').attr('disabled',!1).html('下载失败')}})},func:function(module,config){var $_o=this;$('#add_method').bind('click',function(){$_o.func_add_method()});$('#form_func').on('click','.delete-method',function(){$(this).parents('tr').remove()});if(module){$('#form_func [name="module"]').val(module)} if(config&&!$.isEmptyObject(config)){for(var i in config){$('#form_func [name="'+i+'"]').val(config[i])} -$('#form_func [name="module"]').attr('disabled','disabled');$('#form_func [name="identifier"]').attr('disabled','disabled');$('#form_func [name="copyright"]').attr('disabled','disabled')}},func_add_method:function(method,desc){method=method?method:'';desc=desc?desc:'';var tr=''+''+'';$('#form_func table.method_list tbody').append(tr)}} +$('#form_func [name="module"]').attr('readonly','readonly').attr('onfocus','this.defaultIndex=this.selectedIndex;').attr('onchange','this.selectedIndex=this.defaultIndex;');$('#form_func [name="identifier"]').attr('readonly','readonly');$('#form_func [name="copyright"]').attr('readonly','readonly')}},func_add_method:function(method,desc){method=method?method:'';desc=desc?desc:'';var tr=''+''+'';$('#form_func table.method_list tbody').append(tr)}} var developClass=new DevelopClass() \ No newline at end of file diff --git a/public/static/js/admin/index.js b/public/static/js/admin/index.js index 595e02d..059ba01 100644 --- a/public/static/js/admin/index.js +++ b/public/static/js/admin/index.js @@ -7,12 +7,13 @@ | 使用协议 https://www.skycaiji.com/licenses |-------------------------------------------------------------------------- */ -'use strict';$(document).ready(function(){$('#op_clean').bind('click',function(){windowModal('清理缓存',ulink('setting/clean'))});$('#a_collect_now').bind('click',function(){collectorWindow('实时采集','admin/backstage/collect',null,{lg:1})});$('#upgrade_db').bind('click',function(){var obj=$(this);ajaxOpen({type:'get',dataType:'json',url:ulink('install/upgrade/db'),success:function(data){if(data.code==1){obj.html(data.msg?data.msg:'升级成功');window.setTimeout(function(){window.location.reload()},1000)}else{obj.html(data.msg?data.msg:'升级失败')}}})});$('body').on('click','#op_upgrade',function(){var obj=$(this);if(obj.attr('upgrading')==1){return!1} +'use strict';$(document).ready(function(){$('#op_clean').bind('click',function(){windowModal('清理缓存',ulink('setting/clean'))});$('#a_run_auto_backstage').bind('click',function(){windowModal('正在激活自动采集...',ulink('admin/backstage/run_auto_backstage'))});$('#a_collect_now').bind('click',function(){collectorWindow('实时采集','admin/backstage/collect',null,{lg:1})});$('#upgrade_db').bind('click',function(){var obj=$(this);ajaxOpen({type:'get',dataType:'json',url:ulink('install/upgrade/db'),success:function(data){if(data.code==1){obj.html(data.msg?data.msg:'升级成功');window.setTimeout(function(){window.location.reload()},1000)}else{obj.html(data.msg?data.msg:'升级失败')}}})});$('body').on('click','#op_upgrade',function(){var obj=$(this);if(obj.attr('upgrading')==1){return!1} $('#upgrade_error').html('').hide();var versionFile=obj.attr('data-version-file');if(versionFile=='zip'){obj.html('正在下载压缩包...')}else{obj.html('正在检索更新文件...')} ajaxOpen({type:'get',dataType:'json',url:ulink('upgrade/download?version_file=_vfile_',{'_vfile_':versionFile}),success:function(data){obj.attr('upgrading',1);if(versionFile=='zip'){if(data.code==1){data=data.data?data.data:{};var size=toInt(data.size);size=size/(1024*1024);size=Math.floor(size*100)/100;obj.html('正在下载...  0MB / '+size+'MB');var upgradeZipClass=new UpgradeZipClass(data.size,data.blocks);upgradeZipClass.down_zip(1)}else{obj.html(data.msg?data.msg:'压缩包下载失败')}}else{if(data.code==1){var fileList=new Array();if(data.data.files){for(var i in data.data.files){fileList.push(data.data.files[i])}} -if(fileList.length>0){obj.html('正在更新...');var upgradeClass=new UpgradeClass(fileList);upgradeClass.down_file(0)}else{obj.html('没有需要更新的文件')}}else{var upgradeClass=new UpgradeClass(null);upgradeClass.down_complete()}}}});return!1});$('#refresh_admin_index').bind('click',function(){$('#skycaiji_admin_index').parents('.box').eq(0).find('.overlay').show();ajaxOpen({type:'get',dataType:'json',async:!0,timeout:10000,url:ulink('backstage/adminIndex?refresh=1'),success:function(data){var html=data.html?data.html:'';$('#skycaiji_admin_index').html(html);$('#skycaiji_admin_index').parents('.box').eq(0).find('.overlay').hide()},error:function(){$('#refresh_admin_index').trigger('click')}})});$('#box_open_basedir .close').bind('click',function(){confirmRight('忽略该问题?',function(){ajaxOpen({type:'get',dataType:'json',url:ulink('backstage/ignoreOpenBasedir'),success:function(data){$('#box_open_basedir').remove()}})})});$('#upgrade_check').html('正在检测更新...');ajaxOpen({type:'get',dataType:'json',async:!0,timeout:10000,url:ulink('backstage/newVersion'),success:function(data){data=data.data;if(data){if(data.is_new_version){var html='检测到新版本V'+data.new_version+'点击升级';if(data.version_link){html+='  '+data.version_link[0]+''} +if(fileList.length>0){obj.html('正在更新...');var upgradeClass=new UpgradeClass(fileList);upgradeClass.down_file(0)}else{obj.html('没有需要更新的文件')}}else{var upgradeClass=new UpgradeClass(null);upgradeClass.down_complete()}}}});return!1});$('#refresh_admin_index').bind('click',function(){$('#skycaiji_admin_index').parents('.box').eq(0).find('.overlay').show();ajaxOpen({type:'get',dataType:'json',async:!0,timeout:10000,url:ulink('backstage/adminIndex?refresh=1'),success:function(data){var html=data.html?data.html:'';$('#skycaiji_admin_index').html(html);$('#skycaiji_admin_index').parents('.box').eq(0).find('.overlay').hide()},error:function(){var times=$('#refresh_admin_index').attr('data-times');times=toInt(times);if(times<3){$('#refresh_admin_index').attr('data-times',times+1).trigger('click')}}})});$('#box_open_basedir .close').bind('click',function(){confirmRight('忽略该问题?',function(){ajaxOpen({type:'get',dataType:'json',url:ulink('backstage/ignoreOpenBasedir'),success:function(data){$('#box_open_basedir').remove()}})})});$('#upgrade_check').html('正在检测更新...');ajaxOpen({type:'get',dataType:'json',async:!0,timeout:10000,url:ulink('backstage/newVersion'),success:function(data){data=data.data;if(data){if(data.is_new_version){var html='检测到新版本V'+data.new_version+'点击升级';if(data.version_link){html+='  '+data.version_link[0]+''} $('#upgrade_check').html(html)}else{$('#upgrade_check').html('暂无更新')} if(data.is_new_admin_index){$('#refresh_admin_index').trigger('click')}}else{$('#upgrade_check').html('')}},error:function(){$('#upgrade_check').html('检测失败')}});ajaxOpen({type:'get',dataType:'json',async:!0,timeout:60000,url:ulink('backstage/checkUp'),success:function(data){data=data.data;if(data){var list=[];if(data.phpInvalid){list.push('cli命令行')} +if(data.phpCliVersion){$('#php_cli_version').show().find('span').html(data.phpCliVersion)} if(data.pageRenderInvalid){list.push('页面渲染')} if(list.length>0){list=list.join(' , ');$('#invalid_list').find('[data-box]').html(list);$('#invalid_list').fadeIn()} if(data.tongji){for(var i in data.tongji){$('#data_'+i).text(data.tongji[i])}}}}})});function UpgradeZipClass(size,blocks){this.size=size?toInt(size):0;this.blocks=blocks?toInt(blocks):0;this.nextBlockNo=0;this.existBlocks=0} diff --git a/public/static/js/admin/mystore.js b/public/static/js/admin/mystore.js index a4099a8..51199cd 100644 --- a/public/static/js/admin/mystore.js +++ b/public/static/js/admin/mystore.js @@ -14,7 +14,7 @@ window.location.href=ulink('mystore/rule?order='+order+'&sort='+sort);return!1}) var className=$(this).attr('class');var sort='desc';if(className=='sorting_desc'){sort='asc'} window.location.href=ulink('mystore/releaseApp?order='+order+'&sort='+sort);return!1});$('#deleteall').bind('click',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){ajaxOpen({type:"POST",url:ulink('mystore/releaseAppOp?op=deleteall'),dataType:"json",data:$('#form_list').serialize(),success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg);window.setTimeout("window.location.reload();",2500)}})})});$('#list_table .delete').bind('click',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){ajaxOpen({type:"GET",url:obj.attr('url'),dataType:"json",success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg);if(data.code==1){obj.parents('tr').eq(0).remove()}}})})});$('#list_table').on('click','.store-detail',function(){openStoreUrl($(this).attr('data-url'))});$('#check_update').bind('click',function(){$_o.check_releaseapp_update()});$('#auto_check').bind('click',function(){var auto=$(this).is(':checked')?1:0;ajaxOpen({type:"GET",url:ulink('mystore/releaseAppOp?op=auto_check&auto='+auto),dataType:"json",success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg)}})});if($('#auto_check').is(':checked')){$_o.check_releaseapp_update()}},check_releaseapp_update:function(){var ids=new Array();$('#list_table').find('tr[data-app-id]').each(function(){ids.push($(this).attr('data-app-id'))});if(ids.length>0){$('.nav-check-update #check_update').html('
    正在检测更新');$('.store-detail').find('.new-version').remove();ajaxOpen({type:"get",url:ulink('mystore/releaseAppOp?op=check_store_update'),dataType:"json",async:!0,data:{ids:ids},success:function(data){if(data.code==1){for(var id in data.data){var storeDetail=$('tr[data-app-id="'+data.data[id]+'"]').find('.store-detail');storeDetail.append('')}}},complete:function(){$('.nav-check-update #check_update').html('检测更新')}})}},init_funcapp:function(){var $_o=this;$('table.datatable thead th[data-order]').bind('click',function(){var order=$(this).attr('data-order');if(!order){return!1} var className=$(this).attr('class');var sort='desc';if(className=='sorting_desc'){sort='asc'} -window.location.href=ulink('mystore/funcApp?order='+order+'&sort='+sort);return!1});$('#deleteall').bind('click',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){ajaxOpen({type:"POST",url:ulink('mystore/funcAppOp?op=deleteall'),dataType:"json",data:$('#form_list').serialize(),success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg);window.setTimeout("window.location.reload();",2500)}})})});$('#list_table .delete').bind('click',function(){var obj=$(this);var id=$(this).parents('tr[data-app-id]').attr('data-app-id');confirmRight(window.tpl_lang.confirm_delete,function(){ajaxOpen({type:"GET",url:ulink('mystore/funcAppOp?op=delete'),dataType:"json",data:{id:id},success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg);if(data.code==1){obj.parents('tr').eq(0).remove()}}})})});$('#list_table .enable').bind('click',function(){var obj=$(this);var id=$(this).parents('tr[data-app-id]').attr('data-app-id');var enable=$(this).attr('data-val');enable=(enable==1)?0:1;ajaxOpen({type:'GET',url:ulink('mystore/funcAppOp?op=enable'),dataType:'json',data:{id:id,enable:enable},success:function(data){if(data.code){obj.attr('data-val',enable?1:0);obj.text(enable?'已启用':'已禁用');obj.css('color',(enable?'green':'red'))}else{toastr.error(data.msg)}}})});$('#list_table .methods .dropdown-toggle').bind('click',function(){var obj=$(this);var id=$(this).parents('tr[data-app-id]').attr('data-app-id');var box=obj.parents('.methods');box.find('.dropdown-menu').html('
  • ');ajaxOpen({type:'GET',url:ulink('mystore/funcAppOp?op=detail'),dataType:'json',data:{id:id},success:function(data){if(data.code&&data.data){var methods=data.data.methods;var html='';for(var m in methods){var mMethod=methods[m];mMethod=mMethod?mMethod:{};html+='
  • '+m+':'+(mMethod.comment_cut?mMethod.comment_cut:'')+'
  • '} +window.location.href=ulink('mystore/funcApp?order='+order+'&sort='+sort);return!1});$('#deleteall').bind('click',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){ajaxOpen({type:"POST",url:ulink('mystore/funcAppOp?op=deleteall'),dataType:"json",data:$('#form_list').serialize(),success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg);window.setTimeout("window.location.reload();",2500)}})})});$('#list_table .delete').bind('click',function(){var obj=$(this);var id=$(this).parents('tr[data-app-id]').attr('data-app-id');confirmRight(window.tpl_lang.confirm_delete,function(){ajaxOpen({type:"GET",url:ulink('mystore/funcAppOp?op=delete'),dataType:"json",data:{id:id},success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg);if(data.code==1){obj.parents('tr').eq(0).remove()}}})})});$('#list_table .enable').bind('click',function(){var obj=$(this);var id=$(this).parents('tr[data-app-id]').attr('data-app-id');var enable=$(this).attr('data-val');enable=(enable==1)?0:1;ajaxOpen({type:'GET',url:ulink('mystore/funcAppOp?op=enable'),dataType:'json',data:{id:id,enable:enable},success:function(data){if(data.code){obj.attr('data-val',enable?1:0);obj.text(enable?'已启用':'已禁用');obj.css('color',(enable?'green':'red'))}else{toastr.error(data.msg)}}})});$('#list_table .methods .dropdown-toggle').bind('click',function(){var obj=$(this);var id=$(this).parents('tr[data-app-id]').attr('data-app-id');var box=obj.parents('.methods');box.find('.dropdown-menu').html('
  • ');ajaxOpen({type:'GET',url:ulink('mystore/funcAppOp?op=detail'),dataType:'json',data:{id:id},success:function(data){if(data.code&&data.data){var methods=data.data.methods;methods=methods?methods:{};var html='';for(var m in methods){var mMethod=methods[m];mMethod=mMethod?mMethod:{};html+='
  • '+m+':'+(mMethod.comment_cut?mMethod.comment_cut:'')+'
  • '} if(!html){html='
  • 无方法
  • '} -box.find('.dropdown-menu').html(html);box.find('.dropdown-menu [data-func-method]').bind('click',function(){var funcMethod=$(this).attr('data-func-method');windowModal('方法:'+funcMethod,ulink('mystore/funcAppOp?op=method&id=_id_&name=_name_',{'_id_':id,'_name_':funcMethod}),{lg:1})})}}})});$('#list_table').on('click','.store-detail',function(){openStoreUrl($(this).attr('data-url'))});$('#check_update').bind('click',function(){$_o.check_funcapp_update()});$('#auto_check').bind('click',function(){var auto=$(this).is(':checked')?1:0;ajaxOpen({type:"GET",url:ulink('mystore/funcAppOp?op=auto_check&auto='+auto),dataType:"json",success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg)}})});if($('#auto_check').is(':checked')){$_o.check_funcapp_update()}},check_funcapp_update:function(){var ids=new Array();$('#list_table').find('tr[data-app-id]').each(function(){ids.push($(this).attr('data-app-id'))});if(ids.length>0){$('.nav-check-update #check_update').html('
    正在检测更新');$('.store-detail').find('.new-version').remove();ajaxOpen({type:"get",url:ulink('mystore/funcAppOp?op=check_store_update'),dataType:"json",async:!0,data:{ids:ids},success:function(data){if(data.code==1){for(var id in data.data){var storeDetail=$('tr[data-app-id="'+data.data[id]+'"]').find('.store-detail');storeDetail.append('')}}},complete:function(){$('.nav-check-update #check_update').html('检测更新')}})}}} +box.find('.dropdown-menu').html(html);box.find('.dropdown-menu [data-func-method]').bind('click',function(){var funcMethod=$(this).attr('data-func-method');windowModal('方法:'+funcMethod,ulink('mystore/funcAppOp?op=method&id=_id_&name=_name_',{'_id_':id,'_name_':funcMethod}),{lg:1,'full_height':1})})}}})});$('#list_table').on('click','.store-detail',function(){openStoreUrl($(this).attr('data-url'))});$('#check_update').bind('click',function(){$_o.check_funcapp_update()});$('#auto_check').bind('click',function(){var auto=$(this).is(':checked')?1:0;ajaxOpen({type:"GET",url:ulink('mystore/funcAppOp?op=auto_check&auto='+auto),dataType:"json",success:function(data){data.code==1?toastr.success(data.msg):toastr.error(data.msg)}})});if($('#auto_check').is(':checked')){$_o.check_funcapp_update()}},check_funcapp_update:function(){var ids=new Array();$('#list_table').find('tr[data-app-id]').each(function(){ids.push($(this).attr('data-app-id'))});if(ids.length>0){$('.nav-check-update #check_update').html('
    正在检测更新');$('.store-detail').find('.new-version').remove();ajaxOpen({type:"get",url:ulink('mystore/funcAppOp?op=check_store_update'),dataType:"json",async:!0,data:{ids:ids},success:function(data){if(data.code==1){for(var id in data.data){var storeDetail=$('tr[data-app-id="'+data.data[id]+'"]').find('.store-detail');storeDetail.append('')}}},complete:function(){$('.nav-check-update #check_update').html('检测更新')}})}}} var myStoreClass=new MyStoreClass() \ No newline at end of file diff --git a/public/static/js/admin/proxy.js b/public/static/js/admin/proxy.js index ecd0714..dd1ff4c 100644 --- a/public/static/js/admin/proxy.js +++ b/public/static/js/admin/proxy.js @@ -8,17 +8,13 @@ |-------------------------------------------------------------------------- */ 'use strict';function ProxyClass(){} -ProxyClass.prototype={constructor:ProxyClass,init_setting:function(proxyConfig){var $_o=this;$('#proxy_ip_table').attr('data-tpl',$('#proxy_ip_table .proxy-ip-tpl').html());$('#proxy_ip_table .proxy-ip-tpl').remove();$('[name="open"]').bind('click',function(){if($(this).val()==1){$('.content-wrapper').removeClass('wrapper-not-enable')}else{$('.content-wrapper').addClass('wrapper-not-enable')}});$('#btn_sub').bind('click',function(){var ip_list=new Array();var user_list=new Array();var pwd_list=new Array();var type_list=new Array();$('[data-name="ip_list[]"]').each(function(){ip_list.push($(this).val())});$('[data-name="user_list[]"]').each(function(){user_list.push($(this).val())});$('[data-name="pwd_list[]"]').each(function(){pwd_list.push($(this).val())});$('[data-name="type_list[]"]').each(function(){type_list.push($(this).val())});if(ip_list){$('[name="ip_list"]').val(JSON.stringify(ip_list))} -if(user_list){$('[name="user_list"]').val(JSON.stringify(user_list))} -if(pwd_list){$('[name="pwd_list"]').val(JSON.stringify(pwd_list))} -if(type_list){$('[name="type_list"]').val(JSON.stringify(type_list))}});$('#batch_proxy_ip').bind('click',function(){windowModal('批量添加',ulink('proxy/batch'))});$('#add_proxy_ip').bind('click',function(){windowModal('添加代理IP',ulink('proxy/add'))});$('#invalid_proxy_ip').bind('click',function(){confirmRight('确定清理无效ip?',function(){windowModal('正在清理...',ulink('proxy/clearInvalid'),{ajax:{success:function(){$_o.reload_iframe('清理完成')}}})})});$('#proxy_ip_iframe').bind('load',function(){$('#panel_proxy_ip .loading').hide();$(this).show();var mainheight=$(this).contents().find('body').height()+1;$(this).height(mainheight)});$('#proxy_ip_table').on('click','.delete-proxy-ip',function(){$(this).parents('tr').eq(0).remove()});$('[name="use"]').bind('click',function(){$('[id^="proxy_use_"]').hide();$('#proxy_use_'+$(this).val()).show()});$('[name="api[open]"]').bind('click',function(){if($(this).val()==1){showPanelCollapse('#panel_proxy_api')}});$('#proxy_api .p-api-add').bind('click',function(){$_o.add_api()});$('#proxy_api').on('click','.p-api-format a[data-val]',function(){var obj=$(this).parents('.p-api-panel').eq(0).find('[data-name="api_format"]');insertAtCaret(obj,$(this).attr('data-val'))});eleExchange('#proxy_api','.p-api-move','.p-api-panel');$('#proxy_api').on('click','.p-api-delete',function(){var obj=$(this);confirmRight('确定删除?',function(){obj.parents('.p-api-panel').eq(0).remove()})});$('#proxy_api').on('click','.btn-api-test',function(){var config={};$(this).parents('.p-api-panel').eq(0).find('[data-name]').each(function(){var name=$(this).attr('data-name');config[name]=$(this).val()});windowModal('测试接口',ulink('proxy/testApi'),{ajax:{type:'post',data:{config:config}}})});if(proxyConfig){$('[name="open"][value="'+toInt(proxyConfig.open)+'"]').trigger('click');$('[name="failed"]').val(toInt(proxyConfig.failed));$('[name="use"][value="'+proxyConfig.use+'"]').trigger('click');$('[name="use_num"]').val(toInt(proxyConfig.use_num));$('[name="use_time"]').val(toInt(proxyConfig.use_time));if(proxyConfig.api){$('[name="api[open]"][value="'+toInt(proxyConfig.api.open)+'"]').trigger('click');$('[name="api[insert]"]').val(proxyConfig.api.insert)} +ProxyClass.prototype={constructor:ProxyClass,init_setting:function(proxyConfig){var $_o=this;$('#proxy_ip_table').attr('data-tpl',$('#proxy_ip_table .proxy-ip-tpl').html());$('#proxy_ip_table .proxy-ip-tpl').remove();$('[name="open"]').bind('click',function(){if($(this).val()==1){$('.content-wrapper').removeClass('wrapper-not-enable')}else{$('.content-wrapper').addClass('wrapper-not-enable')}});$('.edit-proxy-groups').bind('click',function(){windowModal('管理分组',ulink('proxy/groups'))});$('#batch_proxy_ip').bind('click',function(){windowModal('批量添加',ulink('proxy/batch'))});$('#add_proxy_ip').bind('click',function(){windowModal('添加代理IP',ulink('proxy/add'),{lg:1})});$('#invalid_proxy_ip').bind('click',function(){confirmRight('确定清理无效ip?',function(){windowModal('正在清理...',ulink('proxy/clearInvalid'),{ajax:{success:function(){$_o.reload_iframe('清理完成')}}})})});$('#proxy_ip_iframe').bind('load',function(){$('#panel_proxy_ip .loading').hide();$(this).show();var mainheight=$(this).contents().find('body').height()+1;$(this).height(mainheight)});$('#proxy_ip_table').on('click','.delete-proxy-ip',function(){$(this).parents('tr').eq(0).remove()});$('[name="use"]').bind('click',function(){$('[id^="proxy_use_"]').hide();$('#proxy_use_'+$(this).val()).show()});$('[name="api[open]"]').bind('click',function(){if($(this).val()==1){showPanelCollapse('#panel_proxy_api')}});$('#proxy_api .p-api-add').bind('click',function(){$_o.add_api()});$('#proxy_api').on('click','.p-api-format a[data-val]',function(){var obj=$(this).parents('.p-api-panel').eq(0).find('[data-name="api_format"]');insertAtCaret(obj,$(this).attr('data-val'))});eleExchange('#proxy_api','.p-api-move','.p-api-panel');$('#proxy_api').on('click','.p-api-delete',function(){var obj=$(this);confirmRight('确定删除?',function(){obj.parents('.p-api-panel').eq(0).remove()})});$('#proxy_api').on('click','.btn-api-test',function(){var config={};$(this).parents('.p-api-panel').eq(0).find('[data-name]').each(function(){var name=$(this).attr('data-name');config[name]=$(this).val()});windowModal('测试接口',ulink('proxy/testApi'),{ajax:{type:'post',data:{config:config}}})});if(proxyConfig){$('[name="open"][value="'+toInt(proxyConfig.open)+'"]').trigger('click');$('[name="failed"]').val(toInt(proxyConfig.failed));$('[name="group_id"]').val(toInt(proxyConfig.group_id));$('[name="use"][value="'+proxyConfig.use+'"]').trigger('click');$('[name="use_num"]').val(toInt(proxyConfig.use_num));$('[name="use_time"]').val(toInt(proxyConfig.use_time));if(proxyConfig.api){$('[name="api[open]"][value="'+toInt(proxyConfig.api.open)+'"]').trigger('click');$('[name="api[insert]"]').val(proxyConfig.api.insert)} if(proxyConfig.apis){for(var i in proxyConfig.apis){$_o.add_api(proxyConfig.apis[i])}}}},reload_iframe:function(msg){$('#myModal').modal('hide');toastr.success(msg);$('#panel_proxy_ip .loading').show();$('#proxy_ip_iframe').hide();$('#proxy_ip_iframe').attr('src',$('#proxy_ip_iframe').attr('src')).hide()},add_api:function(data){data=data?data:{};var tpl=$('#proxy_api_tpl').clone();tpl.removeAttr('id').css('display','block');var unique=generateUUID();var collapseId='api_collapse_'+unique;tpl.find('.p-api-title').attr('href','#'+collapseId);tpl.find('.p-api-collapse').attr('id',collapseId);tpl.find('[data-name]').each(function(){var name=$(this).attr('data-name');$(this).attr('name','apis[i_'+unique+']['+name+']');if(data[name]){$(this).val(data[name])}});if(data.api_url){tpl.find('.p-api-title small').text(':'+data.api_url)} $('#proxy_api_box').append(tpl)},init_list:function(search){search=search?search:{};if(search){for(var i in search){$('#form_search').find('[name="'+i+'"]').val(search[i])}} -$('#form_list').on('change','[data-name="ip_list[]"],[data-name="user_list[]"],[data-name="pwd_list[]"],[data-name="type_list[]"]',function(){$(this).parents('tr').eq(0).find('[data-name="ips[]"]').prop('checked',!0)});$('#form_list').on('click','.op-delete',function(){var tr=$(this).parents('tr').eq(0);var ip=tr.find('[data-name="ips[]"]').val();ajaxOpen({type:'get',dataType:'json',url:ulink('proxy/op?op=delete&ip=_ip_',{'_ip_':ip}),success:function(data){if(data.code==1){tr.fadeOut(100,function(){tr.remove()});toastr.success(data.msg)}else{toastr.error(data.msg)}}})});$('#form_list').on('click','.check-all-ip',function(){var checked=$(this).is(":checked")?true:!1;$('[data-name="ips[]"]').prop('checked',checked)});$('#form_list').on('click','.delete-all-ip',function(){confirmRight('确定删除选中的IP?',function(){$('#form_list').find('[name="op"]').val('delete_all');var ips=new Array();$('#form_list').find('[data-name="ips[]"]').each(function(){if($(this).is(':checked')){ips.push($(this).val())}});if(ips){$('[name="ips"]').val(JSON.stringify(ips))} -$('#form_list').submit()})});$('#form_list').on('click','.update-all-ip',function(){confirmRight('确定修改?',function(){$('#form_list').find('[name="op"]').val('update_all');var ips=new Array();var ip_list=new Array();var user_list=new Array();var pwd_list=new Array();var type_list=new Array();$('#form_list').find('[data-name="ips[]"]').each(function(){if($(this).is(':checked')){ips.push($(this).val());var tr=$(this).parents('tr').eq(0);ip_list.push(tr.find('[data-name="ip_list[]"]').val());user_list.push(tr.find('[data-name="user_list[]"]').val());pwd_list.push(tr.find('[data-name="pwd_list[]"]').val());type_list.push(tr.find('[data-name="type_list[]"]').val())}});if(ips){$('[name="ips"]').val(JSON.stringify(ips))} -if(ip_list){$('[name="ip_list"]').val(JSON.stringify(ip_list))} -if(user_list){$('[name="user_list"]').val(JSON.stringify(user_list))} -if(pwd_list){$('[name="pwd_list"]').val(JSON.stringify(pwd_list))} -if(type_list){$('[name="type_list"]').val(JSON.stringify(type_list))} -$('#form_list').submit()})})},init_add:function(){var $_o=this;var formid='#win_form_proxy_add';$(formid+' .proxy-ip-list').attr('data-tpl',''+$(formid+' .tpl-proxy-ip').html()+'');$(formid+' .tpl-proxy-ip').remove();$(formid+' .add-proxy-ip').bind('click',function(){$(formid+' .proxy-ip-list tbody').append($(formid+' .proxy-ip-list').attr('data-tpl'))});$(formid).on('click','.op-delete',function(){$(this).parents('tr').eq(0).remove()});$(formid).bind('submit',function(){ajaxOpen({type:'post',dataType:'json',url:$(this).attr('action'),data:$(this).serialize(),success:function(data){if(data.code==1){$_o.reload_iframe('添加成功')}else{toastr.error(data.msg)}}});return!1})},init_batch:function(){var $_o=this;var formid='#win_form_proxy_batch';$(formid+' .format a[data-val]').bind('click',function(){var obj=$('#win_form_proxy_batch input[name="format"]');insertAtCaret(obj,$(this).attr('data-val'))});$(formid+' .btn-test').bind('click',function(){$(formid).find('[name="is_test"]').val(1);ajaxOpen({type:'POST',dataType:'json',url:$(formid).attr('action'),data:$(formid).serialize(),success:function(data){if(data.code==1){$(formid+' .test-result').show();$(formid+' .test-result').find('textarea').val(data.msg)}else{toastr.error(data.msg)}}})});$(formid).bind('submit',function(){$(formid).find('[name="is_test"]').val('');ajaxOpen({type:'POST',dataType:'json',url:$(this).attr('action'),data:$(this).serialize(),success:function(data){if(data.code==1){$_o.reload_iframe('添加成功')}else{toastr.error(data.msg)}},});return!1})},} +$('#form_list').on('change','[data-name="ip_list[]"],[data-name="user_list[]"],[data-name="pwd_list[]"],[data-name="type_list[]"],[data-name="gid_list[]"]',function(){$(this).parents('tr').eq(0).find('[data-name="ips[]"]').prop('checked',!0)});$('#form_list').on('click','.op-delete',function(){var tr=$(this).parents('tr').eq(0);var ip=tr.find('[data-name="ips[]"]').val();ajaxOpen({type:'get',dataType:'json',url:ulink('proxy/op?op=delete&ip=_ip_',{'_ip_':ip}),success:function(data){if(data.code==1){tr.fadeOut(100,function(){tr.remove()});toastr.success(data.msg)}else{toastr.error(data.msg)}}})});$('#form_list').on('click','.check-all-ip',function(){var checked=$(this).is(":checked")?true:!1;$('[data-name="ips[]"]').prop('checked',checked)});$('#form_list').on('click','.delete-all-ip',function(){confirmRight('确定删除选中的IP?',function(){$('#form_list').find('[name="op"]').val('delete_all');var ips=new Array();$('#form_list').find('[data-name="ips[]"]').each(function(){if($(this).is(':checked')){ips.push($(this).val())}});if(ips){$('[name="ips"]').val(JSON.stringify(ips))} +$('#form_list').submit()})});$('#form_list').on('click','.update-all-ip',function(){confirmRight('确定修改?',function(){$('#form_list').find('[name="op"]').val('update_all');var ips=new Array();var paramNames=['ip_list','user_list','pwd_list','type_list','gid_list'];var paramDatas={};for(var i in paramNames){paramDatas[paramNames[i]]=new Array()} +$('#form_list').find('[data-name="ips[]"]').each(function(){if($(this).is(':checked')){ips.push($(this).val());var tr=$(this).parents('tr').eq(0);for(var paramName in paramDatas){paramDatas[paramName].push(tr.find('[data-name="'+paramName+'[]"]').val())}}});if(ips){$('[name="ips"]').val(JSON.stringify(ips))} +for(var paramName in paramDatas){if(paramDatas[paramName]){$('[name="'+paramName+'"]').val(JSON.stringify(paramDatas[paramName]))}} +$('#form_list').submit()})})},init_add:function(){var $_o=this;var formid='#win_form_proxy_add';$(formid+' .proxy-ip-list').attr('data-tpl',''+$(formid+' .tpl-proxy-ip').html()+'');$(formid+' .tpl-proxy-ip').remove();$(formid+' .add-proxy-ip').bind('click',function(){$(formid+' .proxy-ip-list tbody').append($(formid+' .proxy-ip-list').attr('data-tpl'))});$(formid).on('click','.op-delete',function(){$(this).parents('tr').eq(0).remove()});$(formid).bind('submit',function(){ajaxOpen({type:'post',dataType:'json',url:$(this).attr('action'),data:$(this).serialize(),success:function(data){if(data.code==1){$_o.reload_iframe('添加成功')}else{toastr.error(data.msg)}}});return!1})},init_batch:function(){var $_o=this;var formid='#win_form_proxy_batch';$(formid+' .format a[data-val]').bind('click',function(){var obj=$('#win_form_proxy_batch input[name="format"]');insertAtCaret(obj,$(this).attr('data-val'))});$(formid+' .btn-test').bind('click',function(){$(formid).find('[name="is_test"]').val(1);ajaxOpen({type:'POST',dataType:'json',url:$(formid).attr('action'),data:$(formid).serialize(),success:function(data){if(data.code==1){$(formid+' .test-result').show();$(formid+' .test-result').find('textarea').val(data.msg)}else{toastr.error(data.msg)}}})});$(formid).bind('submit',function(){$(formid).find('[name="is_test"]').val('');ajaxOpen({type:'POST',dataType:'json',url:$(this).attr('action'),data:$(this).serialize(),success:function(data){if(data.code==1){$_o.reload_iframe('添加成功')}else{toastr.error(data.msg)}},});return!1})},init_groups:function(groups){var $_o=this;var formid='#win_form_proxy_groups';$(formid+' .proxy-group-list').attr('data-tpl',''+$(formid+' .proxy-group-tpl').html()+'');$(formid+' .proxy-group-tpl').remove();$(formid+' .proxy-group-add').bind('click',function(){$(formid+' .proxy-group-list tbody').append($(formid+' .proxy-group-list').attr('data-tpl'))});$(formid).on('click','.proxy-group-delete',function(){var prtObj=$(this).parents('tr').eq(0);var groupId=prtObj.find('[name="group_id[]"]').val();confirmRight('确定删除该分组?',function(){ajaxOpen({type:'get',dataType:'json',url:ulink('proxy/delete_group?id='+groupId),success:function(data){if(data.code==1){prtObj.remove();if(data.msg){toastr.success(data.msg)}}}})})});if(isObject(groups)){for(var i in groups){var tpl=$(formid+' .proxy-group-list').attr('data-tpl');tpl=$(tpl);var groupData=groups[i];if(isObject(groupData)){tpl.find('[name="group_id[]"]').val(groupData.id);tpl.find('[name="group_sort[]"]').val(groupData.sort);tpl.find('[name="group_name[]"]').val(groupData.name);if(groupData._ip_num){tpl.find('.proxy-group-ip-num').html(groupData._ip_num+'个IP')}} +$(formid+' .proxy-group-list tbody').append(tpl)}}},} var proxyClass=new ProxyClass() \ No newline at end of file diff --git a/public/static/js/admin/release.js b/public/static/js/admin/release.js index b67aaef..9e91a68 100644 --- a/public/static/js/admin/release.js +++ b/public/static/js/admin/release.js @@ -8,20 +8,30 @@ |-------------------------------------------------------------------------- */ 'use strict';function ReleaseClass(formid,releid){this.formid='#'+formid;this.releid=releid} -ReleaseClass.prototype={constructor:ReleaseClass,init:function(){var $_o=this;$($_o.formid+' select[name="module"]').bind('change',function(){$($_o.formid+' .rele-module').hide();$($_o.formid+' .rele-module[module="'+$(this).val()+'"]').show()});$('#btn_import_release').bind('click',function(){windowModal('导入配置会覆盖当前任务的发布设置,且不可恢复',ulink('release/import'))});$('#rele_module_cms .btn-cms-detect').bind('click',function(){$_o.cms_detect()});$('#rele_module_cms .btn-cms-bind').bind('click',function(){$_o.cms_bind()});$('#rele_module_cms').on('change','select[name="cms[app]"]',function(){var cmsApp=$(this).val();$_o.cms_bind({cms:{app:cmsApp}})});$('#cms_list').on('click','li a',function(){var path=$(this).attr('path');if(path){$($_o.formid+' [name="cms[path]"]').val(path);$('#cms_tab a[href="#cms_tab_bind"]').tab('show');$_o.cms_bind()}});$('#rele_module_cms').on('change','select[name^="cms_app[param]"]',function(){var cusName=$(this).attr('name').replace('cms_app[param]','cms_app[custom]');if($(this).val()=='custom:'){$('input[name="'+cusName+'"]').show()}else{$('input[name="'+cusName+'"]').hide()}});$('#db_tab_config .dm-db-charset li span').bind('click',function(){var charset=$(this).attr('data-val');charset=charset?charset:'';$('#db_tab_config [name="db[charset]"]').val(charset)});$('#db_tab_config .btn-db-names').bind('click',function(){$_o.db_connect('db_names')});$('#db_tab_config .btn-db-connect').bind('click',function(){$_o.db_connect()});$('#db_tab_table').on('change','select[name^="db_table[field]"]',function(){var cusName=$(this).attr('name').replace('db_table[field]','db_table[custom]');if($(this).val()=='custom:'){$('input[name="'+cusName+'"]').show()}else{$('input[name="'+cusName+'"]').hide()}});$('#rele_module_file').on('click','.btn-file-rand-path',function(){var randStr=$_o.rand_str(10);$($_o.formid+' [name="file[path]"]').val(randStr)});$('#rele_module_api').on('click','.btn-api-rand-url',function(){var randStr=$_o.rand_str(10);$($_o.formid+' [name="api[url]"]').val(randStr)});$('#diy_tab').on('click','[data-type]',function(){$($_o.formid+' [name="diy[type]"]').val($(this).attr('data-type'))});$('#rele_module_toapi').on('change','[name="toapi[type]"]',function(){if($(this).val()=='post'){$('#rele_module_toapi .toapi-content-type').show()}else{$('#rele_module_toapi .toapi-content-type').hide()}});$('#rele_module_toapi').on('click','.toapi-add-param',function(){$_o.toapi_add_param(null,null)});$('#rele_module_toapi').on('click','.toapi-del-param',function(){$(this).parents('tr').eq(0).remove()});inputSelectCustom('#rele_module_toapi [name="toapi[charset]"]','toapi[charset_custom]');inputSelectCustom(null,null,{box:'#rele_module_toapi',slt:'[name="toapi[param_val][]"]',ipt:'[name="toapi[param_addon][]"]'});$('#rele_module_toapi').on('click','.toapi-add-header',function(){$_o.toapi_add_header(null,null)});$('#rele_module_toapi').on('click','.toapi-del-header',function(){$(this).parents('tr').eq(0).remove()});inputSelectCustom(null,null,{box:'#rele_module_toapi',slt:'[name="toapi[header_val][]"]',ipt:'[name="toapi[header_addon][]"]'})},load:function(data){var $_o=this;if(data.module){$($_o.formid+' select[name="module"]').val(data.module).trigger('change')} -if(data.config){if('cms'==data.module){$_o.cms_bind(data.config);$(document).ready(function(){$('#cms_tab a[href="#cms_tab_bind"]').tab('show')})}else if('db'==data.module){$_o.db_bind(data.config)}else if('file'==data.module){if(data.config.file){$($_o.formid+' [name="file[path]"]').val(data.config.file.path);$($_o.formid+' [name="file[type]"]').each(function(){if($(this).val()==data.config.file.type){$(this).prop('checked',!0)}});$($_o.formid+' [name="file[txt_implode]"]').val(data.config.file.txt_implode);if(data.config.file.hide_fields){for(var fi in data.config.file.hide_fields){$($_o.formid+' [name="file[hide_fields][]"][value="'+data.config.file.hide_fields[fi]+'"]').prop('checked',!0)}}}}else if('api'==data.module){if(data.config.api){$($_o.formid+' [name="api[url]"]').val(data.config.api.url);$($_o.formid+' [name="api[cache_time]"]').val(data.config.api.cache_time);if(data.config.api.hide_fields){for(var fi in data.config.api.hide_fields){$($_o.formid+' [name="api[hide_fields][]"][value="'+data.config.api.hide_fields[fi]+'"]').prop('checked',!0)}}}}else if('diy'==data.module){if(data.config.diy){$(document).ready(function(){$('#diy_tab a[href="#diy_tab_'+data.config.diy.type+'"]').tab('show');for(var i in data.config.diy){$($_o.formid+' [name="diy['+i+']"]').val(data.config.diy[i])} +ReleaseClass.prototype={constructor:ReleaseClass,init:function(){var $_o=this;$($_o.formid).bind('submit',function(){var module=$($_o.formid+' select[name="module"]').val();if(module=='diy'&&$_o.has_diy_editor()){var diyCode=editorCodeIfr('#diy_editor_ifr',{'get_value':1});if(diyCode){$($_o.formid+' [name="diy[code]"]').val(diyCode)}} +var settings=getFormAjaxSettings($(this));ajaxOpen(settings);return!1});$($_o.formid+' select[name="module"]').bind('change',function(){var module=$(this).val();$($_o.formid+' .rele-module').hide();$($_o.formid+' .rele-module[module="'+module+'"]').show()});$('#btn_import_release').bind('click',function(){windowModal('导入配置会覆盖当前任务的发布设置,且不可恢复',ulink('release/import'))});$('#rele_module_cms .btn-cms-detect').bind('click',function(){$_o.cms_detect()});$('#rele_module_cms .btn-cms-bind').bind('click',function(){$_o.cms_bind()});$('#rele_module_cms').on('change','select[name="cms[app]"]',function(){var cmsApp=$(this).val();$_o.cms_bind({cms:{app:cmsApp}})});$('#cms_list').on('click','li a',function(){var path=$(this).attr('path');if(path){$($_o.formid+' [name="cms[path]"]').val(path);$('#cms_tab a[href="#cms_tab_bind"]').tab('show');$_o.cms_bind()}});$('#rele_module_cms').on('change','select[name^="cms_app[param]"]',function(){var cusName=$(this).attr('name').replace('cms_app[param]','cms_app[custom]');if($(this).val()=='custom:'){$('input[name="'+cusName+'"]').show()}else{$('input[name="'+cusName+'"]').hide()}});$('#db_tab_config .dm-db-charset li span').bind('click',function(){var charset=$(this).attr('data-val');charset=charset?charset:'';$('#db_tab_config [name="db[charset]"]').val(charset)});$('#db_tab_config .btn-db-names').bind('click',function(){$_o.db_connect('db_names')});$('#db_tab_config .btn-db-connect').bind('click',function(){$_o.db_connect()});$('#rele_module_file').on('click','.btn-file-rand-path',function(){var randStr=$_o.rand_str(10);$($_o.formid+' [name="file[path]"]').val(randStr)});$('#rele_module_api').on('click','.btn-api-rand-url',function(){var randStr=$_o.rand_str(10);$($_o.formid+' [name="api[url]"]').val(randStr)});$('#diy_tab').on('click','[data-type]',function(){var type=$(this).attr('data-type');$($_o.formid+' [name="diy[type]"]').val(type);if(type=='code'&&$_o.has_diy_editor()){var diyCode=$($_o.formid+' [name="diy[code]"]').val();diyCode=diyCode?diyCode:'';editorCodeIfr('#diy_editor_ifr',{'set_value':diyCode})}});$('#rele_module_toapi').on('change','[name="toapi[type]"]',function(){if($(this).val()=='post'){$('#rele_module_toapi .toapi-content-type').show()}else{$('#rele_module_toapi .toapi-content-type').hide()}});$('#rele_module_toapi').on('click','.toapi-add-param',function(){$_o.toapi_add_param(null,null)});$('#rele_module_toapi').on('click','.toapi-del-param',function(){$(this).parents('tr').eq(0).remove()});inputSelectCustom('#rele_module_toapi [name="toapi[charset]"]','toapi[charset_custom]');inputSelectCustom('#rele_module_toapi [name="toapi[encode]"]','toapi[encode_custom]');inputSelectCustom(null,null,{box:'#rele_module_toapi',slt:'[name="toapi[param_val][]"]',ipt:'[name="toapi[param_addon][]"]'});$('#rele_module_toapi').on('click','.toapi-add-header',function(){$_o.toapi_add_header(null,null)});$('#rele_module_toapi').on('click','.toapi-del-header',function(){$(this).parents('tr').eq(0).remove()});inputSelectCustom(null,null,{box:'#rele_module_toapi',slt:'[name="toapi[header_val][]"]',ipt:'[name="toapi[header_addon][]"]'});$('#btn_rele_test').bind('click',function(){collectorWindow('测试','admin/release/test?id='+$_o.releid,null,{lg:1})})},load:function(data){var $_o=this;if(data.module){$($_o.formid+' select[name="module"]').val(data.module).trigger('change')} +if(data.config){if('cms'==data.module){$_o.cms_bind(data.config);$(document).ready(function(){$('#cms_tab a[href="#cms_tab_bind"]').tab('show')})}else if('db'==data.module){$_o.db_bind(data.config)}else if('file'==data.module){if(data.config.file){$($_o.formid+' [name="file[path]"]').val(data.config.file.path);$($_o.formid+' [name="file[type]"]').each(function(){if($(this).val()==data.config.file.type){$(this).prop('checked',!0)}});$($_o.formid+' [name="file[txt_implode]"]').val(data.config.file.txt_implode);if(data.config.file.hide_fields){for(var fi in data.config.file.hide_fields){$($_o.formid+' [name="file[hide_fields][]"][value="'+data.config.file.hide_fields[fi]+'"]').prop('checked',!0)}}}}else if('api'==data.module){if(data.config.api){$($_o.formid+' [name="api[url]"]').val(data.config.api.url);$($_o.formid+' [name="api[cache_time]"]').val(data.config.api.cache_time);if(data.config.api.hide_fields){for(var fi in data.config.api.hide_fields){$($_o.formid+' [name="api[hide_fields][]"][value="'+data.config.api.hide_fields[fi]+'"]').prop('checked',!0)}}}}else if('diy'==data.module){if(data.config.diy){$(document).ready(function(){$('#diy_tab a[href="#diy_tab_'+data.config.diy.type+'"]').tab('show').trigger('click');for(var i in data.config.diy){$($_o.formid+' [name="diy['+i+']"]').val(data.config.diy[i])} if(data.config.diy.app){var appName=data.config.diy.app;if(appName.length>1){appName=appName.substr(0,1).toUpperCase()+appName.substr(1).toLowerCase()}else{appName=appName.toUpperCase()} -$($_o.formid+' [name="diy[app]"]').parent().find('.diy-app-name').text(appName+'.php')}})}}else if('toapi'==data.module){var config=data.config.toapi;if(config){$($_o.formid+' [name="toapi[url]"]').val(config.url);$($_o.formid+' [name="toapi[type]"]').val(config.type).trigger('change');$($_o.formid+' [name="toapi[content_type]"]').val(config.content_type);$($_o.formid+' [name="toapi[charset_custom]"]').val(config.charset_custom);$($_o.formid+' [name="toapi[charset]"]').val(config.charset).trigger('change');if(config.response){for(var i in config.response){$($_o.formid+' [name="toapi[response]['+i+']"]').val(config.response[i])}} +$($_o.formid+' [name="diy[app]"]').parent().find('.diy-app-name').text(appName+'.php');$($_o.formid+' [name="diy[app]"]').parent().find('.diy-app-editor').show().find('.btn_diy_editor').attr('href',ulink('develop/editor?type=release&module=diy&app=_app_',{'_app_':appName}))} +if($_o.has_diy_editor()&&data.config.diy.code){editorCodeIfr('#diy_editor_ifr',{'set_value':data.config.diy.code})}})}}else if('toapi'==data.module){var config=data.config.toapi;if(config){$($_o.formid+' [name="toapi[url]"]').val(config.url);$($_o.formid+' [name="toapi[type]"]').val(config.type).trigger('change');$($_o.formid+' [name="toapi[content_type]"]').val(config.content_type);$($_o.formid+' [name="toapi[charset_custom]"]').val(config.charset_custom);$($_o.formid+' [name="toapi[charset]"]').val(config.charset).trigger('change');$($_o.formid+' [name="toapi[encode_custom]"]').val(config.encode_custom);$($_o.formid+' [name="toapi[encode]"]').val(config.encode).trigger('change');if(config.response){for(var i in config.response){$($_o.formid+' [name="toapi[response]['+i+']"]').val(config.response[i])}} if(config.param_name){config.param_val=config.param_val?config.param_val:{};config.param_addon=config.param_addon?config.param_addon:{};for(var i in config.param_name){var pname=config.param_name[i]?config.param_name[i]:'';var pval=config.param_val[i]?config.param_val[i]:'';var paddon=config.param_addon[i]?config.param_addon[i]:'';$_o.toapi_add_param({name:pname,val:pval,addon:paddon},i)}} if(config.header_name){config.header_val=config.header_val?config.header_val:{};config.header_addon=config.header_addon?config.header_addon:{};for(var i in config.header_name){var hname=config.header_name[i]?config.header_name[i]:'';var hval=config.header_val[i]?config.header_val[i]:'';var haddon=config.header_addon[i]?config.header_addon[i]:'';$_o.toapi_add_header({name:hname,val:hval,addon:haddon},i)}} $($_o.formid+' [name="toapi[interval]"]').val(toInt(config.interval));$($_o.formid+' [name="toapi[wait]"]').val(toInt(config.wait));$($_o.formid+' [name="toapi[retry]"]').val(toInt(config.retry))}}}},cms_detect:function(){var $_o=this;$('#cms_list').html('').addClass('loading');ajaxOpen({type:'get',url:ulink("release/cmsDetect"),dataType:'json',success:function(data){$('#cms_list').removeClass('loading');if(data.code==1){var html='

    点击选择CMS

    ';for(var x in data.data){var list=data.data[x];html+=''} $('#cms_list').html(html)}else{$('#cms_list').html(data.msg)}}})},cms_bind:function(config){var $_o=this;$('#cms_bind').html('').addClass('loading');var postData=$($_o.formid).serialize();if(config&&config.cms&&config.cms.app){postData='cms[app]='+encodeURIComponent(config.cms.app)+'&'+postData} ajaxOpen({type:'post',url:ulink("release/cmsBind"),dataType:'html',data:postData,success:function(data,textStatus,request){$('#cms_bind').removeClass('loading').show();if((/application\/json/i).test(request.getResponseHeader('Content-Type'))){data=jQuery.parseJSON(data);$('#cms_bind').html(''+data.msg+'')}else{$('#cms_bind').html(data);if(config&&config.cms_app){if(config.cms_app.param){for(var f in config.cms_app.param){var paramEle=$('#cms_bind').find('[name="cms_app[param]['+f+']"]');if(paramEle.is('select')){paramEle.val(config.cms_app.param[f]).trigger('change')}else if(paramEle.is('input:radio')){$('#cms_bind').find('[name="cms_app[param]['+f+']"][value="'+config.cms_app.param[f]+'"]').prop('checked','checked')}else{paramEle.val(config.cms_app.param[f])}} -if(config.cms_app.custom){for(var f in config.cms_app.custom){$('#cms_bind').find('[name="cms_app[custom]['+f+']"]').val(config.cms_app.custom[f])}}}}}},error:function(XMLHttpRequest,textStatus,errorThrown){$('#cms_bind').removeClass('loading').show();$('#cms_bind').html(XMLHttpRequest.responseText)}})},db_bind:function(config){var $_o=this;$($_o.formid+' select[name="db[type]"]').val(config.db.type);$(document).ready(function(){$('#db_tab a[href="#db_tab_table"]').tab('show');$('#db_tab_table .db-table-list').html('').addClass('loading');ajaxOpen({type:'get',url:ulink("release/dbTables?id=_id_",{_id_:$_o.releid}),timeout:10000,dataType:'json',success:function(data){if(data.code==1){$('#db_tab_table .db-table-list').html(data.msg)}else{$('#db_tab_table .db-table-list').css('color','red').html(data.msg)}},complete:function(XMLHttpRequest,status){$('#db_tab_table .db-table-list').removeClass('loading');if(status=='timeout'){$('#db_tab_table .db-table-list').css('color','red').html('数据库连接超时')}}});$('#db_tab_table .db-table-list').on('click','.btn-db-table-bind',function(){var curTable=$(this).parents('.db-table-list').eq(0).find('.db-table-select').val();$_o.db_table_bind(curTable)});eleExchange('#db_table_bind_list','.icon-drag-move','.panel-default');$('#db_table_bind_list').on('click','.glyphicon-remove',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){obj.parents('.panel').eq(0).remove()})});$('#db_table_bind_list').on('change','select[name^="field"]',function(){if($(this).val()=='custom:'){$(this).siblings('input[name^="custom"]').show()}});if(config.db_table&&config.db_table.field){var tables=new Array();for(var table in config.db_table.field){tables.push(table)} -tables=tables.join(',');$('#db_tab_table .db-table-binding').addClass('loading');$_o.db_table_bind(tables)}})},db_table_bind:function(curTable){var $_o=this;var bindUrl=ulink("release/dbTableBind?id=_id_&table=_table_",{_id_:$_o.releid,_table_:curTable});if($('#db_table_bind_list').find('[id^="db_table_name_'+curTable+'"]').length>0){toastr.error('已绑定该表')}else{ajaxOpen({type:'get',url:bindUrl,dataType:'html',success:function(data){if(dataIsJson(data)){ajaxDataMsg(data)}else{$('#db_table_bind_list').append(data)}},complete:function(){$('#db_tab_table .db-table-binding').removeClass('loading').hide()}})}},db_connect:function(op){op=op?op:'';var $_o=this;$('#db_tab_config .rele-db-error').html('').addClass('loading');ajaxOpen({type:'post',url:ulink("release/dbConnect?op="+op),timeout:10000,dataType:'json',data:$($_o.formid).serialize(),success:function(data){if(data.code==1){if(op=='db_names'){modal('选择数据库',data.msg)}else{$('#db_tab_config .rele-db-error').css('color','green').html(data.msg)}}else{toastr.error(data.msg);$('#db_tab_config .rele-db-error').css('color','red').html(data.msg)}},complete:function(XMLHttpRequest,status){$('#db_tab_config .rele-db-error').removeClass('loading');if(status=='timeout'){$('#db_tab_config .rele-db-error').css('color','red').html('数据库连接超时')}}})},toapi_add_param:function(param,index){var $_o=this;var paramTable=$('#rele_module_toapi').find('.toapi-param-table');if(!paramTable.attr('data-tpl')){var paramTpl=$('#rele_module_toapi').find('.toapi-param-tpl');paramTable.attr('data-tpl',paramTpl.html());paramTpl.remove()} +if(config.cms_app.custom){for(var f in config.cms_app.custom){$('#cms_bind').find('[name="cms_app[custom]['+f+']"]').val(config.cms_app.custom[f])}}}}}},error:function(XMLHttpRequest,textStatus,errorThrown){$('#cms_bind').removeClass('loading').show();$('#cms_bind').html(XMLHttpRequest.responseText)}})},db_bind:function(config){var $_o=this;$($_o.formid+' select[name="db[type]"]').val(config.db.type);$(document).ready(function(){$('#db_tab a[href="#db_tab_table"]').tab('show');$('#db_tab_table .db-table-list').html('').addClass('loading');ajaxOpen({type:'get',url:ulink("release/dbTables?id=_id_",{_id_:$_o.releid}),timeout:10000,dataType:'json',success:function(data){if(data.code==1){$('#db_tab_table .db-table-list').html(data.msg)}else{$('#db_tab_table .db-table-list').css('color','red').html(data.msg)}},complete:function(XMLHttpRequest,status){$('#db_tab_table .db-table-list').removeClass('loading');if(status=='timeout'){$('#db_tab_table .db-table-list').css('color','red').html('数据库连接超时')}}});$('#db_tab_table .db-table-list').on('click','.btn-db-table-bind',function(){var curTable=$(this).parents('.db-table-list').eq(0).find('.db-table-select').val();$_o.db_table_bind(curTable)});eleExchange('#db_table_bind_list','.db-table-bind-move','[id^="db_table_t_"]');$('#db_table_bind_list').on('click','.db-table-bind-del',function(){var obj=$(this);confirmRight(window.tpl_lang.confirm_delete,function(){obj.parents('[id^="db_table_t_"]').eq(0).remove()})});$('#db_table_bind_list').on('change','.db-table-bind-op',function(){var prtObj=$(this).parents('[id^="db_table_t_"]').eq(0);prtObj.find('.db-table-bind-where').hide();prtObj.find('.db-table-bind-query').hide();prtObj.find('.db-table-bind-data').hide();prtObj.find('.db-table-bind-data-seq').hide();var val=$(this).val();var showData=!1;if(!val){showData=!0;prtObj.find('.db-table-bind-data').show()}else if(val=='update'){showData=!0;prtObj.find('.db-table-bind-where').show();prtObj.find('.db-table-bind-data').show()}else if(val=='query'){prtObj.find('.db-table-bind-where').show();prtObj.find('.db-table-bind-query').show()} +if(showData){var seqObj=prtObj.find('.db-table-bind-data-seq');if(seqObj.length>0){seqObj.show()}}});$('#db_table_bind_list').on('click','.db-table-bind-signs .btn-db-table-bind-signs',function(){var boxObj=$(this).parents('.db-table-bind-signs').eq(0);if(!boxObj||boxObj.length<=0){return} +var dropdownMenu=boxObj.find('.dropdown-menu');if(dropdownMenu.length>0){dropdownMenu.html('');var key=$(this).parents('[id^="db_table_t_"]').eq(0).attr('data-key');ajaxOpen({type:'POST',dataType:'html',url:ulink('release/dbTableBindSings?table_key=_key_',{'_key_':key}),data:$($_o.formid).serialize(),success:function(html){dropdownMenu.html(html);dropdownMenu.find('a[data-val]').bind('click',function(){insertAtCaret($(this).parents('.db-table-bind-signs').eq(0).find('input[name^="db_tables"]').eq(0),$(this).attr('data-val'))})}})}});$('#db_table_bind_list').on('click','.db-table-bind-where-add',function(){$_o.db_table_bind_where_add(this)});$('#db_table_bind_list').on('click','.db-table-bind-where-del',function(){$(this).parents('tr').eq(0).remove()});$('#db_table_bind_list').on('click','.db-table-bind-query-add',function(){$_o.db_table_bind_query_add(this)});$('#db_table_bind_list').on('click','.db-table-bind-query-del',function(){$(this).parents('tr').eq(0).remove()});if(isObject(config.db_tables)&&config.db_tables.length>0){$('#db_tab_table .db-table-binding').addClass('loading');$_o.db_table_bind(null,1)}})},db_table_bind:function(curTable,isDbTables){var $_o=this;curTable=curTable?curTable:'';isDbTables=isDbTables?isDbTables:'';var bindUrl=ulink('release/dbTableBind?id=_id_&table=_tb_&is_db_tables=_dbtb_',{'_id_':$_o.releid,'_tb_':curTable,'_dbtb_':isDbTables});ajaxOpen({type:'get',url:bindUrl,dataType:'html',success:function(data){if(dataIsJson(data)){ajaxDataMsg(data)}else{$('#db_table_bind_list').append(data)}},complete:function(){$('#db_tab_table .db-table-binding').removeClass('loading').hide()}})},db_table_bind_load:function(dbTables){var $_o=this;if(isObject(dbTables)){for(var key in dbTables){var tBoxId='#db_table_t_'+key;var namePre='db_tables['+key+']';var dbTable=dbTables[key];if(isObject(dbTable)){$(tBoxId).find('[name="'+namePre+'[op]').val(dbTable.op).trigger('change');var opTypes=['where','query'];for(var opi in opTypes){var opType=opTypes[opi];if(isObject(dbTable[opType])){for(var i in dbTable[opType]){if(!isObject(dbTable[opType])){dbTable[opType][i]=[]}} +for(var i in dbTable[opType].field){var opData={};for(var ii in dbTable[opType]){opData[ii]=dbTable[opType][ii][i]} +$_o.db_table_bind_op_add(opType,key,opData)}}} +if(isObject(dbTable.query)){for(var qk in dbTable.query){$(tBoxId).find('[name="'+namePre+'[field]['+qk+']"]').val(dbTable.query[qk])}} +if(isObject(dbTable.field)){for(var fk in dbTable.field){$(tBoxId).find('[name="'+namePre+'[field]['+fk+']"]').val(dbTable.field[fk])}} +if(isObject(dbTable.sequence)){for(var sk in dbTable.sequence){$(tBoxId).find('[name="'+namePre+'[sequence]['+sk+']"]').val(dbTable.sequence[sk])}} +eleExchange(tBoxId+' .db-table-bind-where','.db-table-bind-where-move','tbody tr')}}}},db_table_bind_where_add:function(curObj,whereData){var key=$(curObj).parents('[id^="db_table_t_"]').eq(0).attr('data-key');this.db_table_bind_op_add('where',key,whereData)},db_table_bind_query_add:function(curObj,queryData){var key=$(curObj).parents('[id^="db_table_t_"]').eq(0).attr('data-key');this.db_table_bind_op_add('query',key,queryData)},db_table_bind_op_add:function(opType,key,opData){if(opType&&key){var boxObj=$('#db_table_bind_list').find('#db_table_t_'+key);if(boxObj.length>0){var opTypeClass='.db-table-bind-'+opType;var tbBox=boxObj.find(opTypeClass);if(tbBox.length>0){var tpl=tbBox.find(opTypeClass+'-tpl').clone();tpl.removeClass('db-table-bind-'+opType+'-tpl');if(isObject(opData)){for(var dk in opData){tpl.find('[data-name="['+opType+']['+dk+'][]"]').val(opData[dk])}} +tpl.find('[data-name]').each(function(){$(this).attr('name','db_tables['+key+']'+$(this).attr('data-name'));$(this).removeAttr('data-name')});tbBox.find('tbody').append(tpl)}}}},db_connect:function(op){op=op?op:'';var $_o=this;$('#db_tab_config .rele-db-error').html('').addClass('loading');ajaxOpen({type:'post',url:ulink("release/dbConnect?op="+op),timeout:10000,dataType:'json',data:$($_o.formid).serialize(),success:function(data){if(data.code==1){if(op=='db_names'){modal('选择数据库',data.msg)}else{$('#db_tab_config .rele-db-error').css('color','green').html(data.msg)}}else{toastr.error(data.msg);$('#db_tab_config .rele-db-error').css('color','red').html(data.msg)}},complete:function(XMLHttpRequest,status){$('#db_tab_config .rele-db-error').removeClass('loading');if(status=='timeout'){$('#db_tab_config .rele-db-error').css('color','red').html('数据库连接超时')}}})},toapi_add_param:function(param,index){var $_o=this;var paramTable=$('#rele_module_toapi').find('.toapi-param-table table');if(!paramTable.attr('data-tpl')){var paramTpl=$('#rele_module_toapi').find('.toapi-param-tpl');paramTable.attr('data-tpl',paramTpl.html());paramTpl.remove()} param=param?param:{};if(!index){index=generateUUID()} -paramTable.find('tbody').append(''+paramTable.attr('data-tpl')+'');var curTr=paramTable.find('[data-param-id="'+index+'"]');curTr.find('[name="toapi[param_name][]"]').val(param.name?param.name:'');curTr.find('[name="toapi[param_val][]"]').val(param.val?param.val:'').trigger('change');curTr.find('[name="toapi[param_addon][]"]').val(param.addon?param.addon:'')},toapi_add_header:function(header,index){var $_o=this;var headerTable=$('#rele_module_toapi').find('.toapi-header-table');if(!headerTable.attr('data-tpl')){var headerTpl=$('#rele_module_toapi').find('.toapi-header-tpl');headerTable.attr('data-tpl',headerTpl.html());headerTpl.remove()} +paramTable.find('tbody').append(''+paramTable.attr('data-tpl')+'');var curTr=paramTable.find('[data-param-id="'+index+'"]');curTr.find('[name="toapi[param_name][]"]').val(param.name?param.name:'');curTr.find('[name="toapi[param_val][]"]').val(param.val?param.val:'').trigger('change');curTr.find('[name="toapi[param_addon][]"]').val(param.addon?param.addon:'')},toapi_add_header:function(header,index){var $_o=this;var headerTable=$('#rele_module_toapi').find('.toapi-header-table table');if(!headerTable.attr('data-tpl')){var headerTpl=$('#rele_module_toapi').find('.toapi-header-tpl');headerTable.attr('data-tpl',headerTpl.html());headerTpl.remove()} header=header?header:{};if(!index){index=generateUUID()} headerTable.find('tbody').append(''+headerTable.attr('data-tpl')+'');var curTr=headerTable.find('[data-header-id="'+index+'"]');curTr.find('[name="toapi[header_name][]"]').val(header.name?header.name:'');curTr.find('[name="toapi[header_val][]"]').val(header.val?header.val:'').trigger('change');curTr.find('[name="toapi[header_addon][]"]').val(header.addon?header.addon:'')},rand_str:function(len){var chars='ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';  var maxPos=chars.length;  var str='';  for(var i=0;i0?true:!1}} \ No newline at end of file diff --git a/public/static/js/admin/setting.js b/public/static/js/admin/setting.js index 37d90ea..3b13c35 100644 --- a/public/static/js/admin/setting.js +++ b/public/static/js/admin/setting.js @@ -8,7 +8,7 @@ |-------------------------------------------------------------------------- */ 'use strict';function SettingClass(){this.caijiForm='#form_set';this.downImgForm='#form_set';this.pageRenderForm='#form_set';this.transForm='#form_set';this.siteForm='#form_set';this.emailForm='#form_set';this.storeForm='#form_set'} -SettingClass.prototype={constructor:SettingClass,caiji_init:function(caijiConfig){var $_o=this;caijiConfig=caijiConfig?caijiConfig:{};$($_o.caijiForm+' [name="auto"]').bind('click',function(){if($(this).val()>0){$('#set_caiji_run').show()}else{$('#set_caiji_run').hide()}});$($_o.caijiForm+' [name="run"]').bind('change',function(){$('#set_caiji_run .help-block').hide();$('#set_caiji_run .run-'+$(this).val()).show()});$($_o.caijiForm+' [name="server"]').bind('change',function(){$('#set_caiji_server .server-').hide();$('#set_caiji_server .server-cli').hide();$('#set_caiji_server .server-'+$(this).val()).show()});$('#btn_test_php').bind('click',function(){ajax_check_userpwd({type:'POST',dataType:'json',url:ulink('setting/test_php'),data:{php:$($_o.caijiForm+' [name="server_php"]').val()},beforeSend:function(){$('#btn_test_php').text('测试中...').attr('disabled',!0)},success:function(data){if(data.code==1){toastr.success(data.msg)}else{toastr.error(data.msg)}},complete:function(){$('#btn_test_php').text('测试').removeAttr('disabled')}})});$($_o.caijiForm).bind('submit',function(){var formObj=$(this);var settings=getFormAjaxSettings(formObj);settings.complete=function(){formObj.find('button[type="submit"]').removeAttr('disabled')};ajax_check_userpwd(settings);return!1});$($_o.caijiForm+' [name="robots"][value="'+toInt(caijiConfig.robots)+'"]').prop('checked',!0);$($_o.caijiForm+' [name="auto"][value="'+toInt(caijiConfig.auto)+'"]').trigger("click");$($_o.caijiForm+' [name="run"]').val(caijiConfig.run?caijiConfig.run:'backstage').trigger("change");$($_o.caijiForm+' [name="server"]').val(caijiConfig.server).trigger("change");$($_o.caijiForm+' [name="same_url"][value="'+toInt(caijiConfig.same_url)+'"]').prop('checked',!0);$($_o.caijiForm+' [name="same_title"][value="'+toInt(caijiConfig.same_title)+'"]').prop('checked',!0);$($_o.caijiForm+' [name="real_time"][value="'+toInt(caijiConfig.real_time)+'"]').prop('checked',!0)},down_img_init:function(imgConfig){var $_o=this;imgConfig=imgConfig?imgConfig:{};$($_o.downImgForm+' [name="download_img"]').bind('click',function(){if($(this).val()==1){$('.content-wrapper').removeClass('wrapper-not-enable')}else{$('.content-wrapper').addClass('wrapper-not-enable')}});$($_o.downImgForm+' [name="img_name"]').bind('change',function(){if($(this).val()=='custom'){$('#img_name_custom').show()}else{$('#img_name_custom').hide()}});inputSelectCustom($_o.downImgForm+' [name="charset"]','charset_custom');$($_o.downImgForm+' .dropup-img-params .dropdown-menu a').bind('click',function(){var val=$(this).attr('data-val');if(val){var obj=$($_o.downImgForm).find('[name="img_func_param"]');var objVal=obj.val();objVal=objVal?(objVal+"\r\n"):'';obj.val(objVal+val)}});$($_o.downImgForm).on('click','.name-custom-path a[data-val]',function(){insertAtCaret($('[name="name_custom_path"]'),$(this).attr('data-val'))});for(var i in imgConfig){var ele=$($_o.downImgForm).find('[name="'+i+'"]').eq(0);if(!ele.is('input:radio')){ele.val(imgConfig[i])}} +SettingClass.prototype={constructor:SettingClass,caiji_init:function(caijiConfig){var $_o=this;caijiConfig=caijiConfig?caijiConfig:{};$($_o.caijiForm+' [name="auto"]').bind('click',function(){if($(this).val()>0){$('#set_caiji_run').show()}else{$('#set_caiji_run').hide()}});$($_o.caijiForm+' [name="run"]').bind('change',function(){$('#set_caiji_run .help-block').hide();$('#set_caiji_run .run-'+$(this).val()).show()});$($_o.caijiForm+' [name="server"]').bind('change',function(){$('#set_caiji_server .server-').hide();$('#set_caiji_server .server-cli').hide();$('#set_caiji_server .server-'+$(this).val()).show()});$('#btn_test_php').bind('click',function(){ajax_check_userpwd({type:'POST',dataType:'json',url:ulink('setting/test_php'),data:{php:$($_o.caijiForm+' [name="server_php"]').val()},beforeSend:function(){$('#btn_test_php').text('测试中...').attr('disabled',!0)},success:function(data){if(data.code==1){toastr.success(data.msg)}else{toastr.error(data.msg)}},complete:function(){$('#btn_test_php').text('测试').removeAttr('disabled')}})});$($_o.caijiForm).bind('submit',function(){var formObj=$(this);var settings=getFormAjaxSettings(formObj);settings.complete=function(){formObj.find('button[type="submit"]').removeAttr('disabled')};ajax_check_userpwd(settings);return!1});$($_o.caijiForm+' [name="robots"][value="'+toInt(caijiConfig.robots)+'"]').prop('checked',!0);$($_o.caijiForm+' [name="auto"][value="'+toInt(caijiConfig.auto)+'"]').trigger("click");$($_o.caijiForm+' [name="run"]').val(caijiConfig.run?caijiConfig.run:'backstage').trigger("change");$($_o.caijiForm+' [name="server"]').val(caijiConfig.server).trigger("change");$($_o.caijiForm+' [name="ip_resolve"]').val(caijiConfig.ip_resolve);$($_o.caijiForm+' [name="same_url"][value="'+toInt(caijiConfig.same_url)+'"]').prop('checked',!0);$($_o.caijiForm+' [name="same_title"][value="'+toInt(caijiConfig.same_title)+'"]').prop('checked',!0);$($_o.caijiForm+' [name="real_time"][value="'+toInt(caijiConfig.real_time)+'"]').prop('checked',!0);if(caijiConfig.ip_resolve||toInt(caijiConfig.max_redirs)>0){showPanelCollapse('#panel_others')}},down_img_init:function(imgConfig){var $_o=this;imgConfig=imgConfig?imgConfig:{};$($_o.downImgForm+' [name="download_img"]').bind('click',function(){if($(this).val()==1){$('.content-wrapper').removeClass('wrapper-not-enable')}else{$('.content-wrapper').addClass('wrapper-not-enable')}});$($_o.downImgForm+' [name="img_name"]').bind('change',function(){if($(this).val()=='custom'){$('#img_name_custom').show()}else{$('#img_name_custom').hide()}});inputSelectCustom($_o.downImgForm+' [name="charset"]','charset_custom');$($_o.downImgForm+' .dropup-img-params .dropdown-menu a').bind('click',function(){var val=$(this).attr('data-val');if(val){var obj=$($_o.downImgForm).find('[name="img_func_param"]');var objVal=obj.val();objVal=objVal?(objVal+"\r\n"):'';obj.val(objVal+val)}});$($_o.downImgForm).on('click','.name-custom-path a[data-val]',function(){insertAtCaret($('[name="name_custom_path"]'),$(this).attr('data-val'))});for(var i in imgConfig){var ele=$($_o.downImgForm).find('[name="'+i+'"]').eq(0);if(!ele.is('input:radio')){ele.val(imgConfig[i])}} if(imgConfig.img_func){showPanelCollapse('#panel_img_func')} $($_o.downImgForm+' [name="download_img"][value="'+toInt(imgConfig.download_img)+'"]').trigger("click");$($_o.downImgForm+' [name="data_image"][value="'+toInt(imgConfig.data_image)+'"]').trigger("click");$($_o.downImgForm+' [name="img_name"]').trigger("change");$($_o.downImgForm+' [name="charset"]').trigger("change");loadPluginFunc({module:'downloadImg',boxObj:$_o.downImgForm,funcObj:'[name="img_func"]',paramObj:'[name="img_func_param"]',funcVal:imgConfig.img_func})},page_render_init:function(renderConfig){var $_o=this;renderConfig=renderConfig?renderConfig:{};$($_o.pageRenderForm+' [name="tool"]').bind('change',function(){var tool=$(this).val();$('[id^="render_tool_"]').hide();if(tool){$('#render_tool_'+tool).show()}});$('#btn_chrome_test').bind('click',function(){ajax_check_userpwd({type:'POST',dataType:'json',url:ulink('setting/chrome_test'),data:$($_o.pageRenderForm).serialize(),beforeSend:function(){$('#btn_chrome_test').text('测试中...').attr('disabled',!0)},success:function(data){if(data.code==1){toastr.success(data.msg)}else{var warning=!1;if(data.msg.indexOf(':WARNING')>-1){warning=!0;if(data.msg.indexOf(':ERROR')>-1){warning=!1}} if(warning){toastr.warning(data.msg)}else{toastr.error(data.msg)}}},complete:function(){$('#btn_chrome_test').text('测试').removeAttr('disabled')}})});$($_o.pageRenderForm).bind('submit',function(){var formObj=$(this);var settings=getFormAjaxSettings(formObj);settings.complete=function(){formObj.find('button[type="submit"]').removeAttr('disabled')};ajax_check_userpwd(settings);return!1});$('#btn_chrome_clean').bind('click',function(){confirmRight('确定清理?',function(){windowModal('正在清理...',ulink('setting/chrome_clean'))})});$('#btn_chrome_restart').bind('click',function(){confirmRight('确定重启?',function(){windowModal('正在重启...',ulink('setting/chrome_restart'))})});$($_o.pageRenderForm+' [name="tool"]').val(renderConfig.tool).trigger('change')},translate_init:function(transConfig){var $_o=this;transConfig=transConfig?transConfig:{};$($_o.transForm+' [name="open"]').bind('click',function(){if($(this).val()==1){$('.content-wrapper').removeClass('wrapper-not-enable')}else{$('.content-wrapper').addClass('wrapper-not-enable')}});$($_o.transForm+' [name="api"]').bind('change',function(){$('[id^="api_"]').hide();$('#api_'+$(this).val()).show()});$($_o.transForm+' [name="open"][value="'+toInt(transConfig.open)+'"]').trigger('click');$($_o.transForm+' [name="api"]').val(transConfig.api).trigger("change");$($_o.transForm+' [name="pass_html"][value="'+toInt(transConfig.pass_html)+'"]').trigger('click')},site_init:function(siteConfig){var $_o=this;siteConfig=siteConfig?siteConfig:{};siteConfig.login=siteConfig.login?siteConfig.login:{};$($_o.siteForm+' [name="verifycode"]').bind('click',function(){if($(this).val()==1){$('#verifycode_len').show()}else{$('#verifycode_len').hide()}});$($_o.siteForm+' [name="login[limit]"]').bind('click',function(){if($(this).val()==1){$('#login_limit').show()}else{$('#login_limit').hide()}});$($_o.siteForm+' #btn_timezone').bind('click',function(){var nowTime=new Date();var offset=nowTime.getTimezoneOffset()/60;ajaxOpen({type:'post',url:ulink("setting/site_timezone"),data:{time:nowTime.getTime(),offset:offset},dataType:'json',success:function(data){if(data.code==1){if(data.data&&data.data.timezone){if($($_o.siteForm+' [name="timezone"]').find('option[value="'+data.data.timezone+'"]').length>0){$($_o.siteForm+' [name="timezone"]').val(data.data.timezone)}else{toastr.error('自动调整失败,请手动选择!')}}}else{if(data.msg){toastr.error(data.msg)}}}})});$($_o.siteForm+' [name="verifycode"][value="'+toInt(siteConfig.verifycode)+'"]').prop('checked','checked').trigger('click');$($_o.siteForm+' [name="hidehome"][value="'+toInt(siteConfig.hidehome)+'"]').prop('checked','checked');$($_o.siteForm+' [name="closelog"][value="'+toInt(siteConfig.closelog)+'"]').prop('checked','checked');$($_o.siteForm+' [name="dblong"][value="'+toInt(siteConfig.dblong)+'"]').prop('checked','checked');$($_o.siteForm+' [name="login[limit]"][value="'+toInt(siteConfig.login.limit)+'"]').prop('checked','checked').trigger('click');$($_o.siteForm+' [name="closetrans"][value="'+toInt(siteConfig.closetrans)+'"]').prop('checked','checked');$($_o.siteForm+' [name="timezone"]').val(siteConfig.timezone)},email_init:function(emailConfig){var $_o=this;emailConfig=emailConfig?emailConfig:{};$('#btn_test').bind('click',function(){$($_o.emailForm+' [name="is_test"]').val(1);$($_o.emailForm).submit()});$($_o.emailForm+' button[type="submit"]').bind('click',function(){$($_o.emailForm+' [name="is_test"]').val(0)});$($_o.emailForm+' [name="type"][value="'+emailConfig.type+'"]').prop('checked','checked')},store_init:function(storeConfig){var $_o=this;storeConfig=storeConfig?storeConfig:{};$($_o.storeForm).bind('submit',function(){var obj=$(this);ajaxOpen({type:'post',url:obj.attr('action'),dataType:'json',data:obj.serialize(),success:function(data){if(data.code==1){ajaxDataMsg(data)}else{if(data.msg){toastr.error(data.msg)} diff --git a/public/static/js/admin/task.js b/public/static/js/admin/task.js index 8ebc931..a3d9063 100644 --- a/public/static/js/admin/task.js +++ b/public/static/js/admin/task.js @@ -10,13 +10,14 @@ 'use strict';var taskOpClass={import_rule:function(ruleId,ruleName){$('#form_item input[name="rule_id"]').val(ruleId);$('#btn_import_rule').text('导入规则:'+ruleName);if(ruleId=='file'){var settings=getFormAjaxSettings($('#form_item'));settings.url=ulink('task/import_rule_file');settings.beforeSend=null;settings.error=null;settings.success=function(data){data.url='';if(data.msg){ajaxDataMsg(data)} $('#import_rule_file_plugins').hide().find('.plugins-info').html('');var dataData=data.data;if(isObject(dataData)){if(isObject(dataData.show_plugins)){var html='';for(var app in dataData.show_plugins){html+='
    '} $('#import_rule_file_plugins').show().find('.plugins-info').html(html)}}};ajaxOpen(settings)}else{$('#form_item [name="rule_file"]').val('')} -$('#myModal').modal('hide')},import_task:function(id,name){$('#form_item input[name="task_id"]').val(id);$('#btn_import_task').text('导入任务:'+name);$('#myModal').modal('hide')},task_init:function(){$('#form_item select[name="module"]').bind('change',function(){if($(this).val()!='pattern'){$('#btn_import_rule').parents('.input-group-btn').hide()}else{$('#btn_import_rule').parents('.input-group-btn').show()}});$('#form_item select[name="auto"]').bind('change',function(){var val=$(this).val();val=toInt(val);if(val==2){$('#config_task_timer').show()}else{$('#config_task_timer').hide()}});$('select[id^="task_timer_"]').bind('change',function(){var name=$(this).attr('data-name');if(name){var val=$(this).val();val=val?val.join(','):'';$('#form_item [name="'+name+'"]').val(val)}});$('#form_item [name="rule_file"]').bind('change',function(){taskOpClass.import_rule('file',$(this).val());$(this).parents('.dropdown').removeClass('open')});$('#form_item [name="config[download_img]"]').bind('click',function(){var open=!1;var val=$(this).val();if(val=='y'){open=!0}else if($(this).attr('data-global')){open=!0} -$('#config_download_img').css('background',open?'#fff':'#f1f1f1');$('#config_download_img').css('opacity',open?1:0.7)});$('#form_item [name="config[img_name]"]').bind('change',function(){var val=$(this).val();if(val=='custom'){$('#config_img_name_custom').show()}else{$('#config_img_name_custom').hide()}});$('#config_img_name_custom').on('click','.name-custom-path a[data-val]',function(){insertAtCaret($('[name="config[name_custom_path]"]'),$(this).attr('data-val'))});$('#config_img_name_custom').on('click','.name-custom-name a[data-val]',function(){insertAtCaret($('[name="config[name_custom_name]"]'),$(this).attr('data-val'))});$('#form_item [name="config[img_func]"]').bind('change',function(){var open=!1;var val=$(this).val();if(val){if(val!='n'){open=!0}}else{if($(this).attr('data-global')){open=!0}} +$('#myModal').modal('hide')},import_task:function(id,name){$('#form_item input[name="task_id"]').val(id);$('#btn_import_task').text('导入任务:'+name);$('#myModal').modal('hide')},task_init:function(){$('#form_item select[name="module"]').bind('change',function(){if($(this).val()!='pattern'){$('#btn_import_rule').parents('.input-group-btn').hide()}else{$('#btn_import_rule').parents('.input-group-btn').show()}});$('#form_item select[name="auto"]').bind('change',function(){var val=$(this).val();val=toInt(val);if(val==2){$('#config_task_timer').show()}else{$('#config_task_timer').hide()}});$('select[id^="task_timer_"]').bind('change',function(){var name=$(this).attr('data-name');if(name){var val=$(this).val();val=val?val.join(','):'';$('#form_item [name="'+name+'"]').val(val)}});$('#form_item [name="rule_file"]').bind('change',function(){taskOpClass.import_rule('file',$(this).val());$(this).parents('.dropdown').removeClass('open')});$('#form_item [name="config[proxy]"]').bind('click',function(){var open=!1;var val=$(this).val();if(val=='y'){open=!0}else if($(this).attr('data-global')){open=!0} +if(open){$('#config_proxy').removeClass('box-not-enable')}else{$('#config_proxy').addClass('box-not-enable')}});$('#form_item [name="config[download_img]"]').bind('click',function(){var open=!1;var val=$(this).val();if(val=='y'){open=!0}else if($(this).attr('data-global')){open=!0} +if(open){$('#config_download_img').removeClass('box-not-enable')}else{$('#config_download_img').addClass('box-not-enable')}});$('#form_item [name="config[img_name]"]').bind('change',function(){var val=$(this).val();if(val=='custom'){$('#config_img_name_custom').show()}else{$('#config_img_name_custom').hide()}});$('#config_img_name_custom').on('click','.name-custom-path a[data-val]',function(){insertAtCaret($('[name="config[name_custom_path]"]'),$(this).attr('data-val'))});$('#config_img_name_custom').on('click','.name-custom-name a[data-val]',function(){insertAtCaret($('[name="config[name_custom_name]"]'),$(this).attr('data-val'))});$('#form_item [name="config[img_func]"]').bind('change',function(){var open=!1;var val=$(this).val();if(val){if(val!='n'){open=!0}}else{if($(this).attr('data-global')){open=!0}} if(open){$('#config_img_func').show()}else{$('#config_img_func').hide()}});$('#form_item .dropup-img-params .dropdown-menu a').bind('click',function(){var val=$(this).attr('data-val');if(val){var obj=$('#form_item [name="config[img_func_param]"]');var objVal=obj.val();objVal=objVal?(objVal+"\r\n"):'';obj.val(objVal+val)}})},task_load:function(taskData,fieldList){taskOpClass.task_init();var imgFunc='';if(taskData){$('#form_item select[name="tg_id"]').val(toInt(taskData.tg_id));$('#form_item select[name="module"]').val(taskData.module);$('#form_item select[name="auto"]').val(toInt(taskData.auto)).trigger('change');var task_timer=taskData._task_timer;if(task_timer){var timerNames=['month','day','hour','minute'];for(var i in timerNames){var timerName=timerNames[i];var timerData=task_timer[timerName];if(!timerData||typeof(timerData)!='object'){timerData=[]} if(timerData.length>0){$('#form_item [name="task_timer['+timerName+']"]').val(timerData.join(','));for(var ii in timerData){$('#task_timer_'+timerName).find('option[value="'+timerData[ii]+'"]').prop('selected','selected')}}else{$('#task_timer_'+timerName).find('option[value=""]').prop('selected','selected')}}} var task_config=taskData.config;var showConfig=!1;if(task_config){imgFunc=task_config.img_func;for(var i in task_config){if(task_config[i]){showConfig=!0;break}} for(var i in task_config){var ele=$('#form_item').find('[name="config['+i+']"]').eq(0);var eleType=ele.attr('type');if(ele.is('input')&&eleType=='radio'){$('#form_item').find('[name="config['+i+']"][value="'+task_config[i]+'"]').prop('checked','checked')}else if(ele.is('input')&&eleType=='number'){task_config[i]=toInt(task_config[i]);if(task_config[i]!=0){ele.val(task_config[i])}}else{ele.val(task_config[i])}} -$('#form_item [name="config[download_img]"][value="'+task_config.download_img+'"]').trigger('click');$('#form_item [name="config[img_name]"]').trigger('change');$('#form_item [name="config[img_func]"]').trigger('change')} +$('#form_item [name="config[proxy]"][value="'+task_config.proxy+'"]').trigger('click');$('#form_item [name="config[download_img]"][value="'+task_config.download_img+'"]').trigger('click');$('#form_item [name="config[img_name]"]').trigger('change');$('#form_item [name="config[img_func]"]').trigger('change')} if(taskData._show_config||showConfig){showPanelCollapse('#task_config')} if(fieldList&&fieldList.length>0){$('#config_img_name_custom .name-custom-path-fields').html('');$('#config_img_name_custom .name-custom-name-fields').html('');for(var i in fieldList){var fieldHtml='[字段:'+fieldList[i]+']';fieldHtml='
  • '+fieldHtml+'
  • ';$('#config_img_name_custom .name-custom-path-fields').append(fieldHtml);$('#config_img_name_custom .name-custom-name-fields').append(fieldHtml)}}} loadPluginFunc({module:'downloadImg',boxObj:'#form_item',funcObj:'[name="config[img_func]"]',funcVal:imgFunc,paramObj:'[name="config[img_func_param]"]'})}} \ No newline at end of file diff --git a/public/static/js/common.js b/public/static/js/common.js index ea43472..2047f13 100644 --- a/public/static/js/common.js +++ b/public/static/js/common.js @@ -20,7 +20,7 @@ function isNull(str){var space=/^[\s\r\n]*$/;if(space.test(str)||str==null||str= function isObject(data){if(data&&typeof(data)=='object'){return!0}else{return!1}} function toInt(val){val=val?val:0;val=parseInt(val);if(isNaN(val)){val=0} return val} -function dataIsJson(data){if((/^\{(.+\:.+,*){1,}\}$/).test(data)||(/^\[(.+,*)+\]$/).test(data)){return!0}else{return!1}} +function dataIsJson(data){if((/^\{[\s\S]*\}$/).test(data)||(/^\[[\s\S]*\]$/).test(data)){return!0}else{return!1}} function dataIsHtml(data){if((/<\w+[^<>]*>/).test(data)){return!0}else{return!1}} function ajaxOpen(settings){if(settings.type&&'post'==settings.type.toLowerCase()){if(window.site_config){var regToken=new RegExp("_usertoken_\\s*=",'i');if(!regToken.test(settings.url)){var usertoken=window.site_config.usertoken;var data=settings.data;if(isNull(data)){data={'_usertoken_':usertoken}}else{if(typeof(data)=='object'){data._usertoken_=usertoken}else{if(!regToken.test(data)){data+='&_usertoken_='+encodeURIComponent(usertoken)}}} settings.data=data}}} @@ -40,7 +40,7 @@ options.full_height=1;modal(title,'
    \u5347\u7ea7\u6559\u7a0b<\/a>"}; \ No newline at end of file +var tpl_lang={"appname":"\u84dd\u5929\u91c7\u96c6\u5668","user_error_username":"\u7528\u6237\u540d\u5fc5\u987b\u75313-15\u4e2a\u5b57\u7b26\u7ec4\u6210\uff01","user_error_password":"\u5bc6\u7801\u5fc5\u987b\u75316-30\u4f4d\u5b57\u6bcd\u3001\u6570\u5b57\u6216\u7279\u6b8a\u5b57\u7b26\u7ec4\u6210\uff01","user_error_repassword":"\u786e\u8ba4\u5bc6\u7801\u548c\u5bc6\u7801\u4e0d\u4e00\u81f4\uff01","user_error_email":"\u90ae\u7bb1\u683c\u5f0f\u9519\u8bef\uff01","admincp":"\u540e\u53f0","sign_wildcard":"(*)","sign_match":"[\u5185\u5bb9{:id}]","tips_sign_wildcard":"\u901a\u914d\u7b26\u53ef\u5339\u914d\u4efb\u610f\u5b57\u7b26","tips_sign_match":"\u5339\u914d\u4efb\u610f\u5b57\u7b26\u5e76\u4fdd\u5b58\u4e3a\u6807\u7b7e\u4ee5\u4f9b\u8c03\u7528\uff0c\u7b49\u540c\u4e8e\u6355\u83b7\u7ec4\uff1a(?<nr\u6807\u8bc6>.*?)","tips_sign_match_global":"\u5339\u914d\u4efb\u610f\u5b57\u7b26\u5e76\u4fdd\u5b58\u4e3a\u6807\u7b7e\u4ee5\u4f9b\u5168\u5c40\u8c03\u7528\uff0c\u7b49\u540c\u4e8e\u6355\u83b7\u7ec4\uff1a(?<nr\u6807\u8bc6>.*?)","tips_sign_group":"\u6355\u83b7\u7ec4\uff1a(?<nr\u6807\u8bc6>[\\s\\S]*?)\uff0c\u5339\u914d\u6b63\u5219\u5e76\u4fdd\u5b58\u4e3a[\u5185\u5bb9]\u6807\u7b7e\u4ee5\u4f9b\u8c03\u7528","tips_sign_group_global":"\u6355\u83b7\u7ec4\uff1a(?<nr\u6807\u8bc6>[\\s\\S]*?)\uff0c\u5339\u914d\u6b63\u5219\u5e76\u4fdd\u5b58\u4e3a[\u5185\u5bb9]\u6807\u7b7e\u4ee5\u4f9b\u5168\u5c40\u8c03\u7528","tips_regular":"\u53ef\u4f7f\u7528\u6b63\u5219\u8868\u8fbe\u5f0f","setting":"\u8bbe\u7f6e","setting_site":"\u7ad9\u70b9\u8bbe\u7f6e","set_site_verifycode":"\u5f00\u542f\u56fe\u7247\u9a8c\u8bc1\u7801","setting_caiji":"\u91c7\u96c6\u8bbe\u7f6e","set_caiji_auto":"\u5f00\u542f\u81ea\u52a8\u91c7\u96c6","set_caiji_run":"\u81ea\u52a8\u91c7\u96c6\u8fd0\u884c\u65b9\u5f0f","set_caiji_interval":"\u91c7\u96c6\u8fd0\u884c\u95f4\u9694","set_caiji_num":"\u6700\u5927\u91c7\u96c6\u6570\u91cf","set_caiji_timeout":"\u6700\u5927\u6267\u884c\u65f6\u95f4","setting_email":"\u90ae\u4ef6\u53d1\u9001\u8bbe\u7f6e","set_email_sender":"\u53d1\u4ef6\u4eba\u540d\u79f0","set_email_email":"\u53d1\u4ef6\u4eba\u90ae\u7bb1\u8d26\u53f7","set_email_pwd":"\u53d1\u4ef6\u4eba\u90ae\u7bb1\u5bc6\u7801","set_email_smtp":"SMTP\u670d\u52a1\u5668","set_email_port":"SMTP\u7aef\u53e3","set_email_port_tips":"TLS\u4e00\u822c\u4e3a25\uff0cSSL\u4e00\u822c\u4e3a465\uff0c\u54a8\u8be2\u90ae\u7bb1\u670d\u52a1\u5546\u83b7\u53d6","set_email_type":"SMTP\u7aef\u53e3\u7c7b\u578b","set_email_test_subject":"\u6d4b\u8bd5\u53d1\u9001\u90ae\u4ef6","set_email_test_body":"\u606d\u559c\uff0c\u53d1\u9001\u90ae\u4ef6\u6210\u529f\uff01","setting_store":"\u4e91\u7aef\u8bbe\u7f6e","store_authkey_error":"\u901a\u4fe1\u5bc6\u94a5\u53ea\u80fd\u7531\u5b57\u6bcd\u548c\u6570\u5b57\u7ec4\u6210\u4e14\u957f\u5ea6\u57286-100\u4f4d\u4e4b\u95f4","down_img_m_":"\u672c\u5730\u5b58\u50a8","down_img_m_loc":"\u672c\u5730\u5b58\u50a8","down_img_m_func":"\u4f7f\u7528\u51fd\u6570","down_img_name_":"\u6309\u65f6\u95f4\u751f\u6210\uff08\u65b9\u4fbf\u65e5\u671f\u5f52\u7c7b\uff09","down_img_name_time":"\u6309\u65f6\u95f4\u751f\u6210\uff08\u65b9\u4fbf\u65e5\u671f\u5f52\u7c7b\uff09","down_img_name_url":"\u6309\u7f51\u5740\u751f\u6210\uff08\u9632\u6b62\u91cd\u590d\u4e0b\u8f7d\uff09","down_img_name_custom":"\u81ea\u5b9a\u4e49","down_img_name_custom_name_":"\u56fe\u7247\u7f51\u5740MD5\u7801","config_error_none_email":"\u6ca1\u6709\u90ae\u7bb1\u670d\u52a1\u5668\u914d\u7f6e\uff0c\u8bf7\u5728\u540e\u53f0\u8bbe\u7f6e\uff01","user":"\u7528\u6237","user_list":"\u7528\u6237\u5217\u8868","user_add":"\u6dfb\u52a0\u7528\u6237","user_edit":"\u7f16\u8f91\u7528\u6237","user_username":"\u7528\u6237\u540d","user_password":"\u5bc6\u7801","user_repassword":"\u786e\u8ba4\u5bc6\u7801","user_newpwd_tips":"\u5982\u9700\u4fee\u6539\u5bc6\u7801\uff0c\u8bf7\u5728\u6b64\u8f93\u5165\u65b0\u5bc6\u7801\uff0c\u5426\u5219\u7559\u7a7a","user_email":"\u90ae\u7bb1","user_email_tips":"\u7528\u4e8e\u627e\u56de\u8d26\u53f7\u5bc6\u7801","user_groupid":"\u7528\u6237\u7ec4","user_error_has_username":"\u7528\u6237\u540d\u5df2\u7ecf\u5b58\u5728\uff01","user_error_edit_not_allow":"\u53ea\u6709\u521b\u59cb\u4eba\u624d\u80fd\u7f16\u8f91\u4ed6\u4eba\u7684\u8d26\u53f7\uff01","user_error_delete_not_allow":"\u53ea\u6709\u521b\u59cb\u4eba\u624d\u80fd\u5220\u9664\u8d26\u53f7\uff01","user_error_groupid":"\u4e0d\u662f\u5141\u8bb8\u7684\u7528\u6237\u7ec4\uff01","user_error_del_founder":"\u4e0d\u80fd\u5220\u9664\u521b\u59cb\u4eba\u8d26\u53f7\uff01","user_error_null_uid":"UID\u4e0d\u80fd\u4e3a\u7a7a","user_error_empty_user":"\u7528\u6237\u4e0d\u5b58\u5728","user_error_login":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u4e0d\u6b63\u786e\uff01","user_error_sublogin":"\u767b\u5f55\u63d0\u4ea4\u5931\u8d25","user_error_is_not_admin":"\u62b1\u6b49\uff0c\u8bf7\u767b\u5f55\u7ba1\u7406\u5458\u8d26\u53f7\uff01","user_login_in":"\u767b\u5f55\u4e2d...","user_auto_login":"\u6b63\u5728\u81ea\u52a8\u767b\u5f55...","usertoken_error":"\u7528\u6237token\u9519\u8bef\uff0c\u8bf7\u5237\u65b0\u9875\u9762\u6216\u91cd\u65b0\u767b\u5f55\uff01","task":"\u4efb\u52a1","task_add":"\u6dfb\u52a0\u4efb\u52a1","task_edit":"\u7f16\u8f91\u4efb\u52a1","task_list":"\u4efb\u52a1\u5217\u8868","task_change_list":"\u5217\u8868\u6a21\u5f0f","task_change_folder":"\u5206\u7ec4\u6a21\u5f0f","task_name":"\u4efb\u52a1\u540d\u79f0","task_tg":"\u4efb\u52a1\u5206\u7ec4","task_sort":"\u6392\u5e8f","task_sort_help":"\u6570\u5b57\u8d8a\u5927\u8d8a\u9760\u524d","task_module":"\u91c7\u96c6\u6a21\u5757","task_module_":"\u65e0","task_module_pattern":"\u89c4\u5219\u91c7\u96c6","task_module_keyword":"\u5173\u952e\u8bcd\u91c7\u96c6","task_module_weixin":"\u5fae\u4fe1\u91c7\u96c6","task_auto":"\u81ea\u52a8\u91c7\u96c6","task_auto_":"\u5426","task_auto_0":"\u5426","task_auto_1":"\u5faa\u73af","task_auto_2":"\u5b9a\u65f6","task_addtime":"\u6dfb\u52a0\u65f6\u95f4","task_caijitime":"\u91c7\u96c6\u65f6\u95f4","task_edit_collector":"\u4e0b\u4e00\u6b65\uff1a\u7f16\u8f91\u91c7\u96c6\u5668","task_root":"\u6839\u76ee\u5f55","task_loading":"\u6b63\u5728\u8f7d\u5165\u6570\u636e","task_none_data":"\u65e0\u6570\u636e","task_set_task":"\u4efb\u52a1\u8bbe\u7f6e","task_set_collector":"\u91c7\u96c6\u5668\u8bbe\u7f6e","task_set_release":"\u53d1\u5e03\u8bbe\u7f6e","task_error_null_id":"\u8bf7\u8f93\u5165\u4efb\u52a1id","task_error_empty_task":"\u4e0d\u5b58\u5728\u4efb\u52a1","task_error_null_tgid":"\u8bf7\u8f93\u5165\u5206\u7ec4id","task_error_empty_tg":"\u4e0d\u5b58\u5728\u5206\u7ec4","task_error_null_name":"\u8bf7\u8f93\u5165\u540d\u79f0\uff01","task_error_has_name":"\u540d\u79f0\u5df2\u7ecf\u5b58\u5728\uff01","task_error_null_module":"\u672a\u8bbe\u7f6e\u91c7\u96c6\u6a21\u5757","taskgroup_add":"\u6dfb\u52a0\u5206\u7ec4","taskgroup_edit":"\u7f16\u8f91\u5206\u7ec4","taskgroup":"\u4efb\u52a1\u5206\u7ec4","taskgroup_list":"\u5206\u7ec4\u5217\u8868","taskgroup_name":"\u5206\u7ec4\u540d\u79f0","taskgroup_sort":"\u6392\u5e8f","taskgroup_sort_help":"\u6570\u5b57\u8d8a\u5927\u8d8a\u9760\u524d","taskgroup_parent_id":"\u7236\u5206\u7ec4","tg_add_sub":"\u6dfb\u52a0\u5b50\u5206\u7ec4","tg_move":"\u79fb\u52a8\u5206\u7ec4","tg_exist_sub":"\u5b58\u5728\u5b50\u5206\u7ec4\uff01","tg_none":"\u5206\u7ec4\u4e0d\u5b58\u5728\uff01","tg_is_parent":"\u5b58\u5728\u5b50\u5206\u7ec4\uff0c\u8bf7\u5148\u6e05\u7a7a\u5b50\u5206\u7ec4\uff0c\u624d\u80fd\u79fb\u52a8\u5206\u7ec4\uff01","tg_deleteall_has_sub":"\u6709\u5b50\u5206\u7ec4\u7684\u4e0d\u80fd\u5220\u9664\uff0c\u9700\u5148\u6e05\u7a7a\u5b50\u5206\u7ec4\uff01","tg_no_checked":"\u6ca1\u6709\u9009\u4e2d\u7684\u8bb0\u5f55\uff01","tg_error_null_name":"\u8bf7\u8f93\u5165\u540d\u79f0\uff01","tg_error_has_name":"\u540d\u79f0\u5df2\u7ecf\u5b58\u5728\uff01","coll_set":"\u91c7\u96c6\u5668\u8bbe\u7f6e","coll_edit_task":"\u4e0a\u4e00\u6b65\uff1a\u7f16\u8f91\u4efb\u52a1","coll_name":"\u91c7\u96c6\u89c4\u5219\u540d\u79f0","coll_error_invalid_module":"\u65e0\u6548\u7684\u91c7\u96c6\u6a21\u5757","coll_error_empty_coll":"\u91c7\u96c6\u5668\u8bbe\u7f6e\u4e0d\u5b58\u5728","coll_error_empty_effective":"\u9875\u9762\u811a\u672c\u4e0d\u53ef\u7528\uff0c\u4fdd\u5b58\u5931\u8d25\uff01","field_module_rule":"\u89c4\u5219\u5339\u914d","field_module_auto":"\u81ea\u52a8\u83b7\u53d6","field_module_xpath":"XPath\u5339\u914d","field_module_words":"\u56fa\u5b9a\u6587\u5b57","field_module_num":"\u968f\u673a\u6570\u5b57","field_module_time":"\u65f6\u95f4","field_module_list":"\u5217\u8868\u62bd\u53d6","field_module_json":"JSON\u63d0\u53d6","field_module_merge":"\u5b57\u6bb5\u7ec4\u5408","field_module_extract":"\u5b57\u6bb5\u63d0\u53d6","field_module_sign":"\u5185\u5bb9\u6807\u7b7e","process_module_html":"html\u6807\u7b7e\u8fc7\u6ee4","process_module_insert":"\u63d2\u5165\u5185\u5bb9","process_module_replace":"\u5185\u5bb9\u66ff\u6362","process_module_filter":"\u5173\u952e\u8bcd\u8fc7\u6ee4","process_module_if":"\u6761\u4ef6\u5224\u65ad","process_module_translate":"\u7ffb\u8bd1","process_module_tool":"\u5de5\u5177\u7bb1","process_module_batch":"\u6279\u91cf\u66ff\u6362","process_module_substr":"\u622a\u53d6\u5b57\u7b26\u4e32","process_module_func":"\u4f7f\u7528\u51fd\u6570","process_module_api":"\u8c03\u7528\u63a5\u53e3","p_m_if_1":"\u6ee1\u8db3\u6761\u4ef6\u91c7\u96c6","p_m_if_2":"\u6ee1\u8db3\u6761\u4ef6\u4e0d\u91c7\u96c6","p_m_if_3":"\u4e0d\u6ee1\u8db3\u6761\u4ef6\u91c7\u96c6","p_m_if_4":"\u4e0d\u6ee1\u8db3\u6761\u4ef6\u4e0d\u91c7\u96c6","p_m_if_c_has":"\u5305\u542b","p_m_if_c_nhas":"\u4e0d\u5305\u542b","p_m_if_c_eq":"\u7b49\u4e8e","p_m_if_c_neq":"\u4e0d\u7b49\u4e8e","p_m_if_c_heq":"\u6052\u7b49\u4e8e","p_m_if_c_nheq":"\u4e0d\u6052\u7b49\u4e8e","p_m_if_c_gt":"\u5927\u4e8e","p_m_if_c_egt":"\u5927\u4e8e\u7b49\u4e8e","p_m_if_c_lt":"\u5c0f\u4e8e","p_m_if_c_elt":"\u5c0f\u4e8e\u7b49\u4e8e","p_m_if_c_time_eq":"\u65f6\u95f4\u7b49\u4e8e","p_m_if_c_time_egt":"\u65f6\u95f4\u5927\u4e8e\u7b49\u4e8e","p_m_if_c_time_elt":"\u65f6\u95f4\u5c0f\u4e8e\u7b49\u4e8e","p_m_if_c_regexp":"\u6b63\u5219\u8868\u8fbe\u5f0f","p_m_if_c_func":"\u4f7f\u7528\u51fd\u6570","page_front_url":"\u524d\u7f6e\u9875","page_source_url":"\u8d77\u59cb\u9875","page_level_url":"\u591a\u7ea7\u9875","page_url":"\u5185\u5bb9\u9875","page_relation_url":"\u5173\u8054\u9875","rele_set":"\u53d1\u5e03\u8bbe\u7f6e","rele_error_detect_null":"\u6ca1\u6709\u68c0\u6d4b\u5230\u672c\u5730CMS\u7a0b\u5e8f\uff0c\u60a8\u53ef\u4ee5\u624b\u52a8\u7ed1\u5b9a\u6570\u636e","rele_error_empty_rele":"\u53d1\u5e03\u8bbe\u7f6e\u4e0d\u5b58\u5728","rele_error_null_module":"\u8bf7\u9009\u62e9\u53d1\u5e03\u65b9\u5f0f","rele_error_db":"\u6570\u636e\u5e93\u9519\u8bef\uff1a","rele_error_no_table":"\u8be5\u6570\u636e\u5e93\u6ca1\u6709\u8868","rele_success_db_ok":"\u6570\u636e\u5e93\u8fde\u63a5\u6210\u529f","rele_module":"\u53d1\u5e03\u65b9\u5f0f","rele_module_cms":"\u672c\u5730CMS\u7a0b\u5e8f","rele_module_db":"\u6570\u636e\u5e93","rele_module_api":"\u751f\u6210API","rele_module_toapi":"\u8c03\u7528\u63a5\u53e3","rele_module_file":"\u6587\u4ef6\u5b58\u50a8","rele_module_diy":"\u81ea\u5b9a\u4e49\u63d2\u4ef6","rele_btn_detect":"\u5f00\u59cb\u68c0\u6d4b","rele_cms_path":"CMS\u8def\u5f84","rele_db_type":"\u6570\u636e\u5e93\u7c7b\u578b","rele_db_host":"\u6570\u636e\u5e93\u4e3b\u673a","rele_db_name":"\u6570\u636e\u5e93\u540d\u79f0","rele_db_charset":"\u6570\u636e\u5e93\u7f16\u7801","rele_db_port":"\u6570\u636e\u5e93\u7aef\u53e3","rele_db_user":"\u6570\u636e\u5e93\u7528\u6237","rele_db_pwd":"\u6570\u636e\u5e93\u5bc6\u7801","error_unknown_database":"\u672a\u77e5\u7684\u6570\u636e\u5e93","error_null_input":"\u8bf7\u8f93\u5165{:str}","error_open_basedir":"\u60a8\u7684\u7f51\u7ad9\u5f00\u542f\u4e86\u76ee\u5f55\u8bbf\u95ee\u9650\u5236(open_basedir)\uff0c\u8bf7\u68c0\u67e5\u662f\u5426\u56e0\u8be5\u95ee\u9898\u5bfc\u81f4\u76ee\u5f55\u65e0\u6cd5\u8bfb\u5199\uff01","collected":"\u5df2\u91c7\u96c6\u6570\u636e","collected_list":"\u5df2\u91c7\u96c6\u6570\u636e\u5217\u8868","COLLECTED_RELE_":"\u65e0","collected_rele_cms":"CMS","collected_rele_db":"\u6570\u636e\u5e93","collected_rele_file":"\u6587\u4ef6","collected_rele_toapi":"\u63a5\u53e3","collected_rele_api":"API","collected_rele_diy":"\u63d2\u4ef6","verifycode":"\u9a8c\u8bc1\u7801","verifycode_error":"\u9a8c\u8bc1\u7801\u9519\u8bef\uff01","find_password":"\u627e\u56de\u5bc6\u7801","find_pwd_username":"\u8bf7\u8f93\u5165\u90ae\u7bb1\/\u7528\u6237\u540d","find_pwd_yzm":"\u8bf7\u8f93\u5165\u6fc0\u6d3b\u7801","find_pwd_resend":"\u91cd\u65b0\u53d1\u9001","find_pwd_next_step":"\u4e0b\u4e00\u6b65","find_pwd_pwd":"\u8bf7\u8f93\u5165\u65b0\u5bc6\u7801","find_pwd_repwd":"\u786e\u8ba4\u65b0\u5bc6\u7801","find_pwd_sended":"\u5df2\u5411\u90ae\u7bb1{:email}\u53d1\u9001\u4e86\u6fc0\u6d3b\u7801\uff01","find_pwd_email_failed":"\u53d1\u9001\u90ae\u4ef6\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u540e\u53f0\u53d1\u9001\u90ae\u4ef6\u914d\u7f6e\uff01","find_pwd_email_wait":"\u9700\u7b49\u5f85{:seconds}\u79d2\u624d\u80fd\u518d\u6b21\u53d1\u9001","find_pwd_email_subject":"\u627e\u56de\u5bc6\u7801 - \u84dd\u5929\u91c7\u96c6\u5668","find_pwd_email_body":"\u60a8\u7684\u6fc0\u6d3b\u7801\u4e3a\uff1a{:yzm}\uff0c\u6709\u6548\u65f6\u95f4{:minutes}\u5206\u949f","find_pwd_error_username":"\u8bf7\u8f93\u5165\u90ae\u7bb1\/\u7528\u6237\u540d","find_pwd_error_step":"\u6b65\u9aa4\u9519\u8bef\uff0c\u8bf7\u91cd\u65b0\u64cd\u4f5c\uff01","find_pwd_error_post":"\u8868\u5355\u63d0\u4ea4\u5931\u8d25","find_pwd_error_none_email":"\u90ae\u7bb1\u4e0d\u5b58\u5728\uff01","find_pwd_error_multiple_emails":"\u5b58\u5728\u591a\u4e2a\u7528\u6237\u4f7f\u7528\u6b64\u90ae\u7bb1\uff0c\u8bf7\u8f93\u5165\u7528\u6237\u540d\uff01","find_pwd_error_none_user":"\u7528\u6237\u4e0d\u5b58\u5728\uff01","find_pwd_success":"\u5bc6\u7801\u4fee\u6539\u6210\u529f","yzm_error_please_send":"\u8bf7\u53d1\u9001\u6fc0\u6d3b\u7801","yzm_error_please_input":"\u8bf7\u8f93\u5165\u6fc0\u6d3b\u7801","yzm_error_timeout":"\u6fc0\u6d3b\u7801\u5df2\u8fc7\u671f\uff01\u8bf7\u91cd\u65b0\u53d1\u9001","yzm_error_yzm":"\u6fc0\u6d3b\u7801\u9519\u8bef","admincp_style":"\u754c\u9762","admincp_sidebar_mini":"\u83dc\u5355\u6700\u5c0f\u5316","admincp_skins":"\u8bbe\u7f6e\u76ae\u80a4","skin_blue":"\u84dd","skin_black":"\u9ed1","skin_purple":"\u7d2b","skin_green":"\u7eff","skin_red":"\u7ea2","skin_yellow":"\u9ec4","skin_blue_light":"\u84dd\u4eae","skin_black_light":"\u9ed1\u4eae","skin_purple_light":"\u7d2b\u4eae","skin_green_light":"\u7eff\u4eae","skin_red_light":"\u7ea2\u4eae","skin_yellow_light":"\u9ec4\u4eae","store":"\u4e91\u5e73\u53f0","rule_collect":"\u91c7\u96c6\u89c4\u5219","empty_data":"\u6570\u636e\u4e0d\u5b58\u5728","invalid_op":"\u65e0\u6548\u7684\u64cd\u4f5c\uff01","submit":"\u63d0\u4ea4","search":"\u641c\u7d22","op_success":"\u64cd\u4f5c\u6210\u529f","op_failed":"\u64cd\u4f5c\u5931\u8d25","sort":"\u6392\u5e8f","select":"\u9009\u62e9","select_all":"\u5168\u9009","select_first":"\u8bf7\u9009\u62e9","save":"\u4fdd\u5b58","op":"\u64cd\u4f5c","delete":"\u5220\u9664","deleted":"\u5df2\u5220\u9664","edit":"\u7f16\u8f91","test":"\u6d4b\u8bd5","confirm_delete":"\u786e\u5b9a\u5220\u9664\uff1f","delete_success":"\u5220\u9664\u6210\u529f","none":"\u65e0","caiji":"\u91c7\u96c6","more":"\u66f4\u591a","yes":"\u662f","no":"\u5426","all":"\u5168\u90e8","login":"\u767b\u5f55","logout":"\u9000\u51fa","login_auto":"\u4e0b\u6b21\u81ea\u52a8\u767b\u5f55","separator":"\uff1a","redirecting":"\u8df3\u8f6c\u4e2d...","close":"\u5173\u95ed","tips_match":"\u793a\u4f8b\uff1a<div id="a">[\u5185\u5bb91]<\/div>(*)<div id="b">[\u5185\u5bb92]<\/div>","tips_matchn":"\u793a\u4f8b\uff1a[\u5185\u5bb91] [\u5185\u5bb92]","tips_match_only":"\u793a\u4f8b\uff1a<div id="content">[\u5185\u5bb9]<\/div>","tips_match_area":"\u793a\u4f8b\uff1a<div id="a">[\u5185\u5bb9123]<\/div>(*)<div id="b">[\u5185\u5bb9abc]<\/div>","tips_matchn_area":"\u793a\u4f8b\uff1a[\u5185\u5bb9123] [\u5185\u5bb9abc]","tips_match_url":"\u793a\u4f8b\uff1a<a href="http:\/\/demo.com\/[\u5185\u5bb9123]\/[\u5185\u5bb9abc]">(*)<\/a>","tips_matchn_url":"\u793a\u4f8b\uff1ahttp:\/\/www.demo.com\/[\u5185\u5bb9123]-[\u5185\u5bb9abc].html","release_upgrade":"\u63d2\u4ef6\u7248\u672c\u8fc7\u4f4e\uff0c\u8bf7\u5347\u7ea7\u63d2\u4ef6 \u5347\u7ea7\u6559\u7a0b<\/a>"}; \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/controller/Api.php b/vendor/skycaiji/app/admin/controller/Api.php index 14661b5..b152675 100644 --- a/vendor/skycaiji/app/admin/controller/Api.php +++ b/vendor/skycaiji/app/admin/controller/Api.php @@ -17,15 +17,15 @@ class Api extends CollectController{ \util\Param::set_task_close_echo(); $taskId=input('id/d',0); $apiurl=input('apiurl'); - $releData=model('Release')->where(array('task_id'=>$taskId))->find(); + $mrele=model('Release'); + $releData=$mrele->where(array('task_id'=>$taskId))->find(); if(empty($releData)){ json(array('error'=>'没有发布设置!'))->send(); } - $releData['config']=unserialize($releData['config']?:''); + $releData['config']=$mrele->compatible_config($releData['config']); if($apiurl!=$releData['config']['api']['url']){ json(array('error'=>'api地址错误!'))->send(); } - \util\Param::set_task_api_response(); header('Content-type:text/json'); $this->collect_tasks($taskId, null, true); diff --git a/vendor/skycaiji/app/admin/controller/Backstage.php b/vendor/skycaiji/app/admin/controller/Backstage.php index f6384ec..8544bd3 100644 --- a/vendor/skycaiji/app/admin/controller/Backstage.php +++ b/vendor/skycaiji/app/admin/controller/Backstage.php @@ -50,10 +50,10 @@ class Backstage extends BaseController{ $collectBackstageTime=CacheModel::getInstance()->getCache('collect_backstage_time','data'); $collectBackstageTime=intval($collectBackstageTime); - if((time()-$collectBackstageTime)>60*3){ + if((time()-$collectBackstageTime)>60*5){ $runInfo['auto_status']='停止运行'; - $serverData['caiji'].='

    自动采集停止了,请重新保存设置以便激活采集

    '; + $serverData['caiji'].='

    自动采集已停止 点击激活

    '; }else{ $runInfo['auto_status']=''.(model('Config')->server_is_cli()?'cli':'web').'后台运行'; $runInfo['auto_status1']=''.date('m-d H:i:s',$collectBackstageTime).''; @@ -154,8 +154,18 @@ class Backstage extends BaseController{ return $this->fetch('backstage/index'); } + public function run_auto_backstageAction(){ + controller('admin/Setting')->_run_auto_backstage(); + $this->success('操作完成'); + } + /*实时采集*/ + public function collectAction(){ + controller('admin/Index','controller')->auto_collectAction(); + } + public function checkUpAction(){ + \util\Funcs::close_session(); $info=array( 'pageRenderInvalid'=>false, @@ -179,35 +189,30 @@ class Backstage extends BaseController{ if($mconfig->server_is_cli()){ $phpvInfo=$mconfig->exec_php_version(g_sc_c('caiji','server_php')); - if(empty($phpvInfo)||!$phpvInfo['success']){ + if(empty($phpvInfo)||(!$phpvInfo['success']&&$phpvInfo['msg'])){ + $info['phpInvalid']=true; } - } - - $autoTaskIds=model('Task')->where('`auto`>0')->column('id'); - if(!empty($autoTaskIds)){ - foreach ($autoTaskIds as $autoTaskId){ - $collConfig=model('Collector')->where('task_id',$autoTaskId)->value('config'); + if($phpvInfo['msg']){ - $collConfig=unserialize($collConfig?:''); - if(is_array($collConfig)&&$collConfig['page_render']){ - - $pageRender=g_sc_c('page_render'); - init_array($pageRender); - if(empty($pageRender['tool'])){ - - $info['pageRenderInvalid']=true; - }elseif($mconfig->page_render_is_chrome()){ - - init_array($pageRender['chrome']); - $chromeSoket=new \util\ChromeSocket($pageRender['chrome']['host'],$pageRender['chrome']['port'],$pageRender['timeout'],$pageRender['chrome']['filename'],$pageRender['chrome']); - $info['pageRenderInvalid']=$chromeSoket->hostIsOpen()?false:true; - } - break; + if(preg_match('/\bPHP\s+(?P\d+(\.\d+){1,})/i', $phpvInfo['msg'],$mphpv)){ + $mphpv=$mphpv['ver']; + $info['phpCliVersion']=$mphpv; } } } + if(model('Task')->where('`auto`>0')->count()>0){ + + if($mconfig->page_render_is_chrome()){ + + init_array($pageRender['chrome']); + $chromeSoket=new \util\ChromeSocket($pageRender['chrome']['host'],$pageRender['chrome']['port'],$pageRender['timeout'],$pageRender['chrome']['filename'],$pageRender['chrome']); + $info['pageRenderInvalid']=$chromeSoket->hostIsOpen()?false:true; + } + } + + $cacheTongji=cache('admin_check_up_tongji'); $cacheTongji=is_array($cacheTongji)?$cacheTongji:array(); $tongji=array(); @@ -235,18 +240,13 @@ class Backstage extends BaseController{ $this->success('','',$info); } - /*实时采集*/ - public function collectAction(){ - controller('admin/Index','controller')->auto_collectAction(); - } - /*检测更新*/ public function newVersionAction(){ \util\Funcs::close_session(); $version=\util\Tools::curl_skycaiji('/client/info/version?v='.SKYCAIJI_VERSION); $version=json_decode($version,true); $version=is_array($version)?$version:array(); - $new_version=trim($version['new_version']); + $new_version=trim($version['new_version']?:0); $cur_version=g_sc_c('version'); @@ -431,23 +431,24 @@ class Backstage extends BaseController{ if(!empty($taskIds)){ foreach ($taskIds as $taskId){ $cache=$mcache->db()->where('cname',$taskId)->find(); - $taskStatus=$cache['ctype']; - if(empty($taskStatus)){ - - $taskStatus=''; - - $collStatus=\skycaiji\admin\model\Task::collecting_status($taskId); - if($collStatus){ - if($collStatus=='none'){ - $taskStatus='已断开'; - }elseif($collStatus=='unlock'){ - $taskStatus='运行中断'; - } - } - }else{ - - $taskStatus='已结束'; - } + $taskStatus=$cache?$cache['ctype']:''; + if(empty($taskStatus)){ + + $taskStatus=''; + + $collStatus=\skycaiji\admin\model\Task::collecting_status($taskId); + if($collStatus){ + if($collStatus=='none'){ + $taskStatus='已断开'; + }elseif($collStatus=='unlock'){ + $taskStatus='运行中断'; + } + } + }else{ + + $taskStatus='已结束'; + } + $statusList[$taskId]=$taskStatus; } } diff --git a/vendor/skycaiji/app/admin/controller/BaseController.php b/vendor/skycaiji/app/admin/controller/BaseController.php index 64a7518..4c687aa 100644 --- a/vendor/skycaiji/app/admin/controller/BaseController.php +++ b/vendor/skycaiji/app/admin/controller/BaseController.php @@ -53,20 +53,29 @@ class BaseController extends \skycaiji\common\controller\BaseController{ public function ajax_check_userpwd(){ - if(!input('?_userpwd_')){ - $this->error('','',array('_userpwd_'=>true)); - } - $userpwd=input('_userpwd_',''); - if(empty($userpwd)){ - $this->error('请输入密码','',array('_userpwd_'=>true)); - } $user=g_sc('user'); if(empty($user)){ $this->error('请先登录'); } - if(\skycaiji\admin\model\User::pwd_encrypt($userpwd,$user['salt'])!=$user['password']){ + $muser=model('User'); + $checkUserpwd=cookie('check_userpwd'); + if(empty($checkUserpwd)||$checkUserpwd!=$muser->generate_key($user)){ - $this->error('密码错误','',array('_userpwd_'=>true)); + if(!input('?_check_pwd_')){ + $this->error('','',array('_check_pwd_'=>true)); + } + $userpwd=input('_check_pwd_',''); + if(empty($userpwd)){ + $this->error('请输入密码','',array('_check_pwd_'=>true)); + } + if(\skycaiji\admin\model\User::pwd_encrypt($userpwd,$user['salt'])!=$user['password']){ + + $this->error('密码错误','',array('_check_pwd_'=>true)); + } + if(input('_check_skip_')){ + + cookie('check_userpwd',$muser->generate_key($user),array('expire'=>3600)); + } } } } \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/controller/CollectController.php b/vendor/skycaiji/app/admin/controller/CollectController.php index 12b25bc..0469344 100644 --- a/vendor/skycaiji/app/admin/controller/CollectController.php +++ b/vendor/skycaiji/app/admin/controller/CollectController.php @@ -21,7 +21,6 @@ class CollectController extends \skycaiji\admin\controller\BaseController{ if($echo){ $logFilename=\skycaiji\admin\model\Collector::echo_msg_filename(); if(!empty($logFilename)){ - $color=empty($color)?'red':$color; if(!isset(self::$echo_msg_head)){ self::$echo_msg_head=true; @@ -40,30 +39,32 @@ class CollectController extends \skycaiji\admin\controller\BaseController{ $cssJs=''; $this->_echo_msg_write($cssJs, $logFilename); } - - if(is_array($strArgs)){ - - $strArg0=is_array($strArgs[0])?'':$strArgs[0]; - $strArgs=array_slice($strArgs, 1); - foreach ($strArgs as $k=>$v){ - $v=is_array($v)?'':htmlspecialchars($v,ENT_QUOTES); - $strArgs[$k]=$v; - } - $strArgs=vsprintf($strArg0, $strArgs); - } - - $txt='
    '.$strArgs.'
    '.$end_str; - - $this->_echo_msg_write($txt, $logFilename); + $this->_echo_msg_write($this->_echo_msg_str($strArgs,$color,$end_str,$div_style), $logFilename); } } } + protected function _echo_msg_str($strArgs,$color='red',$end_str='',$div_style=''){ + $color=empty($color)?'red':$color; + if(is_array($strArgs)){ + + $strArg0=is_array($strArgs[0])?'':$strArgs[0]; + $strArgs=array_slice($strArgs, 1); + foreach ($strArgs as $k=>$v){ + $v=is_array($v)?'':htmlspecialchars($v,ENT_QUOTES); + $strArgs[$k]=$v; + } + $strArgs=vsprintf($strArg0, $strArgs); + } + return ('
    '.$strArgs.'
    '.$end_str); + } + public function echo_msg_exit($strArgs,$color='red',$echo=true,$end_str='',$div_style=''){ $this->echo_msg($strArgs,$color,$echo,$end_str,$div_style); exit(); diff --git a/vendor/skycaiji/app/admin/controller/Cpattern.php b/vendor/skycaiji/app/admin/controller/Cpattern.php index f790d7d..975d324 100644 --- a/vendor/skycaiji/app/admin/controller/Cpattern.php +++ b/vendor/skycaiji/app/admin/controller/Cpattern.php @@ -111,6 +111,7 @@ class Cpattern extends BaseController { } }else{ $sourceUrl=input('source_url','','trim'); + $source=array(); if($sourceUrl){ $source['objid']=input('objid',''); @@ -147,8 +148,8 @@ class Cpattern extends BaseController { $source['type']='custom'; $source['urls']=$sourceUrl; } - $this->assign('source',$source); } + $this->assign('source',$source); return $this->fetch(); } } @@ -178,7 +179,7 @@ class Cpattern extends BaseController { $field['num_end'] = max ( $field['num_start'], $field ['num_end'] ); break; - case 'list':if(empty($field['list']))$this->error('随机抽取不能为空!');break; + case 'list':if(empty($field['list']))$this->error('列表数据不能为空!');break; case 'extract':if(empty($field['extract']))$this->error('请选择字段!');break; case 'merge':if(empty($field['merge']))$this->error('字段组合不能为空!');break; case 'sign': @@ -194,7 +195,7 @@ class Cpattern extends BaseController { 'words' =>'words', 'num' => array('num_start','num_end'), 'time' => array ('time_format','time_start','time_end','time_stamp'), - 'list' => 'list', + 'list' => array('list','list_type'), 'extract' =>array('extract','extract_module','extract_rule','extract_rule_merge','extract_rule_multi','extract_rule_multi_str','extract_xpath','extract_xpath_attr','extract_xpath_attr_custom','extract_xpath_multi','extract_xpath_multi_str','extract_json','extract_json_arr','extract_json_arr_implode'), 'merge' => 'merge', 'sign' => 'sign' @@ -552,20 +553,39 @@ class Cpattern extends BaseController { $eCpattern=controller('admin/Cpattern','event'); $eCpattern->init($collData); + $resizeWidth=CacheModel::getInstance()->getCache('cpattern_easymode_resize','data'); + init_array($resizeWidth); + $resizeWidth=intval($resizeWidth['width']); + $this->set_html_tags('任务:'.$taskData['name'].'_简单模式'); $this->assign('taskId',$taskId); $this->assign('collId',$collId); + $this->assign('resizeWidth',$resizeWidth); return $this->fetch(); } + public function easymode_resizeAction(){ + $width=input('width/d',0); + $cname='cpattern_easymode_resize'; + $mcache=CacheModel::getInstance(); + $data=$mcache->getCache($cname,'data'); + if(empty($data)&&!is_array($data)){ + $data=array(); + } + $data['width']=$width; + $mcache->setCache($cname,$data); + $this->success(); + } + + public function page_signs_sortAction(){ $mcache=CacheModel::getInstance(); $key='cpattern_page_signs_sort'; $sort=$mcache->getCache($key,'data'); $sort=$sort=='asc'?'desc':'asc'; $mcache->setCache($key,$sort); - $this->success('已将页面设为'.($sort=='asc'?'升序':'倒序').'排列'); + $this->success('已将页面设为'.($sort=='asc'?'升序':'降序').'排列'); } /*获取父级页面的标签列表*/ public function page_signsAction(){ @@ -594,7 +614,9 @@ class Cpattern extends BaseController { $urlConfig['area']=''; $urlConfig['url_rule']=''; $sourceConfig=$urlConfig; - $pageType='url'; + if($pageType=='source_url'){ + $pageType='url'; + } } $eCpattern=controller('admin/Cpattern','event'); diff --git a/vendor/skycaiji/app/admin/controller/CpatternTest.php b/vendor/skycaiji/app/admin/controller/CpatternTest.php index 021f9c0..69c04ff 100644 --- a/vendor/skycaiji/app/admin/controller/CpatternTest.php +++ b/vendor/skycaiji/app/admin/controller/CpatternTest.php @@ -161,10 +161,10 @@ class CpatternTest extends BaseController { } if($sourceIsUrl){ - $vurls=$this->eCpattern->page_convert_url_signs('url', '', $vurls, array(), false); + $vurls=$this->eCpattern->page_convert_url_signs('url', '', false, $vurls, array(), false); }else{ - $vurls=$this->eCpattern->page_convert_url_signs('source_url', '', $vurls, array(), false); + $vurls=$this->eCpattern->page_convert_url_signs('source_url', '', false, $vurls, array(), false); } $source_urls[$v]=$vurls; } @@ -190,20 +190,23 @@ class CpatternTest extends BaseController { $testNum=3; } - $sourceUrlIsPost=$this->eCpattern->page_is_post('source_url')?'[POST] ':''; + $sourceUrlOpened=$this->_page_opened_tips('source_url'); $this->assign('testNum',$testNum); $this->assign('source_urls',$source_urls); $this->assign('sourceIsUrl',$sourceIsUrl); - $this->assign('sourceUrlIsPost',$sourceUrlIsPost); + $this->assign('sourceUrlOpened',$sourceUrlOpened); $this->assign('config',$this->eCpattern->config); $this->assign('openedTools',$this->_opened_tools(false)); return $this->fetch('cpattern:test_source_urls'); } + private function _page_opened_tips($pageType,$pageName=''){ + return $this->eCpattern->page_opened_tips($pageType,$pageName,false,true); + } private function _opened_tools($isHead=true){ $opened_tools=array(); - if($this->eCpattern->config['page_render']){ + if($this->eCpattern->page_render_is_open()){ $opened_tools[]='页面渲染'; } if(g_sc_c('proxy','open')){ @@ -291,18 +294,14 @@ class CpatternTest extends BaseController { ); cache($cacheKeyPre.$curLevel,$cachePageData,1200); - - $urlIsPost=$this->eCpattern->page_is_post('url')?'[POST] ':''; - $levelIsPost=$this->eCpattern->page_is_post('level_url',$levelData['levelName'])?'[POST] ':''; - $this->success('', null, array( 'sourceUrl'=>$source_url, 'urls' => $levelData['urls'], - 'urlIsPost'=>$urlIsPost, + 'urlOpened'=>$this->_page_opened_tips('url'), 'levelName' => $levelData['levelName'], 'level' => $curLevel, 'nextLevel' => $levelData['nextLevel'], - 'levelIsPost'=>$levelIsPost, + 'levelOpened'=>$this->_page_opened_tips('level_url',$levelData['levelName']), )); } @@ -321,12 +320,15 @@ class CpatternTest extends BaseController { $urlParams=input('param.','','trim'); $urlParams=base64_encode(serialize($urlParams)); - $urlIsPost=$this->eCpattern->page_is_post('url'); + $urlOpened=$this->_page_opened_tips('url'); $pageSource=input('page_source',''); + if($pageSource=='source_url'&&$this->eCpattern->source_is_url()){ + $pageSource='url'; + } $this->assign('pageSource',$pageSource); - $this->assign('urlIsPost',$urlIsPost); + $this->assign('urlOpened',$urlOpened); $this->assign('pageSources',$this->eCpattern->page_source_options()); $this->assign('urlParams',$urlParams); if(request()->isAjax()){ @@ -424,6 +426,8 @@ class CpatternTest extends BaseController { $pageSigns=$this->eCpattern->parent_page_signs($pageType,$pageName,'url_web'); $this->_page_signs_input_urls(true,true,$pageSigns,$inputedUrls,$input_urls); + $pageSigns=$this->eCpattern->parent_page_signs($pageType,$pageName,'renderer'); + $this->_page_signs_input_urls(true,true,$pageSigns,$inputedUrls,$input_urls); }elseif($test=='get_html'||$test=='get_browser'){ if(!empty($pageType)){ @@ -494,23 +498,23 @@ class CpatternTest extends BaseController { unset($input_urls['source_url']); } - $is_post_list=array(); + $pageOpenedList=array(); foreach ($input_urls as $iu_type=>$iu_urls){ if($this->eCpattern->page_is_list($iu_type)){ - $is_post_list[$iu_type]=array(); + $pageOpenedList[$iu_type]=array(); foreach ($iu_urls as $v){ - $is_post_list[$iu_type][$v['name']]=$this->eCpattern->page_is_post($iu_type,$v['name']); + $pageOpenedList[$iu_type][$v['name']]=$this->_page_opened_tips($iu_type,$v['name']); } }else{ - $is_post_list[$iu_type]=$this->eCpattern->page_is_post($iu_type); + $pageOpenedList[$iu_type]=$this->_page_opened_tips($iu_type); } } - $is_post=$this->eCpattern->page_is_post($pageType,$pageName); + $pageOpened=$this->_page_opened_tips($pageType,$pageName); $this->assign('input_urls',$input_urls); - $this->assign('is_post_list',$is_post_list); - $this->assign('is_post',$is_post); + $this->assign('pageOpenedList',$pageOpenedList); + $this->assign('pageOpened',$pageOpened); return $this->fetch('cpattern:test_input_url'); } @@ -520,13 +524,15 @@ class CpatternTest extends BaseController { $this->_page_signs_input_urls($isContUrl,false,$pageSigns,$inputedUrls,$input_urls); $pageSigns=$this->eCpattern->parent_page_signs($pageType,$pageName,'url_web'); $this->_page_signs_input_urls($isContUrl,true,$pageSigns,$inputedUrls,$input_urls); + $pageSigns=$this->eCpattern->parent_page_signs($pageType,$pageName,'renderer'); + $this->_page_signs_input_urls($isContUrl,true,$pageSigns,$inputedUrls,$input_urls); } - private function _page_signs_input_urls($isContUrl,$isUrlWeb,$pageSigns,$inputedUrls,&$input_urls){ + private function _page_signs_input_urls($isContUrl,$inPageConfig,$pageSigns,$inputedUrls,&$input_urls){ $iptUrls=array(); if(!empty($pageSigns)){ - if($isUrlWeb){ + if($inPageConfig){ if(!empty($pageSigns['cur'])&&(!empty($pageSigns['cur']['url'])||!empty($pageSigns['cur']['area']))){ @@ -618,31 +624,21 @@ class CpatternTest extends BaseController { } if($levelNames){ foreach ($levelNames as $levelName){ - $pageSigns=$this->eCpattern->parent_page_signs('level_url',$levelName); - $iptUrls=$this->_page_signs_input_urls($isContUrl,false,$pageSigns,$inputedUrls,$input_urls); - if(is_array($iptUrls['level_url'])){ - - foreach ($iptUrls['level_url'] as $k=>$v){ - if(isset($input_urls['level_url'][$k])){ - unset($iptUrls['level_url'][$k]); + $mergeTypes=array(''=>false,'url_web'=>true,'renderer'=>true); + foreach ($mergeTypes as $mtk=>$mtv){ + $pageSigns=$this->eCpattern->parent_page_signs('level_url',$levelName,$mtk); + $iptUrls=$this->_page_signs_input_urls($isContUrl,$mtv,$pageSigns,$inputedUrls,$input_urls); + if(is_array($iptUrls['level_url'])){ + + foreach ($iptUrls['level_url'] as $k=>$v){ + if(isset($input_urls['level_url'][$k])){ + unset($iptUrls['level_url'][$k]); + } } - } - if(!empty($iptUrls['level_url'])){ - $this->_input_urls_parent($isContUrl,$iptUrls, $inputedUrls, $input_urls); - } - } - $pageSigns=$this->eCpattern->parent_page_signs('level_url',$levelName,'url_web'); - $iptUrls=$this->_page_signs_input_urls($isContUrl,true,$pageSigns,$inputedUrls,$input_urls); - if(is_array($iptUrls['level_url'])){ - - foreach ($iptUrls['level_url'] as $k=>$v){ - if(isset($input_urls['level_url'][$k])){ - unset($iptUrls['level_url'][$k]); + if(!empty($iptUrls['level_url'])){ + $this->_input_urls_parent($isContUrl,$iptUrls, $inputedUrls, $input_urls); } } - if(!empty($iptUrls['level_url'])){ - $this->_input_urls_parent($isContUrl,$iptUrls, $inputedUrls, $input_urls); - } } } } @@ -777,9 +773,6 @@ class CpatternTest extends BaseController { $html=$this->eCpattern->get_page_html($test_url, $pageType, $pageName); $jsonHtml=\util\Funcs::convert_html2json($html,true); - $config=$this->eCpattern->config; - $config=is_array($config)?$config:array(); - if(empty($jsonHtml)){ @@ -787,10 +780,24 @@ class CpatternTest extends BaseController { $html=preg_replace('/\bon[a-z]+\s*\=\s*[\'\"]/', "$0return;", $html); $html=preg_replace('/]*charset[^<>]*?>/i', '', $html); $html=preg_replace('/]*http-equiv\s*=\s*[\'\"]{0,1}refresh\b[\'\"]{0,1}[^<>]*?>/i', '', $html); - header("Content-type:text/html;charset=utf-8"); - $this->assign('html',$html); - $this->assign('config',$config); + + $configUnset=array(); + $configSetted=array(); + if(!$this->eCpattern->get_config('url_complete')){ + $configUnset[]='自动补全网址'; + } + if($this->eCpattern->renderer_is_open($pageType,$pageName)){ + $configSetted[]='页面渲染'; + } + if(g_sc_c('proxy','open')){ + $configSetted[]='代理'; + } + + header("Content-type:text/html;charset=utf-8"); + + $this->assign('configTips',array('setted'=>$configSetted,'unset'=>$configUnset)); + $this->assign('html',$html); return $this->fetch('cpattern:browser'); }else{ @@ -1190,7 +1197,6 @@ class CpatternTest extends BaseController { return $matches; } - public function matchAction(){ if(request()->isPost()){ $this->_test_init(); @@ -1199,63 +1205,49 @@ class CpatternTest extends BaseController { $field=input('field/a',array(),'trim'); if($inputType=='url'){ + $pageSource=input('page_source',''); $url=input('url','','trim'); - $charset=input('charset',''); - $charsetCustom=input('charset_custom',''); - $formMethod=input('form_method',''); - $contentType=input('content_type',''); - $formNames=trim_input_array('form_names'); - $formVals=trim_input_array('form_vals'); - $headerGlobal=input('header_global',''); - $headerNames=trim_input_array('header_names'); - $headerVals=trim_input_array('header_vals'); + $config=input('config/a',array(),'trim'); + if(empty($pageSource)){ + $this->error('请选择页面类型'); + } if(empty($url)){ $this->error('请输入网址'); } - $charset=$charset=='custom'?$charsetCustom:$charset; - if(empty($charset)){ - - $charset=$this->eCpattern->config['charset']; + list($pageType,$pageName)=$this->eCpattern->page_source_split($pageSource); + $config=$this->eCpattern->page_set_config($pageType, $config); + init_array($config); + + $eCpConfig1=null; + $eCpConfig2=null; + + if($pageType=='front_url'||$pageType=='level_url'||$pageType=='relation_url'){ + foreach ($this->eCpattern->config[$pageType.'s'] as $k=>$v){ + if($v&&is_array($v)&&$v['name']==$pageName){ + $eCpConfig1=&$this->eCpattern->config[$pageType.'s'][$k]; + $eCpConfig2=&$this->eCpattern->config['new_'.$pageType.'s'][$v['name']]; + break; + } + } + }elseif($pageType=='source_url'){ + if($this->eCpattern->source_is_url()){ + $eCpConfig1=&$this->eCpattern->config; + }else{ + $eCpConfig1=&$this->eCpattern->config['source_config']; + } + }elseif($pageType=='url'){ + $eCpConfig1=&$this->eCpattern->config; + } + if($eCpConfig1){ + $eCpConfig1=array_merge($eCpConfig1,$config); + } + if($eCpConfig2){ + $eCpConfig2=array_merge($eCpConfig2,$config); } - $headers=array(); - if(empty($headerGlobal)){ - - $headers=$this->eCpattern->config_params['headers']['page']; - }elseif($headerGlobal=='y'){ - - $headers=$this->eCpattern->config_params['headers']['page_headers']; - } - - $useCookie=\util\Param::get_gsc_use_cookie(false,true); - - if(!empty($useCookie)){ - unset($headers['cookie']); - $headers['cookie']=$useCookie; - } - - $headers=\util\Funcs::array_key_merge($headers,$this->eCpattern->arrays_to_key_val($headerNames, $headerVals)); - - if($contentType){ - $headers['content-type']=$contentType; - } - - $formData=$this->eCpattern->arrays_to_key_val($formNames, $formVals); - - $postData=false; - if($formMethod=='post'){ - - $postData=$formData; - }else{ - - $postData=false; - - $url=\util\Funcs::url_params_charset($url,$formData,$charset); - } - - $content=$this->eCpattern->get_html($url,$postData,$headers,$charset); + $content=$this->eCpattern->get_page_html($url, $pageType, $pageName); }else{ $content=input('content','','trim'); @@ -1265,7 +1257,7 @@ class CpatternTest extends BaseController { } $val=''; - + if($type=='rule'){ $rule=$this->eCpattern->convert_sign_match($field['rule']); @@ -1303,6 +1295,17 @@ class CpatternTest extends BaseController { }else{ $this->_test_init(true,true); $this->set_html_tags('模拟匹配','模拟匹配'.$this->_opened_tools()); + + $defConfig=array('charset'=>'','encode'=>'','page_render'=>''); + foreach($defConfig as $k=>$v){ + $defConfig[$k]=$this->eCpattern->get_config($k); + $defConfig[$k]=htmlspecialchars($defConfig[$k]); + } + $defConfig['request_headers_open']=$this->eCpattern->get_config('request_headers','open'); + + $this->assign('defConfig',$defConfig); + $this->assign('pageSources',$this->eCpattern->page_source_options()); + if(request()->isAjax()){ return view('cpattern:test_match_ajax'); }else{ diff --git a/vendor/skycaiji/app/admin/controller/Develop.php b/vendor/skycaiji/app/admin/controller/Develop.php index 5ff9c12..87d8c60 100644 --- a/vendor/skycaiji/app/admin/controller/Develop.php +++ b/vendor/skycaiji/app/admin/controller/Develop.php @@ -93,7 +93,7 @@ class Develop extends BaseController { $this->create_cms_app(array('name'=>$name,'app'=>$appName), $params,$is_edit); }else{ - $appName=input('app'); + $appName=input('app',''); $appName=ucfirst($appName); $config=array(); @@ -130,7 +130,7 @@ class Develop extends BaseController { $cmsClass=null; $this->error($ex->getMessage()); } - if(is_array($cmsClass->_params)){ + if($cmsClass&&property_exists($cmsClass,'_params')&&is_array($cmsClass->_params)){ foreach ($cmsClass->_params as $k=>$v){ $param=array( 'key'=>$k, @@ -160,6 +160,11 @@ class Develop extends BaseController { $config['params'][]=$param; } } + + if(empty($cmsClass)){ + + $this->assign('noClass',1); + } } } $this->set_html_tags( @@ -167,7 +172,7 @@ class Develop extends BaseController { '开发CMS发布插件 ', breadcrumb(array(array('url'=>url('mystore/releaseApp'),'title'=>'CMS发布插件'),array('url'=>url('develop/releaseCms'),'title'=>'开发CMS发布插件'))) ); - + $this->assign('appName',$appName); $this->assign('config',$config); $this->assign('is_old_plugin',$is_old_plugin); return $this->fetch('releaseCms'); @@ -787,10 +792,11 @@ EOF; '开发函数插件 ', breadcrumb(array(array('url'=>url('mystore/funcApp'),'title'=>'函数插件'),array('url'=>url('develop/func'),'title'=>'开发函数插件'))) ); - - if(input('?app')){ + + $app=input('app',''); + if($app){ - $funcData=$mfuncApp->where('app',input('app'))->find(); + $funcData=$mfuncApp->where('app',$app)->find(); if(!empty($funcData)){ $funcClass=$mfuncApp->get_app_class($funcData['module'],$funcData['app']); $funcClass['name']=$funcData['name']; @@ -798,7 +804,7 @@ EOF; $this->assign('funcClass',$funcClass); } } - + $this->assign('app',$app); $this->assign('module',$module); $this->assign('modules',$mfuncApp->funcModules); return $this->fetch(); @@ -938,4 +944,183 @@ EOF; } } } + + + public function editorAction(){ + $type=input('type',''); + $module=input('module',''); + $app=input('app',''); + $mReleApp=model('ReleaseApp'); + $mFuncApp=model('FuncApp'); + $isApp=false; + $setTitle=''; + $setNav=''; + if(!empty($type)){ + if(!empty($app)){ + $isApp=true; + $appcode=''; + if($type=='release'){ + $setTitle='发布插件'; + $appName=$app; + if($module=='diy'){ + + if($mReleApp->isSystemApp($app,'diy')){ + $this->error('不能编辑系统文件'); + } + $setTitle.=' » 自定义'; + if($mReleApp->appFileExists($app,'diy')){ + $appcode=file_get_contents($mReleApp->appFileName($app,'diy')); + } + }else{ + + if($mReleApp->isSystemApp($app,'cms')){ + $this->error('不能编辑系统文件'); + } + $releData=$mReleApp->where('app',$app)->find(); + if(!empty($releData)){ + if($mReleApp->appFileExists($releData['app'],$releData['module'])){ + $appcode=file_get_contents($mReleApp->appFileName($releData['app'],$releData['module'])); + } + if($releData['module']=='cms'){ + $setTitle.=' » cms程序'; + if($releData['name']){ + $appName.='('.$releData['name'].')'; + } + $setNav=breadcrumb(array(array('url'=>url('develop/releaseCms?app='.$app),'title'=>$app),'编辑插件')); + } + } + } + $setTitle.=' » '.$appName; + }elseif($type=='func'){ + $setTitle='函数插件'; + $appName=$app; + $funcData=$mFuncApp->where('app',$app)->find(); + if(!empty($funcData)){ + if(file_exists($mFuncApp->filename($funcData['module'],$funcData['app']))){ + $appcode=file_get_contents($mFuncApp->filename($funcData['module'],$funcData['app'])); + } + if($funcData['module']){ + + $setTitle.=' » '.$mFuncApp->get_func_module_val($funcData['module'],'name'); + } + if($funcData['name']){ + $appName.='('.$funcData['name'].')'; + } + } + $setTitle.=' » '.$appName; + $setNav=breadcrumb(array(array('url'=>url('develop/func?app='.$app),'title'=>$app),'编辑插件')); + } + $appcode=$appcode?:''; + if($setTitle){ + $setTitle='编辑插件:'.$setTitle.''; + } + } + }else{ + $type='release'; + } + $appList=array(); + if($type=='release'){ + $appList=$mReleApp->order('app asc')->column('name','app'); + init_array($appList); + $mRele=model('Release'); + + $diyList=$mRele->where('module','diy')->column('config','id'); + if($diyList){ + foreach ($diyList as $k=>$v){ + $diyApp=''; + if($v){ + $v=unserialize($v); + if(is_array($v)&&is_array($v['diy'])&&$v['diy']['type']=='app'&&$v['diy']['app']){ + $diyApp=$v['diy']['app']; + } + } + if($diyApp&&!$mReleApp->isSystemApp($diyApp,'diy')){ + $diyList[$k]=$diyApp; + }else{ + unset($diyList[$k]); + } + } + if($diyList){ + $diyList=array_unique($diyList); + sort($diyList); + $this->assign('diyList',$diyList); + } + } + }elseif($type=='func'){ + $appList=$mFuncApp->order('app asc')->column('name','app'); + } + init_array($appList); + + $setTitle=$setTitle?:'插件编辑器'; + $setNav=$setNav?:breadcrumb(array(array('url'=>url('develop/editor'),'title'=>'插件编辑器'))); + + $this->set_html_tags( + '插件编辑器', + $setTitle, + $setNav + ); + + $this->assign('config',array('type'=>$type,'module'=>$module,'app'=>$app)); + $this->assign('type',$type); + $this->assign('module',$module); + $this->assign('app',$app); + $this->assign('isApp',$isApp); + $this->assign('appList',$appList); + $this->assign('appcode',$appcode); + return $this->fetch(); + } + + public function editor_codeAction(){ + $appcode=input('appcode','','trim'); + $this->assign('appcode',$appcode); + return $this->fetch('editor_code'); + } + + public function editor_saveAction(){ + $this->ajax_check_userpwd(); + if(request()->isPost()){ + $type=input('type',''); + $module=input('module',''); + $app=input('app',''); + $appcode=input('appcode','','trim'); + + $filename=''; + if($type=='release'){ + $mReleApp=model('ReleaseApp'); + $module=$module=='diy'?'diy':'cms'; + if($mReleApp->isSystemApp($app,$module)){ + $this->error('不能编辑系统文件'); + } + if(!$mReleApp->isRightApp($app,$module)){ + $this->error('插件名称格式错误'); + } + if($module=='cms'){ + + $releData=$mReleApp->where(array('app'=>$app,'module'=>'cms'))->find(); + if(empty($releData)){ + $this->error('插件不存在'); + } + } + $filename=$mReleApp->appFileName($app,$module); + }elseif($type=='func'){ + $mFuncApp=model('FuncApp'); + + $funcData=$mFuncApp->where('app',$app)->find(); + if(empty($funcData)||empty($funcData['module'])||empty($funcData['app'])){ + $this->error('插件不存在'); + } + $filename=$mFuncApp->filename($funcData['module'],$funcData['app']); + }else{ + $this->error('类型错误'); + } + if(empty($filename)){ + $this->error('插件文件错误'); + } + file_put_contents($filename, $appcode); + $uri=sprintf('develop/editor?type=%s&module=%s&app=%s',$type,$module,$app); + $this->success('操作成功',$uri); + }else{ + $this->error('提交错误'); + } + } } \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/controller/Index.php b/vendor/skycaiji/app/admin/controller/Index.php index b48c9ae..1fce7d2 100644 --- a/vendor/skycaiji/app/admin/controller/Index.php +++ b/vendor/skycaiji/app/admin/controller/Index.php @@ -553,7 +553,6 @@ class Index extends CollectController{ $this->collect_tasks($taskIds,input('collect_num/d'),input('collect_auto')); } - private function _collect_check_key(){ if(is_empty(session('user_login'))){ @@ -564,4 +563,26 @@ class Index extends CollectController{ } return true; } + + + public function proc_open_execAction(){ + $key=input('key'); + if(empty($key)||$key!=\util\Param::get_proc_open_exec_key()){ + $this->error('密钥错误'); + } + $params=cache('proc_open_exec_params'); + + \util\Param::set_proc_open_exec_key(); + cache('proc_open_exec_params',null); + + $info=array(); + if(!empty($params)&&is_array($params)){ + $timeout=intval($params[2]); + $timeout=max($timeout,15); + set_time_limit($timeout); + \util\Funcs::close_session(); + $info=\util\Tools::proc_open_exec($params[0],$params[1],$params[2],$params[3],$params[4]); + } + return json($info); + } } \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/controller/Mystore.php b/vendor/skycaiji/app/admin/controller/Mystore.php index 05b9ab7..74d91d6 100644 --- a/vendor/skycaiji/app/admin/controller/Mystore.php +++ b/vendor/skycaiji/app/admin/controller/Mystore.php @@ -704,6 +704,16 @@ class Mystore extends BaseController { } } + private function _safe_unserialize($code){ + $code=base64_decode(trim($code)); + if(preg_match('/\bO\:\d+\:[\'\"][^\'\"]+?[\'\"]/',$code)){ + + $this->error('错误的文件'); + } + $code=unserialize($code); + return $code; + } + public function _upload_addon($typeIsRule,$formFileName,$installRule,$installPlugin){ $typeName=$typeIsRule?'规则':'插件'; $file=$_FILES[$formFileName]; @@ -717,7 +727,7 @@ class Mystore extends BaseController { if(preg_match_all('/\/\*skycaiji-plugin-start\*\/(?P[\s\S]+?)\/\*skycaiji-plugin-end\*\//i',$fileTxt,$fileMatches)){ foreach ($fileMatches['data'] as $k=>$v){ $v=$v?:''; - $v=unserialize(base64_decode(trim($v))); + $v=$this->_safe_unserialize($v); if($v['type']&&$v['app']){ $pluginDataList[$v['type'].':'.$v['app']]=$v; } @@ -727,7 +737,7 @@ class Mystore extends BaseController { if($typeIsRule){ if(preg_match('/\/\*skycaiji-collector-start\*\/(?P[\s\S]+?)\/\*skycaiji-collector-end\*\//i',$fileTxt,$fileMatch)){ - $ruleData=unserialize(base64_decode(trim($fileMatch['data']))); + $ruleData=$this->_safe_unserialize($fileMatch['data']); } if(empty($ruleData)||!is_array($ruleData)){ return return_result('不是规则文件'); diff --git a/vendor/skycaiji/app/admin/controller/Proxy.php b/vendor/skycaiji/app/admin/controller/Proxy.php index f00cbbe..c8690c3 100644 --- a/vendor/skycaiji/app/admin/controller/Proxy.php +++ b/vendor/skycaiji/app/admin/controller/Proxy.php @@ -13,7 +13,7 @@ namespace skycaiji\admin\controller; class Proxy extends BaseController { public function listAction(){ - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); $cond=array(); $search=array( 'num'=>input('num/d',200), @@ -21,7 +21,8 @@ class Proxy extends BaseController { 'user'=>input('user'), 'pwd'=>input('pwd'), 'type'=>input('?type')?input('type'):'all', - 'invalid'=>input('?invalid')?input('invalid'):'all', + 'invalid'=>input('?invalid')?input('invalid'):'all', + 'group_id'=>input('?group_id')?input('group_id'):'all', ); if(!empty($search['ip'])){ @@ -35,6 +36,10 @@ class Proxy extends BaseController { $cond['invalid']=$search['invalid']; } + if($search['group_id']!='all'){ + + $cond['group_id']=$search['group_id']; + } if(!empty($search['user'])){ $cond['user']=$search['user']; @@ -71,72 +76,66 @@ class Proxy extends BaseController { $urlParams=input('param.'); $urlParams=http_build_query($urlParams); + $this->assign('proxyGroups',model('ProxyGroup')->getAll()); $this->assign('proxyTypes',$mproxy->proxy_types()); $this->assign('search',$search); $this->assign('urlParams',$urlParams); return $this->fetch(); } + private function _input_str2json($name){ + $data=input($name,'','trim'); + $data=empty($data)?array():json_decode($data,true); + init_array($data); + $data=array_map('trim', $data); + return $data; + } + public function opAction(){ $op=input('op'); $listUrl=input('url_params','','trim'); $listUrl='proxy/list?'.ltrim($listUrl,'?'); - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); if($op=='delete'){ $ip=input('ip'); $mproxy->where('ip',$ip)->delete(); $this->success('删除成功',$listUrl); }elseif($op=='delete_all'){ - $ips=input('ips','','trim'); - $ips=empty($ips)?array():json_decode($ips,true); - $ips=array_map('trim', $ips); - + $ips=$this->_input_str2json('ips'); if(!empty($ips)){ $mproxy->where('ip','in',$ips)->delete(); } $this->success('删除成功',$listUrl); }elseif($op=='update_all'){ - $ips=input('ips','','trim'); - $ip_list=input('ip_list','','trim'); - $user_list=input('user_list','','trim'); - $pwd_list=input('pwd_list','','trim'); - $type_list=input('type_list','','trim'); - - $ips=empty($ips)?array():json_decode($ips,true); - $ip_list=empty($ip_list)?array():json_decode($ip_list,true); - $user_list=empty($user_list)?array():json_decode($user_list,true); - $pwd_list=empty($pwd_list)?array():json_decode($pwd_list,true); - $type_list=empty($type_list)?array():json_decode($type_list,true); - - $ips=array_map('trim', $ips); - $ip_list=array_map('trim', $ip_list); - $user_list=array_map('trim', $user_list); - $pwd_list=array_map('trim', $pwd_list); - $type_list=array_map('trim', $type_list); - + $ips=$this->_input_str2json('ips'); + $paramNames=array('ip'=>'ip_list','user'=>'user_list','pwd'=>'pwd_list','type'=>'type_list','group_id'=>'gid_list'); + $paramDatas=array(); + foreach($paramNames as $paramField=>$paramName){ + $paramDatas[$paramField]=$this->_input_str2json($paramName); + } for($i=0;$istrict(false)->where('ip',$ips[$i])->update(array( - 'ip'=>$ip_list[$i], - 'user'=>$user_list[$i], - 'pwd'=>$pwd_list[$i], - 'type'=>$type_list[$i], - )); + $proxyData=array(); + foreach ($paramDatas as $paramField=>$paramData){ + $proxyData[$paramField]=$paramData[$i]; + } + $mproxy->strict(false)->where('ip',$ips[$i])->update($proxyData); } $this->success('修改成功',$listUrl); } } public function addAction(){ - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); $proxyTypes=$mproxy->proxy_types(); if(request()->isPost()){ $ip_list=input('ip_list/a',array(),'trim'); $user_list=input('user_list/a',array(),'trim'); $pwd_list=input('pwd_list/a',array(),'trim'); $type_list=input('type_list/a',array(),'trim'); + $gid_list=input('gid_list/a',array(),'intval'); if(!empty($ip_list)){ $nowTime=time(); @@ -146,6 +145,7 @@ class Proxy extends BaseController { 'user'=>$user_list[$k], 'pwd'=>$pwd_list[$k], 'type'=>$type_list[$k], + 'group_id'=>$gid_list[$k], 'addtime'=>$nowTime ); $mproxy->db()->strict(false)->insert($newData,true); @@ -155,15 +155,15 @@ class Proxy extends BaseController { $this->error('请添加ip'); } }else{ + $this->assign('proxyGroups',model('ProxyGroup')->getAll()); $this->assign('proxyTypes',$proxyTypes); return $this->fetch(); } - } /*批量添加代理*/ public function batchAction(){ - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); $proxyTypes=$mproxy->proxy_types(); if(request()->isPost()){ $type=input('type'); @@ -171,11 +171,12 @@ class Proxy extends BaseController { $fmt=input('format','','trim'); $user=input('user','','trim'); $pwd=input('pwd','','trim'); + $groupId=input('group_id/d',0); $ipList=array(); if(!empty($fmt)&&preg_match_all('/[^\r\n]+/',$ips,$mips)){ foreach ($mips[0] as $ip){ - $ip=model('Proxyip')->get_format_ips($ip,$fmt,false); + $ip=model('ProxyIp')->get_format_ips($ip,$fmt,false); if(empty($ip)){ continue; } @@ -186,7 +187,8 @@ class Proxy extends BaseController { $ipList=$mproxy->ips_format2db($ipList,array( 'type'=>$type, 'user'=>$user, - 'pwd'=>$pwd + 'pwd'=>$pwd, + 'group_id'=>$groupId, )); if(empty($ipList)){ @@ -207,6 +209,7 @@ class Proxy extends BaseController { $this->success('批量添加成功'); } }else{ + $this->assign('proxyGroups',model('ProxyGroup')->getAll()); $this->assign('proxyTypes',$proxyTypes); return $this->fetch(); } @@ -214,7 +217,7 @@ class Proxy extends BaseController { /*清理无效ip*/ public function clearInvalidAction(){ - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); $mproxy->where('invalid',1)->delete(); $this->success('清理完成','setting/proxy'); } @@ -222,14 +225,15 @@ class Proxy extends BaseController { /*测试代理接口*/ public function testApiAction(){ $config=input('config/a',array(),'trim'); - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); $html=get_html($config['api_url']); $ips=$mproxy->get_format_ips($html,$config['api_format'],true); $ips=$mproxy->ips_format2db ( $ips, array ( 'type' => $config ['api_type'], 'user' => $config ['api_user'], - 'pwd' => $config ['api_pwd'], + 'pwd' => $config ['api_pwd'], + 'group_id' => $config ['api_group_id'], ) ); @@ -249,4 +253,77 @@ class Proxy extends BaseController { $this->assign('ips',$ips); return $this->fetch('testApi'); } + + + public function groupsAction(){ + $mgroup=model('ProxyGroup'); + $mip=model('ProxyIp'); + if(request()->isPost()){ + $groupIds=input('group_id/a',array(),'intval'); + $groupSorts=input('group_sort/a',array(),'intval'); + $groupNames=input('group_name/a',array(),'trim'); + \util\Funcs::filter_key_val_list3($groupNames,$groupIds,$groupSorts); + + $upDatas=array(); + $addDatas=array(); + $addNames=array(); + foreach ($groupIds as $k=>$groupId){ + $groupData=array('name'=>$groupNames[$k],'sort'=>$groupSorts[$k]); + if($groupId>0){ + + $upDatas[$groupId]=$groupData; + }else{ + $addDatas[]=$groupData; + $addNames[$groupData['name']]=$groupData['name']; + } + } + if($upDatas){ + + $dbNames=$mgroup->where('id','in',array_keys($upDatas))->column('name','id'); + foreach ($dbNames as $dbId=>$dbName){ + $upName=$upDatas[$dbId]['name']; + if($dbName!=$upName&&$mgroup->where('name',$upName)->count()>0){ + + unset($upDatas[$dbId]); + } + } + + foreach ($upDatas as $upId=>$upData){ + $mgroup->strict(false)->where('id',$upId)->update($upData); + } + } + if($addDatas){ + + $dbNames=$mgroup->where('name','in',$addNames)->column('name','id'); + if($dbNames){ + + foreach ($addDatas as $k=>$addData){ + if(in_array($addData['name'], $dbNames)){ + unset($addDatas[$k]); + } + } + } + $mgroup->strict(false)->insertAll($addDatas); + } + $this->success('操作成功'); + }else{ + $groups=$mgroup->order('sort desc')->column('id,name,sort'); + init_array($groups); + $groups=array_values($groups); + foreach ($groups as $k=>$v){ + $v['_ip_num']=$mip->where('group_id',$v['id'])->count(); + $groups[$k]=$v; + } + $this->assign('groups',$groups); + return $this->fetch(); + } + } + public function delete_groupAction(){ + $id=input('id/d',0); + if($id>0){ + model('ProxyGroup')->where('id',$id)->delete(); + model('ProxyIp')->where('group_id',$id)->update(array('group_id'=>0)); + } + $this->success('删除成功'); + } } \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/controller/Release.php b/vendor/skycaiji/app/admin/controller/Release.php index 3448d7d..ca73817 100644 --- a/vendor/skycaiji/app/admin/controller/Release.php +++ b/vendor/skycaiji/app/admin/controller/Release.php @@ -12,6 +12,7 @@ namespace skycaiji\admin\controller; use skycaiji\admin\model\DbCommon; +use think\db\Query; class Release extends CollectController{ /*发布设置*/ public function setAction(){ @@ -71,26 +72,20 @@ class Release extends CollectController{ if(!empty($releData)){ $releData=$releData->toArray(); - $releData['config']=unserialize($releData['config']?:''); }else{ $releData=array(); } - if(!is_array($releData['config'])){ - $releData['config']=array(); - } - foreach (config('release_modules') as $v){ - if(!is_array($releData['config'][$v])){ - $releData['config'][$v]=array(); - } - } + + $releData['config']=$mrele->compatible_config($releData['config']); + $this->assign('config',$releData['config']); $this->assign('releData',$releData); $apiRootUrl=config('root_website'); - if(stripos(\think\Request::instance()->root(),'/index.php?s=')!==false){ + if(stripos(\think\Request::instance()->root()?:'','/index.php?s=')!==false){ $apiRootUrl.='/index.php?s='; - }elseif(stripos(\think\Request::instance()->root(),'/index.php')!==false){ + }elseif(stripos(\think\Request::instance()->root()?:'','/index.php')!==false){ $apiRootUrl.='/index.php'; } @@ -244,20 +239,16 @@ class Release extends CollectController{ } public function testAction(){ - $this->_test(); - } - - public function test_toapiAction(){ - $this->_test(array('test_toapi'=>1)); - } - - private function _test($urlParams=null){ $releId=input('id/d',0); $releData=model('Release')->getById($releId); if(empty($releData)){ $this->error(lang('rele_error_empty_rele')); } - + $urlParams=null; + if($releData['module']=='toapi'){ + + $urlParams=array('test_toapi'=>1); + } $this->collect_create_or_run(function()use($releData){ return array($releData['task_id']); },1,null,false,$urlParams); @@ -354,69 +345,138 @@ class Release extends CollectController{ $this->success($msgSuccess); } } + /*数据表绑定数据*/ public function dbTableBindAction(){ - $releId=input('id/d',0); - $table=input('table'); - $tables=explode(',', $table); - $tables=array_filter($tables); - $tables=array_values($tables); - if(empty($table)){ - $this->error('请选择表'); - } - $mrele=model('Release'); - $mtask=model('Task'); - $mcoll=model('Collector'); - $releData=$mrele->where(array('id'=>$releId))->find(); - if(empty($releData)){ - $this->error(lang('rele_error_empty_rele')); - } - $config=unserialize($releData['config']?:''); - $adb=controller('admin/Rdb','event'); - $db_config=$adb->get_db_config($config['db']); - - try { - - $mdb=new DbCommon($db_config); - $fields=array(); - $field_values=array(); - foreach ($tables as $tbName){ - $fields[$tbName]=$mdb->getFields($tbName); - - if(!empty($config['db_table']['field'][$tbName])){ - $tableFields=$config['db_table']['field'][$tbName]; - if(!empty($tableFields)){ - - $issetFields=array(); - foreach ($fields[$tbName] as $k=>$v){ - if(isset($tableFields[$k])){ - $issetFields[$k]=$v; - } - } - $fields[$tbName]=\util\Funcs::array_key_merge($issetFields,$fields[$tbName]); - } - - $field_values[$tbName]['field']=$tableFields; - $field_values[$tbName]['custom']=$config['db_table']['custom'][$tbName]; - } - } - - $taskData=$mtask->getById($releData['task_id']); - - if(!empty($taskData)){ - $collFields=$adb->get_coll_fields($taskData['id'], $taskData['module']); - } - }catch (\Exception $ex){ - $dbMsg=$this->trans_db_msg($ex->getMessage()); - $this->error($dbMsg); - } - - $this->assign('collFields',$collFields); - $this->assign('tables',$tables); - $this->assign('fields',$fields); - $this->assign('field_values',$field_values); - return $this->fetch('dbTableBind'); + $releId=input('id/d',0); + $table=input('table',''); + $isDbTables=input('is_db_tables'); + $mrele=model('Release'); + $mtask=model('Task'); + $mcoll=model('Collector'); + $releData=$mrele->where(array('id'=>$releId))->find(); + if(empty($releData)){ + $this->error(lang('rele_error_empty_rele')); + } + $releData['config']=$mrele->compatible_config($releData['config']); + $dbTables=$releData['config']['db_tables']; + init_array($dbTables); + + $tbTables=array(); + if($isDbTables){ + foreach ($dbTables as $v){ + if(is_array($v)){ + $tbTables[]=$v['table']; + } + } + }else{ + $tbTables=explode(',', $table); + } + $tbTables=array_filter($tbTables); + $tbTables=array_values($tbTables); + if(empty($tbTables)){ + $this->error('请选择表'); + } + init_array($tbTables); + + if(!$isDbTables){ + + $dbTables=array(); + foreach ($tbTables as $v){ + $dbTables[]=array('table'=>$v); + } + } + + $dbTables1=array(); + foreach ($dbTables as $k=>$v){ + init_array($v); + init_array($v['field']); + $k='i_'.\util\Funcs::uniqid($v['table']); + $dbTables1[$k]=$v; + } + $dbTables=$dbTables1; + $seqList=array(); + $adb=controller('admin/Rdb','event'); + $dbConfig=$adb->get_db_config($releData['config']['db']); + try { + + $mdb=new DbCommon($dbConfig); + $tbFields=array(); + foreach ($tbTables as $tbName){ + $tbFields[$tbName]=$mdb->getFields($tbName); + } + + $dbHasSeq=$mrele->db_has_sequence($releData['config']['db']['type']); + if($dbHasSeq){ + + $seqList=$mdb->db()->query('select * from user_sequences'); + foreach ($seqList as $k=>$v){ + $seqList[$k]=$v['SEQUENCE_NAME']; + } + init_array($seqList); + } + }catch (\Exception $ex){ + $dbMsg=$this->trans_db_msg($ex->getMessage()); + $this->error($dbMsg); + } + $this->assign('tbTables',$tbTables); + $this->assign('tbFields',$tbFields); + $this->assign('dbTables',$dbTables); + $this->assign('seqList',$seqList); + $this->assign('dbHasSeq',$mrele->db_has_sequence($releData['config']['db']['type'])); + return $this->fetch('dbTableBind'); } + + public function dbTableBindSingsAction(){ + $collFields=array(); + $autoIds=array(); + $querySigns=array(); + if(request()->isPost()){ + $mrele=model('Release'); + $tableKey=input('table_key','','trim'); + $taskId=input('task_id/d',0); + $dbTables=trim_input_array('db_tables'); + $dbTables=$mrele->config_db_tables($dbTables,true); + + $taskData=model('Task')->getById($taskId); + + if(!empty($taskData)){ + $collFields=controller('admin/Rdb','event')->get_coll_fields($taskData['id'], $taskData['module']); + } + init_array($collFields); + foreach ($dbTables as $tbKey=>$dbTable){ + if($tbKey==$tableKey){ + + break; + } + if(empty($dbTable['op'])){ + + if($dbTable['table']){ + $autoIds[$dbTable['table']]=$dbTable['table']; + } + }elseif($dbTable['op']=='query'){ + + $tbQuery=$dbTable['query']; + foreach ($tbQuery['sign'] as $k=>$v){ + $v=$mrele->db_tables_query_sign($tbQuery['type'][$k],$tbQuery['field'][$k],$v); + $querySigns[$v]=$v; + } + } + } + } + $collFields=array_values($collFields); + $autoIds=array_values($autoIds); + $querySigns=array_values($querySigns); + + $maxCount=max(count($collFields),count($autoIds),count($querySigns)); + + $this->assign('maxCount',$maxCount); + $this->assign('collFields',$collFields); + $this->assign('autoIds',$autoIds); + $this->assign('querySigns',$querySigns); + return $this->fetch('dbTableBindSings'); + } + /*翻译数据库错误信息*/ public function trans_db_msg($msg){ $msg=lang('rele_error_db').str_replace('Unknown database', lang('error_unknown_database'), $msg); diff --git a/vendor/skycaiji/app/admin/controller/Setting.php b/vendor/skycaiji/app/admin/controller/Setting.php index 29aa4e7..c712f5f 100644 --- a/vendor/skycaiji/app/admin/controller/Setting.php +++ b/vendor/skycaiji/app/admin/controller/Setting.php @@ -111,6 +111,8 @@ class Setting extends BaseController { $config['real_time']=input('real_time/d',0); $config['retry']=input('retry/d',0); $config['wait']=input('wait/d',0); + $config['ip_resolve']=input('ip_resolve'); + $config['max_redirs']=input('max_redirs/d',0); unset($config['download_img']); @@ -123,17 +125,10 @@ class Setting extends BaseController { } } - $mconfig->setConfig('caiji',$config); - if($config['auto']){ - - if($config['run']=='backstage'){ - - $bskey=\util\Funcs::uniqid('auto_backstage'); - \util\Param::set_auto_backstage_key($bskey); - @get_html(url('admin/index/auto_backstage?key='.$bskey,null,false,true),null,array('timeout'=>3)); - } - } + + $this->_run_auto_backstage(); + $this->success(lang('op_success'),'setting/caiji'); }else{ $this->set_html_tags( @@ -155,6 +150,23 @@ class Setting extends BaseController { } return $this->fetch(); } + + public function _run_auto_backstage(){ + $mconfig=model('Config'); + $config=$mconfig->getConfig('caiji','data'); + init_array($config); + if($config['auto']){ + + if($config['run']=='backstage'){ + + $bskey=\util\Param::set_auto_backstage_key(); + @get_html(url('admin/index/auto_backstage?key='.$bskey,null,false,true),null,array('timeout'=>3)); + } + } + + $config=$mconfig->getConfig('page_render','data'); + $this->_chrome_start($config); + } /*图片本地化设置*/ public function download_imgAction(){ $mconfig=model('Config'); @@ -290,21 +302,12 @@ class Setting extends BaseController { /*代理设置*/ public function proxyAction(){ $mconfig=model('Config'); - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); if(request()->isPost()){ $config=array(); - $ip_list=input('ip_list','','trim'); - $user_list=input('user_list','','trim'); - $pwd_list=input('pwd_list','','trim'); - $type_list=input('type_list','','trim'); - - $ip_list=empty($ip_list)?array():json_decode($ip_list,true); - $user_list=empty($user_list)?array():json_decode($user_list,true); - $pwd_list=empty($pwd_list)?array():json_decode($pwd_list,true); - $type_list=empty($type_list)?array():json_decode($type_list,true); - $config['open']=input('open/d',0); $config['failed']=input('failed/d',0); + $config['group_id']=input('group_id/d',0); $config['use']=strtolower(input('use')); $config['use_num']=input('use_num/d',0); $config['use_time']=input('use_time/d',0); @@ -316,46 +319,6 @@ class Setting extends BaseController { $this->error('每个IP使用多少分钟必须大于0'); } - - if(!empty($ip_list)&&is_array($ip_list)){ - - $ip_list=array_map('trim', $ip_list); - $user_list=array_map('trim', $user_list); - $pwd_list=array_map('trim', $pwd_list); - $type_list=array_map('trim', $type_list); - - - $nowTime=time(); - for($k=count($ip_list);$k>=0;$k--){ - $v=$ip_list[$k]; - if(empty($v)){ - - continue; - } - $newData=array( - 'ip'=>$v, - 'user'=>$user_list[$k], - 'pwd'=>$pwd_list[$k], - 'type'=>$type_list[$k], - 'invalid'=>0, - 'failed'=>0, - 'num'=>0, - 'time'=>0, - 'addtime'=>$nowTime, - ); - if($mproxy->where(array('ip'=>$newData['ip']))->count()>0){ - - unset($newData['invalid']); - - $mproxy->strict(false)->where(array('ip'=>$newData['ip']))->update($newData); - }else{ - - $mproxy->db()->insert($newData,true); - } - } - } - - $config['api']=input('api/a',array(),'trim'); $config['apis']=input('apis/a',array(),'trim'); $config['apis']=is_array($config['apis'])?$config['apis']:array(); @@ -374,10 +337,11 @@ class Setting extends BaseController { '代理设置', '代理设置', breadcrumb(array(array('url'=>url('setting/caiji'),'title'=>lang('setting_caiji')),array('url'=>url('setting/proxy'),'title'=>'代理'))) - ); + ); $proxyConfig=$mconfig->getConfig('proxy','data'); init_array($proxyConfig); $proxyConfig['ip_count']=$mproxy->count(); + $this->assign('proxyGroups',model('ProxyGroup')->getAll()); $this->assign('proxyConfig',$proxyConfig); $this->assign('proxyTypes',$mproxy->proxy_types()); } @@ -519,6 +483,8 @@ class Setting extends BaseController { $config['tool']=strtolower(input('tool')); $config['chrome']=input('chrome/a',array()); $config['timeout']=input('timeout/d'); + $config['wait_end_ms']=input('wait_end_ms/d',0); + $config['wait_end_num']=input('wait_end_num/d',0); if(!in_array($config['tool'],array('chrome'))){ $config['tool']=''; } @@ -528,10 +494,7 @@ class Setting extends BaseController { } $mconfig->setConfig('page_render',$config); - if($config['tool']=='chrome'){ - $chromeSoket=new \util\ChromeSocket($config['chrome']['host'],$config['chrome']['port'],$config['timeout'],$config['chrome']['filename'],$config['chrome']); - $this->_chrome_start($chromeSoket); - } + $this->_chrome_start($config); $this->success(lang('op_success'),'setting/page_render'); }else{ $this->set_html_tags( @@ -544,17 +507,16 @@ class Setting extends BaseController { init_array($config['chrome']); $this->assign('config',$config); - if($mconfig->page_render_is_chrome(true,$config['tool'])){ - - $chromeSoket=new \util\ChromeSocket($config['chrome']['host'],$config['chrome']['port'],$config['timeout'],$config['chrome']['filename'],$config['chrome']); - $toolIsOpen=$chromeSoket->hostIsOpen(); - $this->assign('toolIsOpen',$toolIsOpen); - } + $chromeSocket=$this->_chrome_socket($config); + $toolIsOpen=$chromeSocket?$chromeSocket->hostIsOpen():false; + $this->assign('toolIsOpen',$toolIsOpen); + return $this->fetch('page_render'); } } /*清理缓存目录*/ public function cleanAction(){ + $clearPageRender=model('Config')->page_render_is_chrome()?true:false; if(request()->isPost()){ set_time_limit(1000); $types=input('types/a'); @@ -586,6 +548,11 @@ class Setting extends BaseController { \util\Tools::clear_runtime_dir($systemPaths); } } + + if($clearPageRender&&(in_array('all', $types)||in_array('page_render', $types))){ + + $this->_clear_page_render(); + } } if(in_array('all', $types)||in_array('data', $types)){ @@ -607,6 +574,7 @@ class Setting extends BaseController { $this->success('清理完成','backstage/index'); }else{ + $this->assign('clearPageRender',$clearPageRender); return $this->fetch(); } } @@ -640,7 +608,7 @@ class Setting extends BaseController { $this->ajax_check_userpwd(); $chrome=input('chrome/a',array()); - $return=\util\ChromeSocket::execHeadless($chrome['filename'], $chrome['port'], $chrome, 'all', true); + $return=\util\ChromeSocket::execHeadless($chrome['filename'], $chrome['port'], $chrome, true); if(!empty($return['error'])){ $this->error($return['error']); @@ -670,33 +638,52 @@ class Setting extends BaseController { } public function chrome_cleanAction(){ - $config=model('Config')->getConfig('page_render','data'); - init_array($config); - init_array($config['chrome']); - $chromeSoket=new \util\ChromeSocket($config['chrome']['host'],$config['chrome']['port'],$config['timeout'],$config['chrome']['filename'],$config['chrome']); - $chromeSoket->clearBrowser(); + $this->_clear_page_render(); $this->success('清理完成',''); } public function chrome_restartAction(){ $config=model('Config')->getConfig('page_render','data'); - init_array($config); - init_array($config['chrome']); - $chromeSoket=new \util\ChromeSocket($config['chrome']['host'],$config['chrome']['port'],$config['timeout'],$config['chrome']['filename'],$config['chrome']); - $this->_chrome_start($chromeSoket); + $this->_chrome_start($config,true); $this->success('重启完成','setting/page_render'); } - private function _chrome_start($chromeSoket){ - if($chromeSoket){ + private function _chrome_start($config,$restart=false){ + init_array($config); + $chromeSocket=$this->_chrome_socket($config); + if($chromeSocket){ try { - $chromeSoket->closeBrowser(); - $chromeSoket->openHost(); + if($restart){ + + $chromeSocket->closeBrowser(); + $chromeSocket->openHost(); + }else{ + if(!$chromeSocket->hostIsOpen()){ + + $chromeSocket->openHost(); + } + } }catch (\Exception $ex){ $this->error($ex->getMessage()); } - }else{ - $this->error('失败'); + } + } + + private function _chrome_socket($config){ + init_array($config); + init_array($config['chrome']); + $chromeSocket=null; + if(model('Config')->page_render_is_chrome(true,$config['tool'])){ + $chromeSocket=new \util\ChromeSocket($config['chrome']['host'],$config['chrome']['port'],$config['timeout'],$config['chrome']['filename'],$config['chrome']); + } + return $chromeSocket; + } + + private function _clear_page_render(){ + $config=model('Config')->getConfig('page_render','data'); + $chromeSocket=$this->_chrome_socket($config); + if($chromeSocket){ + $chromeSocket->clearBrowser(); } } } \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/controller/Task.php b/vendor/skycaiji/app/admin/controller/Task.php index 7464f47..32f3fe5 100644 --- a/vendor/skycaiji/app/admin/controller/Task.php +++ b/vendor/skycaiji/app/admin/controller/Task.php @@ -331,7 +331,6 @@ class Task extends CollectController { }else{ $mtaskgroup=model('Taskgroup'); $tgSelect=$mtaskgroup->getLevelSelect(); - $gConfig=$this->_global_caiji_config(); if($isAdd){ $this->set_html_tags( lang('task_add'), @@ -375,8 +374,39 @@ class Task extends CollectController { $this->assign('timerInfo',$timerInfo); $this->assign('fieldList',$fieldList); } + + $imgFuncParam=g_sc_c('download_img','img_func_param'); + if($imgFuncParam){ + $imgFuncParam=str_replace("\r", '\r', $imgFuncParam); + $imgFuncParam=str_replace("\n", '\n', $imgFuncParam); + $imgFuncParam=htmlspecialchars($imgFuncParam,ENT_QUOTES); + }else{ + $imgFuncParam=''; + } + $proxyGroupId=g_sc_c('proxy','group_id'); + $proxyGroupId=intval($proxyGroupId); + $gConfig=array( + 'num'=>intval(g_sc_c('caiji','num')), + 'interval'=>intval(g_sc_c('caiji','interval')), + 'interval_html'=>intval(g_sc_c('caiji','interval_html')), + 'same_url'=>g_sc_c('caiji','same_url')>0?'允许':'过滤', + 'same_title'=>g_sc_c('caiji','same_title')>0?'允许':'过滤', + 'real_time'=>g_sc_c('caiji','real_time')>0?'是':'否', + 'proxy'=>g_sc_c('proxy','open')>0?'1':'', + 'proxy_group_id'=>$proxyGroupId<=0?'全部':model('ProxyGroup')->getNameById($proxyGroupId), + 'download_img'=>g_sc_c('download_img','download_img')>0?'1':'', + 'img_path'=>g_sc_c('download_img','img_path')?g_sc_c('download_img','img_path'):(config('root_path').DS.'data'.DS.'images'), + 'img_url'=>g_sc_c('download_img','img_url')?g_sc_c('download_img','img_url'):(config('root_website').'/data/images'), + 'img_name'=>g_sc_c('download_img','img_name'), + 'name_custom_path'=>g_sc_c('download_img','name_custom_path')?g_sc_c('download_img','name_custom_path'):'无', + 'name_custom_name'=>lang('down_img_name_custom_name_'.g_sc_c('download_img','name_custom_name')), + 'interval_img'=>intval(g_sc_c('download_img','interval_img')), + 'img_func'=>g_sc_c('download_img','img_func'), + 'img_func_param'=>$imgFuncParam + ); $this->assign('gConfig',$gConfig); $this->assign('tgSelect',$tgSelect); + $this->assign('proxyGroups',model('ProxyGroup')->getAll()); if(request()->isAjax()){ return view('save_ajax'); }else{ @@ -399,38 +429,6 @@ class Task extends CollectController { } - private function _global_caiji_config(){ - $imgFuncParam=g_sc_c('download_img','img_func_param'); - if($imgFuncParam){ - $imgFuncParam=str_replace("\r", '\r', $imgFuncParam); - $imgFuncParam=str_replace("\n", '\n', $imgFuncParam); - $imgFuncParam=htmlspecialchars($imgFuncParam,ENT_QUOTES); - }else{ - $imgFuncParam=''; - } - - $gConfig=array( - 'num'=>intval(g_sc_c('caiji','num')), - 'interval'=>intval(g_sc_c('caiji','interval')), - 'interval_html'=>intval(g_sc_c('caiji','interval_html')), - 'same_url'=>g_sc_c('caiji','same_url')>0?'允许':'过滤', - 'same_title'=>g_sc_c('caiji','same_title')>0?'允许':'过滤', - 'real_time'=>g_sc_c('caiji','real_time')>0?'是':'否', - 'proxy'=>g_sc_c('proxy','open')>0?'是':'否', - 'download_img'=>g_sc_c('download_img','download_img')>0, - 'img_path'=>g_sc_c('download_img','img_path')?g_sc_c('download_img','img_path'):(config('root_path').DS.'data'.DS.'images'), - 'img_url'=>g_sc_c('download_img','img_url')?g_sc_c('download_img','img_url'):(config('root_website').'/data/images'), - 'img_name'=>g_sc_c('download_img','img_name'), - 'name_custom_path'=>g_sc_c('download_img','name_custom_path')?g_sc_c('download_img','name_custom_path'):'无', - 'name_custom_name'=>lang('down_img_name_custom_name_'.g_sc_c('download_img','name_custom_name')), - 'interval_img'=>intval(g_sc_c('download_img','interval_img')), - 'img_func'=>g_sc_c('download_img','img_func'), - 'img_func_param'=>$imgFuncParam - ); - return $gConfig; - } - - private function _save_config($config=array()){ $config=is_array($config)?$config:array(); $config['num']=intval($config['num']); @@ -439,7 +437,8 @@ class Task extends CollectController { $config['img_path']=trim($config['img_path']); $config['img_url']=trim($config['img_url']); $config['interval_img']=intval($config['interval_img']); - + $config['proxy_group_id']=trim($config['proxy_group_id']); + $mconfig=model('Config'); if(!empty($config['img_path'])){ diff --git a/vendor/skycaiji/app/admin/event/CollectBase.php b/vendor/skycaiji/app/admin/event/CollectBase.php index 29f87c1..e398c85 100644 --- a/vendor/skycaiji/app/admin/event/CollectBase.php +++ b/vendor/skycaiji/app/admin/event/CollectBase.php @@ -19,13 +19,24 @@ class CollectBase extends \skycaiji\admin\controller\CollectController { $this->echo_msg($msg,'red'); return null; }else{ - parent::error($msg,$url,$data,$wait,$header); + $url=$url?$url:''; + + $msg=$this->_echo_msg_str($msg,'red'); + $txt=g_sc('collect_echo_msg_txt'); + $txt=$txt?($txt."\r\n".$msg):$msg; + parent::error($txt,$url,$data,$wait,$header); } } /*采集器的输出内容需要重写,只有正在采集时才输出内容*/ public function echo_msg($strArgs,$color='red',$echo=true,$end_str='',$div_style=''){ if($this->is_collecting()){ parent::echo_msg($strArgs,$color,$echo,$end_str,$div_style); + }else{ + + $msg=$this->_echo_msg_str($strArgs,$color,$end_str,$div_style); + $txt=g_sc('collect_echo_msg_txt'); + $txt=$txt?($txt."\r\n".$msg):$msg; + set_g_sc('collect_echo_msg_txt',$txt); } } @@ -229,5 +240,49 @@ class CollectBase extends \skycaiji\admin\controller\CollectController { } return true; } + + public function retry_first_echo($retryCur,$msg,$url=null,$htmlInfo=null){ + if($retryCur<=0){ + $msg=$msg?:''; + if(is_array($htmlInfo)&&$htmlInfo['error']&&is_array($htmlInfo['error'])){ + + $msg.='»Curl Error '.$htmlInfo['error']['no'].': '.$htmlInfo['error']['msg']; + } + $msg=htmlspecialchars($msg); + if($url){ + $url=htmlspecialchars($url); + $msg='
    '.$msg.':'.$url.'
    '; + } + $this->echo_msg($msg); + } + } + + public function retry_do_func(&$retryCur,$retryMax,$echoMsg,$echoError=null){ + $do=false; + if($retryMax>0){ + + if($retryCur<$retryMax){ + + $retryCur++; + $this->echo_msg(array('%s第%s次',$retryCur>1?' / ':'重试:',$retryCur),'black',true,'','display:inline;'); + $do=true; + }else{ + $retryCur=0; + if($this->is_collecting()){ + + if($echoMsg){ + $this->echo_msg(' / '.htmlspecialchars($echoMsg),'black',true,'','display:inline;margin-right:5px;'); + } + }else{ + + if($echoError){ + $this->echo_error(htmlspecialchars($echoError)); + } + } + + } + } + return $do; + } } ?> \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/event/Cpattern.php b/vendor/skycaiji/app/admin/event/Cpattern.php index 6b9119f..cc371fc 100644 --- a/vendor/skycaiji/app/admin/event/Cpattern.php +++ b/vendor/skycaiji/app/admin/event/Cpattern.php @@ -19,6 +19,7 @@ class Cpattern extends CpatternEvent{ $config['url_reverse']=intval($config['url_reverse']); $config['page_render']=intval($config['page_render']); $config['url_repeat']=intval($config['url_repeat']); + $config['url_no_name']=intval($config['url_no_name']); if(!is_array($config['regexp_flags'])){ $config['regexp_flags']=array(); @@ -176,9 +177,11 @@ class Cpattern extends CpatternEvent{ ); } - $config['charset'] = $config['charset']=='custom' ? $config ['charset_custom'] : $config ['charset']; + $config['charset']= $config['charset']=='custom' ? $config ['charset_custom'] : $config ['charset']; $config['charset']= empty($config['charset'])?'auto':$config['charset']; + $config['encode']=$config['encode']=='custom' ? $config ['encode_custom'] : $config ['encode']; + $config['regexp_flags']=is_array($config['regexp_flags'])?$config['regexp_flags']:array(); $regexpFlags=''; @@ -493,7 +496,7 @@ class Cpattern extends CpatternEvent{ if(g_sc_c('caiji','robots')){ $opened_tools[]='遵守robots协议'; } - if($this->config['page_render']){ + if($this->page_render_is_open()){ $opened_tools[]='页面渲染'; } if(g_sc_c('download_img','download_img')){ @@ -607,15 +610,13 @@ class Cpattern extends CpatternEvent{ if($sourceIsUrl){ $source_urls=array_values($source_urls); - $this->cont_urls_list['_source_is_url_']=$this->page_convert_url_signs('url', '', $source_urls, array(), false); + $this->cont_urls_list['_source_is_url_']=$this->page_convert_url_signs('url', '', false, $source_urls, array(), false); $source_urls=array('_source_is_url_'=>'_source_is_url_'); }else{ - $source_urls=$this->page_convert_url_signs('source_url', '', $source_urls, array(), false); + $source_urls=$this->page_convert_url_signs('source_url', '', false, $source_urls, array(), false); } - - $isFormPost=$this->page_is_post('source_url')?'[POST] ':''; - + $pageOpened=$this->page_opened_tips('source_url'); foreach ($source_urls as $key_source_url=>$source_url){ $this->cur_source_url=$source_url; if(array_key_exists($source_url,$this->used_source_urls)){ @@ -649,7 +650,7 @@ class Cpattern extends CpatternEvent{ } } $this->used_pagination_urls['source_url'][$pageCurMd5]=1; - $this->echo_msg($isFormPost?array('采集起始页%s:%s',$pagePnStr,$isFormPost.$pageCurUrl):array('采集起始页%s:%s',$pagePnStr,$pageCurUrl,$pageCurUrl),$pageIsPn?'black':'green'); + $this->echo_msg($pageOpened?array('采集起始页%s:%s',$pagePnStr,$pageOpened.$pageCurUrl):array('采集起始页%s:%s',$pagePnStr,$pageCurUrl,$pageCurUrl),$pageIsPn?'black':'green'); if(!empty($this->config['level_urls'])){ @@ -718,8 +719,8 @@ class Cpattern extends CpatternEvent{ } } if($frontUrl){ - $isFormPost=$this->page_is_post('front_url',$fuv['name'])?'[POST] ':''; - $this->echo_msg($isFormPost?array('采集前置页“%s”:%s',$fuv['name'],$isFormPost.$frontUrl):array('采集前置页“%s”:%s',$fuv['name'],$frontUrl,$frontUrl),'black'); + $pageOpened=$this->page_opened_tips('front_url',$fuv['name']); + $this->echo_msg($pageOpened?array('采集前置页“%s”:%s',$fuv['name'],$pageOpened.$frontUrl):array('采集前置页“%s”:%s',$fuv['name'],$frontUrl,$frontUrl),'black'); $htmlInfo=$this->get_page_html($frontUrl,'front_url',$fuv['name'],false,true); if($fuv['use_cookie']||$fuv['use_cookie_img']){ @@ -880,8 +881,7 @@ class Cpattern extends CpatternEvent{ $allowColl=false; } if($allowColl){ - $base_url=$this->match_base_url($fromUrl, $html); - $domain_url=$this->match_domain_url($fromUrl); + $url_info=$this->match_url_info($fromUrl,$html); $pn_area=''; if(!empty($pnConfig['reg_area'])){ @@ -905,9 +905,9 @@ class Cpattern extends CpatternEvent{ if(!empty($pnConfig['url_complete'])){ - $pn_area=preg_replace_callback('/(\bhref\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche_p_a) use ($base_url,$domain_url){ + $pn_area=preg_replace_callback('/(\bhref\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche_p_a) use ($url_info){ - $matche_p_a[2]=\skycaiji\admin\event\Cpattern::create_complete_url($matche_p_a[2], $base_url, $domain_url); + $matche_p_a[2]=\util\Tools::create_complete_url($matche_p_a[2], $url_info); return $matche_p_a[1].$matche_p_a[2].$matche_p_a[3]; },$pn_area); } @@ -1042,25 +1042,16 @@ class Cpattern extends CpatternEvent{ return; } + $pageOpened=$this->page_opened_tips($pageType,$pageName); $cur_url=$field_source_url; - $this->echo_msg(array('%s:%s',$source_echo_msg,$field_source_url,$field_source_url),'black'); + $this->echo_msg($pageOpened?array('%s:%s',$source_echo_msg,$pageOpened.$field_source_url):array('%s:%s',$source_echo_msg,$field_source_url,$field_source_url),'black'); $htmlInfo=$this->get_page_html($field_source_url, $pageType, $pageName,false,true); $html=$htmlInfo['html']; } } static $fieldArr=array('words','num','time','list'); - static $baseUrls=array(); - static $domainUrls=array(); - - $urlMd5=md5($cur_url); - if(empty($baseUrls[$urlMd5])){ - $baseUrls[$urlMd5]=$this->match_base_url($cur_url, $html); - } - if(empty($domainUrls[$urlMd5])){ - $domainUrls[$urlMd5]=$this->match_domain_url($cur_url); - } - $base_url=$baseUrls[$urlMd5]; - $domain_url=$domainUrls[$urlMd5]; + + $url_info=$this->match_url_info($cur_url, $html, 'set_field'); $val=''; $field_func='field_module_'.$module; @@ -1077,7 +1068,7 @@ class Cpattern extends CpatternEvent{ 'value'=>$v, 'img'=>$this->field_val_list[$field_params['extract']]['imgs'][$cur_url_md5][$k], ); - $val[$k]=$this->field_module_extract($field_params, $extract_field_val, $base_url, $domain_url); + $val[$k]=$this->field_module_extract($field_params, $extract_field_val, $url_info); } }else{ @@ -1085,7 +1076,7 @@ class Cpattern extends CpatternEvent{ 'value'=>$this->field_val_list[$field_params['extract']]['values'][$cur_url_md5], 'img'=>$this->field_val_list[$field_params['extract']]['imgs'][$cur_url_md5], ); - $val=$this->field_module_extract($field_params, $extract_field_val, $base_url, $domain_url); + $val=$this->field_module_extract($field_params, $extract_field_val, $url_info); } }elseif('merge'==$module){ @@ -1103,15 +1094,17 @@ class Cpattern extends CpatternEvent{ $val=array(); - foreach ($this->field_val_list[$this->first_loop_field]['values'][$cur_url_md5] as $v_k=>$v_v){ - $cur_field_val_list=array(); - foreach ($this->field_val_list as $k=>$v){ - $cur_field_val_list[$k]=array( - 'value'=>(is_array($v['values'][$cur_url_md5])?$v['values'][$cur_url_md5][$v_k]:$v['values'][$cur_url_md5]), - 'img'=>(is_array($v['imgs'][$cur_url_md5][$v_k])?$v['imgs'][$cur_url_md5][$v_k]:$v['imgs'][$cur_url_md5]) - ); - } - $val[$v_k]=$this->field_module_merge($field_params,$cur_field_val_list); + if(is_array($this->field_val_list[$this->first_loop_field]['values'][$cur_url_md5])){ + foreach ($this->field_val_list[$this->first_loop_field]['values'][$cur_url_md5] as $v_k=>$v_v){ + $cur_field_val_list=array(); + foreach ($this->field_val_list as $k=>$v){ + $cur_field_val_list[$k]=array( + 'value'=>(is_array($v['values'][$cur_url_md5])?$v['values'][$cur_url_md5][$v_k]:$v['values'][$cur_url_md5]), + 'img'=>((is_array($v['imgs'][$cur_url_md5])&&is_array($v['imgs'][$cur_url_md5][$v_k]))?$v['imgs'][$cur_url_md5][$v_k]:$v['imgs'][$cur_url_md5]) + ); + } + $val[$v_k]=$this->field_module_merge($field_params,$cur_field_val_list); + } } } }elseif(in_array($module,$fieldArr)){ @@ -1123,8 +1116,10 @@ class Cpattern extends CpatternEvent{ $val=array(); - foreach ($this->field_val_list[$this->first_loop_field]['values'][$cur_url_md5] as $v_k=>$v_v){ - $val[$v_k]=$this->$field_func($field_params); + if(is_array($this->field_val_list[$this->first_loop_field]['values'][$cur_url_md5])){ + foreach ($this->field_val_list[$this->first_loop_field]['values'][$cur_url_md5] as $v_k=>$v_v){ + $val[$v_k]=$this->$field_func($field_params); + } } } }elseif($module=='json'){ @@ -1204,13 +1199,13 @@ class Cpattern extends CpatternEvent{ } - $val=preg_replace_callback('/(\bhref\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($base_url,$domain_url){ + $val=preg_replace_callback('/(\bhref\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($url_info){ - $matche[2]=\skycaiji\admin\event\Cpattern::create_complete_url($matche[2], $base_url, $domain_url); + $matche[2]=\util\Tools::create_complete_url($matche[2], $url_info); return $matche[1].$matche[2].$matche[3]; },$val); - $val=preg_replace_callback('/(\bsrc\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($base_url,$domain_url){ - $matche[2]=\skycaiji\admin\event\Cpattern::create_complete_url($matche[2], $base_url, $domain_url); + $val=preg_replace_callback('/(\bsrc\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($url_info){ + $matche[2]=\util\Tools::create_complete_url($matche[2], $url_info); return $matche[1].$matche[2].$matche[3]; },$val); @@ -1282,7 +1277,8 @@ class Cpattern extends CpatternEvent{ if(empty($pnConfig['max'])||(count((array)$this->used_pagination_urls['url'])<$pnConfig['max'])){ $this->collect_sleep(g_sc_c('caiji','interval_html'),true,true); - $this->echo_msg(array('——采集分页:%s',$page_url,$page_url),'black'); + $pageOpened=$this->page_opened_tips('url','',true); + $this->echo_msg($pageOpened?array('——采集分页:%s',$pageOpened.$page_url):array('——采集分页:%s',$page_url,$page_url),'black'); $htmlInfo=$this->get_page_html($page_url,'url','',true,true); if(empty($htmlInfo['html'])){ return $this->echo_error('未获取到分页源代码'); @@ -1555,6 +1551,7 @@ class Cpattern extends CpatternEvent{ /*初始化数据处理,初始化config时使用*/ public function initProcess($processList){ if(!empty($processList)){ + $processList=$this->set_process($processList); foreach ($processList as $k=>$v){ if('replace'==$v['module']){ $v['replace_from']=$this->correct_reg_pattern($v['replace_from']); @@ -1603,8 +1600,9 @@ class Cpattern extends CpatternEvent{ } return $this->echo_error('未获取到'.htmlspecialchars($sourceTips).($sourceIsPagination?'分页':'').'源代码'); } - $base_url=$this->match_base_url($sourceUrl, $html); - $domain_url=$this->match_domain_url($sourceUrl); + + $url_info=$this->match_url_info($sourceUrl,$html); + $areaMatch=$this->rule_match_area($pageType,$pageName,false,$html,true); $html=$areaMatch['area']; @@ -1627,7 +1625,7 @@ class Cpattern extends CpatternEvent{ } } - $contUrlsMatches=$this->rule_match_urls($pageType,$pageName,false,$html,$op_not_complete?false:array('base'=>$base_url,'domain'=>$domain_url),true); + $contUrlsMatches=$this->rule_match_urls($pageType,$pageName,false,$html,$op_not_complete?false:$url_info,true); $cont_urls=$contUrlsMatches['urls']; @@ -1797,8 +1795,8 @@ class Cpattern extends CpatternEvent{ } } $this->used_pagination_urls[$levelSource][$pageCurMd5]=1; - $isFormPost=$this->page_is_post('level_url',$levelConfig['name'])?'[POST] ':''; - $this->echo_msg($isFormPost?array('%s分析第%s级%s:%s',$next_level_str,$level,$pagePnStr,$isFormPost.$pageCurUrl):array('%s分析第%s级%s:%s',$next_level_str,$level,$pagePnStr,$pageCurUrl,$pageCurUrl),'black'); + $pageOpened=$this->page_opened_tips('level_url',$levelConfig['name']); + $this->echo_msg($pageOpened?array('%s分析第%s级%s:%s',$next_level_str,$level,$pagePnStr,$pageOpened.$pageCurUrl):array('%s分析第%s级%s:%s',$next_level_str,$level,$pagePnStr,$pageCurUrl,$pageCurUrl),'black'); if($level_data['nextLevel']>0){ $return_msg=$this->_collect_level($pageCurUrl,$level_data['nextLevel']); @@ -1862,7 +1860,7 @@ class Cpattern extends CpatternEvent{ $mcacheSource=CacheModel::getInstance('source_url'); $mcacheLevel=CacheModel::getInstance('level_url'); $mcacheCont=CacheModel::getInstance('cont_url'); - $isFormPost=$this->page_is_post('url')?'[POST] ':''; + $pageOpened=$this->page_opened_tips('url'); foreach ($this->cont_urls_list as $cont_key=>$cont_urls){ @@ -1919,7 +1917,7 @@ class Cpattern extends CpatternEvent{ continue; } $mcacheCont->setCache($md5_cont_url, 1); - $this->echo_msg($isFormPost?array('%s采集内容页:%s',$echo_str,$isFormPost.$cont_url):array('%s采集内容页:%s',$echo_str,$cont_url,$cont_url),'black'); + $this->echo_msg($pageOpened?array('%s采集内容页:%s',$echo_str,$pageOpened.$cont_url):array('%s采集内容页:%s',$echo_str,$cont_url,$cont_url),'black'); $field_vals_list=$this->getFields($cont_url); $is_loop=empty($this->first_loop_field)?false:true; diff --git a/vendor/skycaiji/app/admin/event/CpatternBase.php b/vendor/skycaiji/app/admin/event/CpatternBase.php index cca1247..f1661fe 100644 --- a/vendor/skycaiji/app/admin/event/CpatternBase.php +++ b/vendor/skycaiji/app/admin/event/CpatternBase.php @@ -18,112 +18,6 @@ class CpatternBase extends CollectBase{ public function init($config){} public function collect($num=10){} - - /** - * 匹配根目录 - * @param string $url 完整的网址 - * @param string $html 页面源码 - * @return Ambigous - */ - public function match_base_url($url,$html){ - - if(!empty($html)&&preg_match('/]*\bhref\s*=\s*[\'\"](?P[^\'\"]*)[\'\"]/i',$html,$base_url)){ - $base_url=$base_url['base']; - if(!preg_match('/^\w+\:\/\//', $base_url)){ - - $urlBase=$this->match_base_url($url, null); - $urlDomain=$this->match_domain_url($url); - $base_url=$this->create_complete_url($base_url, $urlBase, $urlDomain); - } - }else{ - - $base_url=preg_replace('/[\#\?].*$/', '', $url); - } - if(!preg_match('/\/$/', $base_url)){ - - - if(preg_match('/(^\w+\:\/\/[^\/]+)(.*$)/',$base_url,$mbase)){ - - $mbase[2]=preg_replace('/[^\/]+$/', '', $mbase[2]); - $base_url=$mbase[1].$mbase[2]; - } - } - $base_url=rtrim($base_url,'/'); - - return $base_url?$base_url:null; - } - /** - * 匹配域名 - * @param string $url 完整的网址 - * @return NULL|string - */ - public function match_domain_url($url){ - - $domain_url=null; - if(preg_match('/^\w+\:\/\/([\w\-]+\.){1,}[\w]+/',$url,$domain_url)){ - $domain_url=rtrim($domain_url[0],'/'); - }else{ - $domain_url=null; - } - return empty($domain_url)?null:$domain_url; - } - /** - * 生成完整网址 - * @param string $url 要填充的网址 - * @param string $base_url 根目录网址 - * @param string $domain_url 域名 - */ - public function create_complete_url($url,$base_url,$domain_url){ - static $base_domain=array(); - - if(preg_match('/^\w+\:/', $url)){ - - return $url; - }elseif(strpos($url,'//')===0){ - - $url=(stripos($base_url, 'https://')===0?'https:':'http:').$url; - }elseif(strpos($url,'/')===0){ - - $curDomain=''; - if($base_url){ - $baseMd5=md5($base_url); - if(!isset($base_domain[$baseMd5])){ - $base_domain[$baseMd5]=$this->match_domain_url($base_url); - } - $curDomain=$base_domain[$baseMd5]; - } - $curDomain=empty($curDomain)?rtrim($domain_url,'/'):$curDomain; - $url=$curDomain.'/'.ltrim($url,'/'); - }elseif(stripos($url,'javascript')===0||stripos($url,'#')===0||$url==''){ - - $url=''; - }elseif(!preg_match('/^\w+\:\/\//', $url)){ - - $url=$base_url.'/'.ltrim($url,'/'); - } - - if(!empty($url)&&preg_match('/\/(\.){1,2}\//', $url)){ - - if(preg_match('/(^\w+\:\/\/(?:[\w\-]+\.){1,}[\w]+\/)([^\?\#]+)(.*$)/',$url,$murl)){ - - $paths=explode('/', $murl[2]); - $newPaths=array(); - foreach ($paths as $k=>$v){ - if($v=='..'){ - - array_pop($newPaths); - }elseif($v!='.'){ - - $newPaths[]=$v; - } - } - $url=$murl[1].implode('/', $newPaths).$murl[3]; - } - } - - return $url; - } - /*正则规则匹配数据*/ public function rule_module_rule_data($configParams,$html,$parentMatches=array(),$whole=false,$returnMatch=false){ $val=null; @@ -651,6 +545,7 @@ class CpatternBase extends CollectBase{ if(is_array($processList)){ $processList=\util\Funcs::array_array_map('trim',$processList); foreach ($processList as $k=>$v){ + init_array($v); $v['module']=strtolower($v['module']); if(!empty($v['title'])){ $v['title']=str_replace(array("'",'"'),'',strip_tags($v['title'])); @@ -668,20 +563,22 @@ class CpatternBase extends CollectBase{ $v['filter_list']=trim($v['filter_list']); }elseif('api'==$v['module']){ - if(!is_array($v['api_params'])){ - $v['api_params']=array(); - } + init_array($v['api_params']); \util\Funcs::filter_key_val_list3($v['api_params']['name'],$v['api_params']['val'],$v['api_params']['addon']); - if(!is_array($v['api_headers'])){ - $v['api_headers']=array(); - } + init_array($v['api_headers']); \util\Funcs::filter_key_val_list3($v['api_headers']['name'],$v['api_headers']['val'],$v['api_headers']['addon']); + }elseif('tool'==$v['module']){ + init_array($v['tool_list']); + }elseif('if'==$v['module']){ + init_array($v['if_addon']); + \util\Funcs::filter_key_val_list5($v['if_cond'],$v['if_logic'],$v['if_val'],$v['if_addon']['func'],$v['if_addon']['turn']); } $processList[$k]=$v; } $processList=array_values($processList); } + init_array($processList); return $processList; } @@ -741,6 +638,22 @@ class CpatternBase extends CollectBase{ $pageConfig['pagination']=$pnConfig; } + + $renderer=$pageConfig['renderer']; + init_array($renderer); + \util\Funcs::filter_key_val_list3($renderer['types'], $renderer['elements'], $renderer['contents']); + foreach ($renderer['types'] as $k=>$v){ + if(!$this->renderer_type_has_option($v, 'element')){ + + $renderer['elements'][$k]=''; + } + if(!$this->renderer_type_has_option($v, 'content')){ + + $renderer['contents'][$k]=''; + } + } + $pageConfig['renderer']=$renderer; + return $pageConfig; } @@ -984,6 +897,18 @@ class CpatternBase extends CollectBase{ } return array($type,$name); } + + public function renderer_type_has_option($type,$checkOption){ + $types=array( + 'wait_time'=>array('content'=>true), + 'scroll_top'=>array('content'=>true), + 'click'=>array('element'=>true), + 'val'=>array('element'=>true,'content'=>true), + ); + $options=$types[$type]; + init_array($options); + return $options[$checkOption]?true:false; + } /*多个数组合并成键值对*/ public function arrays_to_key_val($arr1,$arr2){ if(!is_array($arr1)){ @@ -1000,7 +925,7 @@ class CpatternBase extends CollectBase{ if(!isset($data)){ $data=array(); foreach ($arr1 as $k=>$v){ - if(!empty($v)){ + if(!\util\Funcs::is_null($v)){ $data[$v]=$arr2[$k]; } @@ -1042,35 +967,15 @@ class CpatternBase extends CollectBase{ $tips=($result['error']?(':'.$result['error']):''); - if($retryMax<=0||($retryCur<=0&&$this->is_collecting())){ - - $this->echo_error('数据处理»翻译失败'.$tips); - } + $this->retry_first_echo($retryCur,'数据处理»翻译失败'.$tips); $this->collect_sleep($transConf['wait']); - if($retryMax>0){ + if($this->retry_do_func($retryCur,$retryMax,'翻译无效','翻译无效'.$tips)){ - if($retryCur<$retryMax){ - - $retryCur++; - if($this->is_collecting()){ - - $this->echo_msg(array('%s第%s次',$retryCur>1?' / ':'重试:',$retryCur),'black',true,'','display:inline;'.($retryCur==$retryMax?'margin-right:5px;':'')); - } - - return $this->execute_translate($retryParams[0],$retryParams[1],$retryParams[2]); - }else{ - $retryCur=0; - - if($this->is_collecting()){ - $this->echo_msg('翻译无效','red',true,'','display:inline;margin-right:5px;'); - }else{ - - $this->echo_error('数据处理»翻译:已重试'.$retryMax.'次,翻译无效'.$tips); - } - } + return $this->execute_translate($retryParams[0],$retryParams[1],$retryParams[2]); } + $result=''; } } @@ -1096,12 +1001,18 @@ class CpatternBase extends CollectBase{ return $return['data']; } - public function page_url_web_opened($urlWebConfig){ - if(is_array($urlWebConfig)&&!empty($urlWebConfig['open'])){ - return true; + public function page_url_web_opened($urlWebConfig,$paginationConfig=null){ + $opened=false; + if($paginationConfig&&is_array($paginationConfig)&&$paginationConfig['use_url_web']){ + + $opened=$paginationConfig['use_url_web']=='y'?true:false; }else{ - return false; + + if($urlWebConfig&&is_array($urlWebConfig)&&!empty($urlWebConfig['open'])){ + $opened=true; + } } + return $opened; } } ?> \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/event/CpatternColl.php b/vendor/skycaiji/app/admin/event/CpatternColl.php index dbdca23..e7be24a 100644 --- a/vendor/skycaiji/app/admin/event/CpatternColl.php +++ b/vendor/skycaiji/app/admin/event/CpatternColl.php @@ -62,6 +62,33 @@ class CpatternColl extends CpatternBase{ } + public function match_url_info($url,$html,$cacheKey=false){ + static $cacheList=array(); + $cacheMd5=null; + $info=array(); + if($cacheKey){ + + init_array($cacheList[$cacheKey]); + $cacheMd5=md5($url); + $info=$cacheList[$cacheKey][$cacheMd5]; + } + if(empty($info)){ + + $info=array('cur_url'=>$url,'url_no_name'=>$this->config['url_no_name']); + $baseInfo=\util\Tools::match_base_url($url,$html,true); + $info=array_merge($info,$baseInfo); + $info['domain_url']=\util\Tools::match_domain_url($url); + if($cacheKey){ + + $cacheList[$cacheKey][$cacheMd5]=$info; + } + } + init_array($info); + return $info; + } + + + /*规则匹配区域*/ public function rule_match_area($pageType,$pageName,$isPagination,$html,$returnMatch=false){ $matches=array(); @@ -139,7 +166,7 @@ class CpatternColl extends CpatternBase{ /*规则匹配网址*/ - public function rule_match_urls($pageType,$pageName,$isPagination,$html,$urlComplete=false,$returnMatch=false){ + public function rule_match_urls($pageType,$pageName,$isPagination,$html,$completeUrlInfo=false,$returnMatch=false){ $cont_urls=array(); $cont_urls_matches=array(); $config=$this->get_page_config($pageType,$pageName,$isPagination?'pagination':null); @@ -208,7 +235,7 @@ class CpatternColl extends CpatternBase{ $doComplete=false; $doMust=false; $doBan=false; - if(!empty($urlComplete)&&is_array($urlComplete)){ + if(!empty($completeUrlInfo)&&is_array($completeUrlInfo)){ $doComplete=true; } @@ -245,7 +272,7 @@ class CpatternColl extends CpatternBase{ if($doComplete){ - $contUrl=$this->create_complete_url($contUrl, $urlComplete['base'], $urlComplete['domain']); + $contUrl=\util\Tools::create_complete_url($contUrl, $completeUrlInfo); $cont_urls[$k]=$contUrl; } if($doMust){ @@ -279,7 +306,7 @@ class CpatternColl extends CpatternBase{ } } - return $this->page_convert_url_signs($pageType, $pageName, $cont_urls, $cont_urls_matches, $returnMatch); + return $this->page_convert_url_signs($pageType, $pageName, $isPagination, $cont_urls, $cont_urls_matches, $returnMatch); } /*正则规则匹配数据*/ @@ -293,9 +320,13 @@ class CpatternColl extends CpatternBase{ } /*页面转换网址标签参数*/ - public function page_convert_url_signs($pageType,$pageName,$cont_urls,$cont_urls_matches,$returnMatch=false){ + public function page_convert_url_signs($pageType,$pageName,$isPagination,$cont_urls,$cont_urls_matches,$returnMatch=false){ + $urlPostKeys=array(); + $urlRenderKeys=array(); + + $pnConfig=$isPagination?$this->get_page_config($pageType,$pageName,'pagination'):null; $urlWebConfig=$this->get_page_config($pageType,$pageName,'url_web'); - if($this->page_url_web_opened($urlWebConfig)){ + if($this->page_url_web_opened($urlWebConfig,$pnConfig)){ $formData=$this->arrays_to_key_val($urlWebConfig['form_names'], $urlWebConfig['form_vals']); @@ -325,29 +356,29 @@ class CpatternColl extends CpatternBase{ foreach ($cont_urls as $k=>$v){ - $cont_urls[$k]=$v.'#post_'.md5(serialize($urlsForms[$k])); + $urlPostKeys[$k]=md5(serialize($urlsForms[$k])); } }else{ - $charset=$urlWebConfig['charset']=='custom'?$urlWebConfig['charset_custom']:$urlWebConfig['charset']; - if(empty($charset)){ - - $charset=$this->config['charset']; - } - $charset=strtolower($charset); + $charset=$this->page_url_web_charset($urlWebConfig); if(!empty($charset)&&!in_array($charset,array('auto','utf-8','utf8'))){ - foreach ($cont_urls as $k=>$v){ - foreach ($urlsForms[$k] as $fk=>$fv){ - $urlsForms[$k][$fk]=iconv('utf-8',$charset.'//IGNORE',$fv); + foreach ($urlsForms as $k=>$v){ + $urlsForms[$k]=\util\Funcs::convert_charset($v, 'utf-8', $charset); + } + } + + foreach ($cont_urls as $k=>$v){ + $vName=''; + if(strpos($v,'#')!==false){ + + if(preg_match('/(^.*?)\#(.*$)/',$v,$mv)){ + $v=$mv[1]; + $vName='#'.$mv[2]; } - $cont_urls[$k]=$v.(strpos($v,'?')===false?'?':'&').http_build_query($urlsForms[$k]); - } - }else{ - - foreach ($cont_urls as $k=>$v){ - $cont_urls[$k]=$v.(strpos($v,'?')===false?'?':'&').http_build_query($urlsForms[$k]); } + $cont_urls[$k]=$v.(strpos($v,'?')===false?'?':'&').http_build_query($urlsForms[$k]).$vName; + unset($urlsForms[$k]); } } } @@ -355,6 +386,54 @@ class CpatternColl extends CpatternBase{ } } + $renderConfig=$this->get_page_config($pageType,$pageName,'renderer'); + if($this->renderer_is_open(null,null,$renderConfig,$pnConfig)){ + + if(!empty($renderConfig['types'])){ + + $renderParentMatches=$this->merge_str_signs(implode(' ',$renderConfig['contents'])); + if(!empty($renderParentMatches)){ + + $renderParentMatches=$this->parent_page_signs2matches($this->parent_page_signs($pageType,$pageName,'renderer')); + } + init_array($renderParentMatches); + foreach ($cont_urls as $k=>$v){ + + $renderContParentMatches=array_merge($renderParentMatches,is_array($cont_urls_matches[$k])?$cont_urls_matches[$k]:array()); + $renderContent=array(); + foreach ($renderConfig['contents'] as $rck=>$rcv){ + + $renderContent[$rck]=$rcv?$this->merge_match_signs($renderContParentMatches,$rcv):$rcv; + } + + $urlRenderKeys[$k]=md5(serialize(array('types'=>$renderConfig['types'],'elements'=>$renderConfig['elements'],'contents'=>$renderContent))); + } + } + } + if(!empty($urlPostKeys)||!empty($urlRenderKeys)){ + foreach ($cont_urls as $k=>$v){ + $urlPostKeys[$k]=$urlPostKeys[$k]?:''; + $urlRenderKeys[$k]=$urlRenderKeys[$k]?:''; + $vUrl=''; + $vUrlKey=''; + if($urlPostKeys[$k]){ + $vUrl.='post_'; + $vUrlKey=$urlPostKeys[$k]; + } + if($urlRenderKeys[$k]){ + $vUrl.='render_'; + if($vUrlKey){ + $vUrlKey=md5($vUrlKey.$urlRenderKeys[$k]); + }else{ + $vUrlKey=$urlRenderKeys[$k]; + } + } + if($vUrl){ + + $cont_urls[$k]=$v.'#'.$vUrl.$vUrlKey; + } + } + } if($returnMatch){ $return=array('urls'=>array(),'matches'=>array()); @@ -636,9 +715,25 @@ class CpatternColl extends CpatternBase{ $foundContentIsArr=is_array($foundPageSigns['cur']['content'])?true:false; $ruleWhole=$this->page_rule_is_null($pageType)?false:true; - if(empty($mergeType)||$mergeType=='content_sign'||in_array($mergeType,$inUrlRule)){ - $openUrlWeb=$this->page_url_web_opened($pageConfig['url_web']); + if(empty($mergeType)||$mergeType=='content_sign'||$mergeType=='renderer'||in_array($mergeType,$inUrlRule)){ + $pageRendererMerge=''; + if(empty($mergeType)||$mergeType=='renderer'){ + + if($this->renderer_is_open(null,null,$pageConfig['renderer'])){ + if(is_array($pageConfig['renderer']['types'])&&is_array($pageConfig['renderer']['contents'])){ + $pageRendererMerge=array(); + foreach ($pageConfig['renderer']['types'] as $k=>$v){ + if($this->renderer_type_has_option($v, 'content')){ + $pageRendererMerge[]=$pageConfig['renderer']['contents'][$k]; + } + } + $pageRendererMerge=implode(' ', $pageRendererMerge); + } + } + } + + $openUrlWeb=$this->page_url_web_opened($pageConfig['url_web']); $pageHeaderMerge=''; if(empty($mergeType)||$mergeType=='url_web'||$mergeType=='header'){ if($openUrlWeb){ @@ -671,7 +766,7 @@ class CpatternColl extends CpatternBase{ } } - $pageSigns=$this->signs_not_in_rule($pageConfig['reg_url'],$pageUrlMerge.$pageHeaderMerge.$pageFormMerge.implode('',$unknownPageSigns),$ruleWhole,false,true); + $pageSigns=$this->signs_not_in_rule($pageConfig['reg_url'],$pageUrlMerge.$pageHeaderMerge.$pageFormMerge.$pageRendererMerge.implode('',$unknownPageSigns),$ruleWhole,false,true); if(is_array($pageSigns['unknown'])){ $unknownPageSigns=$pageSigns['unknown']; } @@ -911,10 +1006,76 @@ class CpatternColl extends CpatternBase{ return $pageSources; } + + public function page_opened_tips($pageType,$pageName='',$isPagination=false,$returnHtml=false){ + $tips=''; + if($this->page_is_post($pageType,$pageName,$isPagination)){ + $tips.=$returnHtml?'post ':'[post] '; + } + if($this->renderer_is_open($pageType,$pageName,null,$isPagination)){ + $tips.=$returnHtml?'渲染 ':'[渲染] '; + } + return $tips; + } + + + public function page_render_is_open(){ + static $pages=array('front_url','level_url','relation_url'); + $opened=false; + foreach ($pages as $page){ + if(!$opened){ + + $pageData=$this->get_config('new_'.$page.'s'); + if(is_array($pageData)){ + foreach ($pageData as $k=>$v){ + $opened=$this->renderer_is_open($page,$k); + if($opened){ + + break; + } + } + } + } + } + + if(!$opened){ + $opened=$this->renderer_is_open('source_url'); + } + + if(!$opened){ + $opened=$this->renderer_is_open('url'); + } + return $opened; + } + + + public function renderer_is_open($pageType,$pageName='',$rendererConfig=null,$paginationConfig=null){ + $opened=$this->get_config('page_render'); + if($pageType){ + + $rendererConfig=$this->get_page_config($pageType,$pageName,'renderer'); + if($paginationConfig){ + + $paginationConfig=$this->get_page_config($pageType,$pageName,'pagination'); + } + } + + if($paginationConfig&&is_array($paginationConfig)&&$paginationConfig['use_renderer']){ + + $opened=$paginationConfig['use_renderer']=='y'?true:false; + }else{ + if($rendererConfig&&is_array($rendererConfig)&&!empty($rendererConfig['open'])){ + $opened=$rendererConfig['open']=='y'?true:false; + } + } + return $opened; + } + /*页面是否是post模式*/ - public function page_is_post($pageType,$pageName=''){ + public function page_is_post($pageType,$pageName='',$isPagination=false){ $urlWebConfig=$this->get_page_config($pageType,$pageName,'url_web'); - if($this->page_url_web_opened($urlWebConfig)&&$urlWebConfig['form_method']=='post'){ + $pnConfig=$isPagination?$this->get_page_config($pageType,$pageName,'pagination'):null; + if($this->page_url_web_opened($urlWebConfig,$pnConfig)&&$urlWebConfig['form_method']=='post'){ return true; }else{ return false; @@ -932,6 +1093,33 @@ class CpatternColl extends CpatternBase{ } + public function page_url_web_charset($urlWebConfig){ + $charset=''; + if($this->page_url_web_opened($urlWebConfig)){ + $charset=$urlWebConfig['charset']=='custom'?$urlWebConfig['charset_custom']:$urlWebConfig['charset']; + } + if(empty($charset)){ + + $charset=$this->config['charset']; + } + $charset=strtolower($charset); + return $charset; + } + + public function page_url_web_encode($urlWebConfig){ + $encode=''; + if($this->page_url_web_opened($urlWebConfig)){ + $encode=$urlWebConfig['encode']=='custom'?$urlWebConfig['encode_custom']:$urlWebConfig['encode']; + } + if(empty($encode)){ + + $encode=$this->config['encode']; + } + $encode=strtolower($encode); + return $encode; + } + + public function get_config($key1,$key2=null,$key3=null){ $keys=array($key1); if(isset($key2)){ @@ -987,14 +1175,12 @@ class CpatternColl extends CpatternBase{ public function get_page_html($url,$pageType,$pageName,$isPagination=false,$returnInfo=false){ $pageName=$pageName?$pageName:''; $headers=array(); - $urlForm=array(); - $pageSource=$this->page_source_merge($pageType, $pageName); - $charset=null; - $urlWebConfig=$this->get_page_config($pageType,$pageName,'url_web'); - $openUrlWeb=$this->page_url_web_opened($urlWebConfig); + $pnConfig=$isPagination?$this->get_page_config($pageType,$pageName,'pagination'):null; + + $openUrlWeb=$this->page_url_web_opened($urlWebConfig,$pnConfig); if(!empty($pageSource)){ @@ -1056,10 +1242,19 @@ class CpatternColl extends CpatternBase{ } } + $otherConfig=array('curlopts'=>array()); + + $charset=$this->page_url_web_charset($urlWebConfig); + $encode=$this->page_url_web_encode($urlWebConfig); + if($encode){ + $otherConfig['curlopts'][CURLOPT_ENCODING]=$encode; + } + + $filterUrl=false; + $postData=null; if($openUrlWeb){ - $charset=$urlWebConfig['charset']=='custom'?$urlWebConfig['charset_custom']:$urlWebConfig['charset']; $formData=$this->arrays_to_key_val($urlWebConfig['form_names'], $urlWebConfig['form_vals']); if(!empty($formData)&&is_array($formData)){ @@ -1078,11 +1273,11 @@ class CpatternColl extends CpatternBase{ if($urlWebConfig['form_method']=='post'){ + $filterUrl=true; $postData=$formData; if($urlWebConfig['content_type']){ $headers['content-type']=$urlWebConfig['content_type']; } - $url=preg_replace('/\#post_\w{32}$/i', '', $url); }else{ $postData=null; @@ -1090,17 +1285,32 @@ class CpatternColl extends CpatternBase{ unset($formData); } - if(empty($charset)){ - - $charset=$this->config['charset']; + + $rendererConfig=$this->get_page_config($pageType,$pageName,'renderer'); + if($this->renderer_is_open(null,null,$rendererConfig,$pnConfig)){ + $filterUrl=true; + $signs=$this->merge_str_signs(implode(' ',$rendererConfig['contents'])); + if(!empty($signs)){ + + $signs=$this->parent_page_signs($pageType, $pageName, 'renderer'); + $signs=$this->parent_page_signs2matches($signs); + + foreach ($rendererConfig['contents'] as $k=>$v){ + $rendererConfig['contents'][$k]=$this->merge_match_signs($signs, $v); + } + } + $otherConfig['renderer']=$rendererConfig; + } + + if($filterUrl){ + $url=preg_replace('/\#(post_|render_|post_render_){1,}\w{32}$/i', '', $url); } $htmlInfo=array(); $html=null; - if($isPagination){ - $htmlInfo=$this->get_html($url,$postData,$headers,$charset,true); + $htmlInfo=$this->get_html($url,$postData,$headers,$charset,$otherConfig,true); $html=$htmlInfo['html']; }else{ @@ -1144,7 +1354,7 @@ class CpatternColl extends CpatternBase{ $htmlInfo=$this->cache_page_htmls[$pageType][$pageName][$cacheKey]; }else{ - $htmlInfo=$this->get_html($url,$postData,$headers,$charset,true); + $htmlInfo=$this->get_html($url,$postData,$headers,$charset,$otherConfig,true); $this->cache_page_htmls[$pageType][$pageName][$cacheKey]=$htmlInfo; } @@ -1213,36 +1423,39 @@ class CpatternColl extends CpatternBase{ * @param string $url 网址 * @param bool|array $postData post数据 * @param array $headers 请求头信息 - * @param string $charset 页面编码 + * @param string $charset 网页编码 + * @param array $otherConfig 其他配置 * @param string $returnInfo 返回数据信息 * @return string|array */ - public function get_html($url,$postData=false,$headers=array(),$charset=null,$returnInfo=false){ + public function get_html($url,$postData=false,$headers=array(),$charset=null,$otherConfig=array(),$returnInfo=false){ static $retryCur=0; $retryMax=intval(g_sc_c('caiji','retry')); $retryParams=null; if($retryMax>0){ - $retryParams=array(0=>$url,1=>$postData,2=>$headers,3=>$charset,4=>$returnInfo); + $retryParams=array(0=>$url,1=>$postData,2=>$headers,3=>$charset,4=>$otherConfig,5=>$returnInfo); } - $isPost=false; + + $pageOpened=''; if(isset($postData)&&$postData!==false){ - $isPost=true; + + $pageOpened.='[post] '; } if(empty($charset)){ $charset=$this->config['charset']; } - $pageRenderTool=null; - if($this->config['page_render']){ + if($this->renderer_is_open(null,null,$otherConfig['renderer'])){ $pageRenderTool=g_sc_c('page_render','tool'); if(empty($pageRenderTool)){ $this->echo_error('页面渲染未设置,请检查渲染设置','setting/page_render'); return null; } + $pageOpened.='[渲染] '; } $htmlInfo=array(); $html=null; @@ -1260,15 +1473,17 @@ class CpatternColl extends CpatternBase{ $headers['cookie']=$hdCookie; } } - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); $proxyDbIp=null; if(!is_empty(g_sc_c('proxy','open'))){ $proxyDbIp=$mproxy->get_usable_ip(); $proxyIp=$mproxy->to_proxy_ip($proxyDbIp); - - if(!empty($proxyIp)){ + if(empty($proxyIp)){ + $this->echo_error('没有可用的代理IP'); + return null; + }else{ $options['proxy']=$proxyIp; } } @@ -1287,12 +1502,13 @@ class CpatternColl extends CpatternBase{ $chromeConfig=g_sc_c('page_render','chrome'); init_array($chromeConfig); try { + $options['renderer']=$otherConfig['renderer']; $chromeSocket=new \util\ChromeSocket($chromeConfig['host'],$chromeConfig['port'],g_sc_c('page_render','timeout'),$chromeConfig['filename'],$chromeConfig); $chromeSocket->newTab($options['proxy']); $chromeSocket->websocket(null); $htmlInfo=$chromeSocket->getRenderHtml($url,$headers,$options,$charset,$postData,true); }catch (\Exception $ex){ - $this->echo_error('页面渲染失败,请检查渲染设置','setting/page_render'); + $this->echo_error('页面渲染失败:'.$ex->getMessage().' 请检查渲染设置'); return null; } }else{ @@ -1300,24 +1516,28 @@ class CpatternColl extends CpatternBase{ return null; } }else{ + $options['curlopts']=$otherConfig['curlopts']; + + init_array($options['curlopts']); + $confMaxRedirs=g_sc_c('caiji','max_redirs'); + $confMaxRedirs=intval($confMaxRedirs); + if($confMaxRedirs>0){ + + $options['curlopts'][CURLOPT_MAXREDIRS]=$confMaxRedirs; + } $htmlInfo=get_html($url,$headers,$options,$charset,$postData,true); } init_array($htmlInfo); $html=$htmlInfo['html']; if(empty($html)||!$htmlInfo['ok']){ - if($this->is_collecting()){ - if(!empty($proxyDbIp)){ - $this->echo_msg(array('代理IP:%s',$proxyDbIp['ip']),'black',true,'','display:inline;margin-right:5px;'); - } - if($retryCur<=0){ - - $urlEcho=htmlspecialchars($url); - $echoMsg='
    访问网址失败:'.($isPost?('[POST] '.$urlEcho):(''.$urlEcho.'')).'
    '; - $this->echo_error($echoMsg); - } + if(!empty($proxyDbIp)){ + $this->echo_msg(array('代理IP:%s',$proxyDbIp['ip']),'black',true,'','display:inline;margin-right:5px;'); } + $this->retry_first_echo($retryCur,'访问网址失败',$url,$htmlInfo); + + if(!empty($proxyDbIp)){ if($htmlInfo['code']!=404){ @@ -1327,37 +1547,25 @@ class CpatternColl extends CpatternBase{ $this->collect_sleep(g_sc_c('caiji','wait')); - if($retryMax>0&&is_array($retryParams)){ - - if($retryCur<$retryMax){ - - $retryCur++; - if($this->is_collecting()){ - $this->echo_msg(array('%s第%s次',$retryCur>1?' / ':'重试:',$retryCur),'black',true,'','display:inline;'.($retryCur==$retryMax?'margin-right:5px;':'')); - } - return $this->get_html($retryParams[0],$retryParams[1],$retryParams[2],$retryParams[3],$retryParams[4]); - }else{ - $retryCur=0; - if($this->is_collecting()){ - $this->echo_msg('网址无效','red',true,'','display:inline;margin-right:5px;'); - } - } + if($this->retry_do_func($retryCur,$retryMax,'网址无效')){ + return $this->get_html($retryParams[0],$retryParams[1],$retryParams[2],$retryParams[3],$retryParams[4],$retryParams[5]); } + return $returnInfo?$htmlInfo:null; } $retryCur=0; if($this->config['url_complete']){ - $base_url=$this->match_base_url($url, $html); - $domain_url=$this->match_domain_url($url, $html); - $html=preg_replace_callback('/(\bhref\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($base_url,$domain_url){ + $url_info=$this->match_url_info($url,$html); + + $html=preg_replace_callback('/(\bhref\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($url_info){ - $matche[2]=\skycaiji\admin\event\Cpattern::create_complete_url($matche[2], $base_url, $domain_url); + $matche[2]=\util\Tools::create_complete_url($matche[2], $url_info); return $matche[1].$matche[2].$matche[3]; },$html); - $html=preg_replace_callback('/(\bsrc\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($base_url,$domain_url){ - $matche[2]=\skycaiji\admin\event\Cpattern::create_complete_url($matche[2], $base_url, $domain_url); + $html=preg_replace_callback('/(\bsrc\s*=\s*[\'\"])([^\'\"]*)([\'\"])/i',function($matche) use ($url_info){ + $matche[2]=\util\Tools::create_complete_url($matche[2], $url_info); return $matche[1].$matche[2].$matche[3]; },$html); } diff --git a/vendor/skycaiji/app/admin/event/CpatternEvent.php b/vendor/skycaiji/app/admin/event/CpatternEvent.php index 6b4b45f..aae0a2b 100644 --- a/vendor/skycaiji/app/admin/event/CpatternEvent.php +++ b/vendor/skycaiji/app/admin/event/CpatternEvent.php @@ -90,7 +90,7 @@ class CpatternEvent extends CpatternColl{ return $val; } /*字段提取内容*/ - public function field_module_extract($field_params,$extract_field_val,$base_url,$domain_url){ + public function field_module_extract($field_params,$extract_field_val,$url_info){ $field_html=$extract_field_val['value']; if(empty($field_html)){ return ''; @@ -105,7 +105,7 @@ class CpatternEvent extends CpatternColl{ }else{ if(preg_match('/]*\bsrc\s*=\s*[\'\"](?P[^\'\"]+?)[\'\"]/i',$field_html,$cover)){ $cover=$cover['url']; - $cover=$this->create_complete_url($cover, $base_url, $domain_url); + $cover=\util\Tools::create_complete_url($cover, $url_info); $val=$cover; } } @@ -256,21 +256,54 @@ class CpatternEvent extends CpatternColl{ $key=md5($field_params['list']); if(!isset($list[$key])){ - if(preg_match_all('/[^\r\n]+/', $field_params['list'],$str_list)){ - $str_list=$str_list[0]; - }else{ - $str_list=array(); + if(preg_match_all('/[^\r\n]+/', $field_params['list'],$strList)){ + $strList=$strList[0]; } - $list[$key]=$str_list; + init_array($strList); + $list[$key]=$strList; } - $str_list=$list[$key]; + $strList=$list[$key]; $val=''; - if(!empty($str_list)){ - $randi=array_rand($str_list,1); - $val=$str_list[$randi]; + if(!empty($strList)){ + if(empty($field_params['list_type'])){ + + $randi=array_rand($strList,1); + $val=$strList[$randi]; + }else{ + static $keyIndexs=array(); + $isAsc=$field_params['list_type']=='asc'?true:false; + $endIndex=count($strList)-1; + + if(isset($keyIndexs[$key])){ + + $curIndex=intval($keyIndexs[$key]); + }else{ + + $curIndex=$isAsc?0:$endIndex; + } + if($isAsc){ + + if($curIndex>$endIndex){ + + $curIndex=0; + } + $val=$strList[$curIndex]; + $curIndex++; + }else{ + + if($curIndex<0){ + + $curIndex=$endIndex; + } + $val=$strList[$curIndex]; + $curIndex--; + } + $keyIndexs[$key]=$curIndex; + } } return $val; } + public function field_module_merge($field_params,$val_list){ $val=''; @@ -372,9 +405,7 @@ class CpatternEvent extends CpatternColl{ static $regEmpty='/^([\s\r\n]|\ \;)*$/'; if(!is_empty(g_sc_c('translate'))&&!is_empty(g_sc_c('translate','open'))&&!empty($fieldVal)){ - if($this->is_collecting()){ - $this->echo_msg(array('正在翻译:%s',$fieldName),'black',true,'','display:inline;margin-right:5px;'); - } + $this->echo_msg(array('正在翻译:%s',$fieldName),'black',true,'','display:inline;margin-right:5px;'); $langFrom=$params['translate_from']=='custom'?$params['translate_from_custom']:$params['translate_from']; $langTo=$params['translate_to']=='custom'?$params['translate_to_custom']:$params['translate_to']; @@ -717,7 +748,7 @@ class CpatternEvent extends CpatternColl{ } $url=$params['api_url']; - $result=null; + $htmlInfo=null; if(!empty($url)){ $isLoc=false; if(!preg_match('/^\w+\:\/\//', $url)&&strpos($url, '/')===0){ @@ -736,6 +767,15 @@ class CpatternEvent extends CpatternColl{ if(empty($charset)){ $charset='utf-8'; } + $curlopts=array(); + + $encode=$params['api_encode']; + if($encode=='custom'){ + $encode=$params['api_encode_custom']; + } + if($encode){ + $curlopts[CURLOPT_ENCODING]=$encode; + } $postData=array(); @@ -794,43 +834,19 @@ class CpatternEvent extends CpatternColl{ $postData=null; } - $result=get_html($url,$headers,array(),$charset,$postData,true); + $htmlInfo=get_html($url,$headers,array('curlopts'=>$curlopts),$charset,$postData,true); $this->collect_sleep($params['api_interval'],true); - if(!empty($result['ok'])){ + if(!empty($htmlInfo['ok'])){ $retryCur=0; - $fieldVal=$this->rule_module_json_data(array('json'=>$params['api_json'],'json_arr'=>$params['api_json_arr'],'json_arr_implode'=>$params['api_json_implode']),$result['html']); + $fieldVal=$this->rule_module_json_data(array('json'=>$params['api_json'],'json_arr'=>$params['api_json_arr'],'json_arr_implode'=>$params['api_json_implode']),$htmlInfo['html']); }else{ - if($retryMax<=0||($retryCur<=0&&$this->is_collecting())){ - - $urlEcho=htmlspecialchars($url); - $echoMsg='
    数据处理»调用接口失败:'.$urlEcho.'
    '; - if(!$this->is_collecting()){ - $echoMsg=strip_tags($echoMsg); - } - $this->echo_error($echoMsg); - } + $this->retry_first_echo($retryCur,'数据处理»调用接口失败',$url,$htmlInfo); $this->collect_sleep($params['api_wait']); - if($retryMax>0&&is_array($retryParams)){ - - if($retryCur<$retryMax){ - - $retryCur++; - if($this->is_collecting()){ - $this->echo_msg(array('%s第%s次',$retryCur>1?' / ':'重试:',$retryCur),'black',true,'','display:inline;'.($retryCur==$retryMax?'margin-right:5px;':'')); - } - return $this->process_f_api($retryParams[0],$retryParams[1],$retryParams[2],$retryParams[3],$retryParams[4],$retryParams[5]); - }else{ - $retryCur=0; - if($this->is_collecting()){ - $this->echo_msg('接口无效','red',true,'','display:inline;margin-right:5px;'); - }else{ - - $this->echo_error('数据处理»调用接口:'.htmlspecialchars($url).',已重试'.$retryMax.'次,接口无效 '); - } - } + if($this->retry_do_func($retryCur,$retryMax,'接口无效','接口无效')){ + return $this->process_f_api($retryParams[0],$retryParams[1],$retryParams[2],$retryParams[3],$retryParams[4],$retryParams[5]); } } } diff --git a/vendor/skycaiji/app/admin/event/Rcms.php b/vendor/skycaiji/app/admin/event/Rcms.php index 34a561c..a373c58 100644 --- a/vendor/skycaiji/app/admin/event/Rcms.php +++ b/vendor/skycaiji/app/admin/event/Rcms.php @@ -184,7 +184,10 @@ class Rcms extends Release{ 'catfish'=>'application/catfishajax/controller/Index.php', 'pboot'=>'data/pbootcms.db', 'yzmcms'=>'yzmphp/yzmphp.php', - 'chanzhi'=>'js/chanzhi.all.js', + 'chanzhi'=>'js/chanzhi.all.js', + 'eyoucms'=>'core/library/think/template/taglib/Eyou.php', + 'xunruicms'=>'static/assets/global/css/xunruicms.css', + 'dedebiz'=>'system/libraries/dedebiz.class.php', ); return $files; } diff --git a/vendor/skycaiji/app/admin/event/Rdb.php b/vendor/skycaiji/app/admin/event/Rdb.php index 91b2e38..0b56b52 100644 --- a/vendor/skycaiji/app/admin/event/Rdb.php +++ b/vendor/skycaiji/app/admin/event/Rdb.php @@ -12,222 +12,312 @@ namespace skycaiji\admin\event; use skycaiji\admin\model\DbCommon; class Rdb extends Release{ - protected $db_conn_list=array(); - /** - * 设置页面post过来的config - * @param unknown $config - */ - public function setConfig($config){ - $db=input('db/a',array(),'trim'); - foreach ($db as $k=>$v){ - if(empty($v)&&'pwd'!=$k){ - - $this->error(lang('error_null_input',array('str'=>lang('rele_db_'.$k)))); - } - } - $config['db']=$db; - $config['db_table']=input('db_table/a',array(),'trim'); - - if(is_array($config['db_table'])&&is_array($config['db_table']['field'])){ - foreach($config['db_table']['field'] as $tbName=>$tbFields){ - if(is_array($tbFields)){ - foreach ($tbFields as $tbField=>$fieldVal){ - if(empty($fieldVal)){ - - unset($config['db_table']['field'][$tbName][$tbField]); - unset($config['db_table']['custom'][$tbName][$tbField]); - continue; - } - } - } - } - } - return $config; - } - /*导出数据*/ - public function export($collFieldsList,$options=null){ - - $db_config=$this->get_db_config($this->config['db']); - $db_config['fields_strict']=false; - - $db_key=md5(serialize($db_config)); - if(empty($this->db_conn_list[$db_key])){ - - $mdb=new DbCommon($db_config); - $mdb=$mdb->db(); - $this->db_conn_list[$db_key]=$mdb; - }else{ - $mdb=$this->db_conn_list[$db_key]; - } - - $addedNum=0; - - $dbCharset=strtolower($db_config['db_charset']); - if(empty($dbCharset)||$dbCharset=='utf-8'||$dbCharset=='utf8'){ - - $dbCharset=null; - } - - - foreach ($collFieldsList as $collFieldsKey=>$collFields){ - - $mdb->startTrans(); - - $contTitle=$collFields['title']; - $contUrl=$collFields['url']; - $collFields=$collFields['fields']; - $this->init_download_img($this->task,$collFields); - $tableFields=array(); - foreach ($this->config['db_table']['field'] as $tbName=>$tbFields){ - foreach ($tbFields as $tbField=>$fieldVal){ - if(empty($fieldVal)){ - - unset($tbFields[$tbField]); - continue; - } - if(strcasecmp('custom:',$fieldVal)==0){ - - $fieldVal=$this->config['db_table']['custom'][$tbName][$tbField]; - }elseif(preg_match('/^field\:(.+)$/ui',$fieldVal,$collField)){ - - $fieldVal=$this->get_field_val($collFields[$collField[1]]); - $fieldVal=is_null($fieldVal)?'':$fieldVal; - } - - if(!empty($dbCharset)){ - - $fieldVal=$this->utf8_to_charset($dbCharset, $fieldVal); - } - - $tbFields[$tbField]=$fieldVal; - } - $tableFields[$tbName]=$tbFields; - } - if(!empty($tableFields)){ - if('oracle'==$db_config['db_type']){ - - $pdoOracle=new \PDO($db_config['db_dsn'], $db_config['db_user'], $db_config['db_pwd'],array()); - $pdoOracle->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - } - - $errorMsg=false; - - $autoidList=array(); - foreach ($tableFields as $table=>$fields){ - $table=strtolower($table); - foreach ($fields as $k=>$v){ - - $fields[$k]=preg_replace_callback('/auto_id\@([^\s\#]+)[\#]{0,1}/i',function($autoidTbName)use($autoidList){ - $autoidTbName=trim($autoidTbName[1]); - $autoidTbName=strtolower($autoidTbName); - return $autoidList[$autoidTbName]; - },$v); - } - try { - if('oracle'==$db_config['db_type']){ - - $insertSql='insert into '.$table.' '; - $insertKeys=array(); - $insertVals=array(); - $sequenceName=''; - foreach ($fields as $k=>$v){ - if(preg_match('/^sequence\@([^\s]+)$/i', $v,$m_sequence)){ - - $sequenceName=$m_sequence[1]; - continue; - } - $insertKeys[]=$k; - $insertVals[]="'".str_replace("'", "''", $v)."'"; - } - $insertSql.='('.implode(',', $insertKeys).') values ('.implode(',', $insertVals).')'; - - if($pdoOracle->exec($insertSql)){ - - if(!empty($sequenceName)){ - - $autoId=$pdoOracle->query("select {$sequenceName}.CURRVAL as id FROM DUAL"); - if($autoId){ - $autoId=$autoId->fetch(); - $autoidList[$table]=$autoId[0]; - } - } - if(empty($autoidList[$table])){ - - $autoidList[$table]=1; - } - }else{ - $autoidList[$table]=0; - } - }else{ - - $autoidList[$table]=$mdb->table($table)->insert($fields,false,true); - } - }catch (\Exception $ex){ - $errorMsg=$ex->getMessage(); - $this->echo_msg(array('%s',$errorMsg)); - $errorMsg=!empty($errorMsg)?$errorMsg:($table.'表入库失败'); - break; - } - if($autoidList[$table]<=0){ - - break; - } - } - $returnData=array('id'=>0); - if(!empty($errorMsg)){ - - $mdb->rollback(); - $returnData['error']=$errorMsg; - }else{ - - $mdb->commit(); - reset($autoidList); - $firstTable=key($autoidList); - $firstId=intval($autoidList[$firstTable]); - if($firstId>0){ - $addedNum++; - $returnData['id']=$firstId; - $returnData['target']="{$db_config['db_type']}:{$db_config['db_name']}@table:{$firstTable}@id:{$firstId}"; - }else{ - $returnData['error']='数据库插入失败'; - } - } - $this->record_collected($contUrl,$returnData,$this->release,$contTitle); - } - - unset($collFieldsList[$collFieldsKey]['fields']); - } - return $addedNum; - } - - /*将发布配置中的数据库参数转换成tp数据库参数*/ + protected $db_conn_list=array(); + /** + * 设置页面post过来的config + * @param unknown $config + */ + public function setConfig($config){ + $db=input('db/a',array(),'trim'); + foreach ($db as $k=>$v){ + if(empty($v)&&'pwd'!=$k){ + + $this->error(lang('error_null_input',array('str'=>lang('rele_db_'.$k)))); + } + } + $config['db']=$db; + $dbTables=trim_input_array('db_tables'); + $dbTables=model('Release')->config_db_tables($dbTables); + $config['db_tables']=$dbTables; + return $config; + } + + private function _convert_val_signs($val,$charset,&$collFields,&$querySigns,&$autoIds){ + + $error=''; + $val=preg_replace_callback('/\[([\x{4e00}-\x{9fa5}]+)\:(.*?)\]/u',function($match)use(&$error,&$collFields,&$querySigns,&$autoIds){ + $type=$match[1]; + $name=$match[2]; + $name=$name?trim($name):''; + $returnVal=''; + if($type=='采集字段'){ + $returnVal=$this->get_field_val($collFields[$name]); + $returnVal=is_null($returnVal)?'':$returnVal; + }elseif($type=='查询'){ + $name=strtolower($name); + $returnVal=$querySigns[$name]; + $returnVal=is_null($returnVal)?'':$returnVal; + }elseif($type=='自增主键'){ + $name=strtolower($name); + $returnVal=$autoIds[$name]; + $returnVal=$returnVal?:''; + if(!$returnVal){ + $error='没有自增主键'.$name; + } + }else{ + + $returnVal=$match[0]; + } + return $returnVal; + }, $val); + if($error){ + + throw new \Exception($error); + } + if(!empty($charset)){ + + $val=$this->utf8_to_charset($charset, $val); + } + return $val; + } + + /*导出数据*/ + public function export($collFieldsList,$options=null){ + + $db_config=$this->get_db_config($this->config['db']); + $db_config['fields_strict']=false; + $db_key=md5(serialize($db_config)); + if(empty($this->db_conn_list[$db_key])){ + + $mdb=new DbCommon($db_config); + $mdb=$mdb->db(); + $this->db_conn_list[$db_key]=$mdb; + }else{ + $mdb=$this->db_conn_list[$db_key]; + } + + $addedNum=0; + + $dbCharset=strtolower($db_config['db_charset']); + if(empty($dbCharset)||$dbCharset=='utf-8'||$dbCharset=='utf8'){ + + $dbCharset=null; + } + $mrele=model('Release'); + static $whereCondStrs=array('eq'=>'=','neq'=>'<>','gt'=>'>','egt'=>'>=','lt'=>'<','elt'=>'<=','like'=>'like','nlike'=>'not like','in'=>'in','nin'=>'not in','between'=>'between','nbetween'=>'not between'); + static $queryTypeStrs=array('','max','min','count','sum','avg'); + static $mvalConds=array('in','nin','between','nbetween'); + + $dbHasSeq=$mrele->db_has_sequence($db_config['db_type']); + + foreach ($collFieldsList as $collFieldsKey=>$collFields){ + + $autoIds=array(); + $querySigns=array(); + + $insertTables=array(); + $updateTables=array(); + + $contTitle=$collFields['title']; + $contUrl=$collFields['url']; + $collFields=$collFields['fields']; + $this->init_download_img($this->task,$collFields); + + $dbTables=$this->config['db_tables']; + $errorMsg=false; + $mdb->startTrans(); + foreach ($dbTables as $tbKey=>$dbTable){ + $table=$dbTable['table']?:''; + $table=strtolower($table); + try{ + if(!$table){ + continue; + } + $sqlWhereList=array(); + if(!empty($dbTable['op'])){ + + $tbWhere=$dbTable['where']; + foreach ($tbWhere['logic'] as $k=>$v){ + if($k===0){ + + $v='and'; + } + $v=$v?:'and'; + if($whereCondStrs[$tbWhere['cond'][$k]]){ + + $whereVal=$tbWhere['val'][$k]; + + $whereVal=$this->_convert_val_signs($whereVal,$dbCharset,$collFields,$querySigns,$autoIds); + if(in_array($tbWhere['cond'][$k],$mvalConds)){ + + $whereVal=explode(',',$whereVal); + } + $sqlWhereList[]=array($v,$tbWhere['field'][$k],$whereCondStrs[$tbWhere['cond'][$k]],$whereVal); + } + } + } + if($dbTable['op']=='query'){ + + $tbQuery=$dbTable['query']; + foreach ($tbQuery['type'] as $k=>$v){ + $v=$v?:''; + if(in_array($v, $queryTypeStrs)){ + $v=$v?($v.'('.$tbQuery['field'][$k].')'):$tbQuery['field'][$k]; + $mdb=$mdb->table($table)->field($v.' as qval'); + if($sqlWhereList){ + foreach ($sqlWhereList as $sqlWhere){ + if($sqlWhere[0]=='or'){ + $mdb=$mdb->whereOr($sqlWhere[1],$sqlWhere[2],$sqlWhere[3]); + }else{ + $mdb=$mdb->where($sqlWhere[1],$sqlWhere[2],$sqlWhere[3]); + } + } + } + $v=$mdb->find(); + $v=is_array($v)?$v['qval']:''; + $v=is_null($v)?'':$v; + $k=$mrele->db_tables_query_sign($tbQuery['type'][$k],$tbQuery['field'][$k],$tbQuery['sign'][$k]); + if($k){ + $v=$this->charset_to_utf8($dbCharset, $v); + $querySigns[$k]=$v; + } + } + } + }elseif(empty($dbTable['op'])||$dbTable['op']=='update'){ + + $sequenceName=''; + $tbField=$dbTable['field']; + foreach ($tbField as $k=>$v){ + $v=$this->_convert_val_signs($v,$dbCharset,$collFields,$querySigns,$autoIds); + $tbField[$k]=$v; + } + if($dbHasSeq){ + + $tbSeq=$dbTable['sequence']; + $sequenceName=$tbSeq['seq']; + if($sequenceName&&$tbSeq['field']&&!$tbSeq['trigger']){ + + $tbField[$tbSeq['field']]='#sequence:'.$sequenceName.'#'; + } + } + + if(empty($tbField)){ + $this->echo_msg('表'.$table.'字段必须绑定数据','orange'); + }else{ + if(empty($dbTable['op'])){ + + $status=$mdb->table($table)->insert($tbField); + if($status>0){ + $insertTables[]=$table; + if($dbHasSeq){ + + $autoIds[$table]=$mdb->getLastInsID($sequenceName); + }else{ + $autoIds[$table]=$mdb->getLastInsID(); + } + }else{ + + throw new \Exception('新增失败'); + } + }elseif($dbTable['op']=='update'){ + + if(empty($sqlWhereList)){ + + $this->echo_msg('表'.$table.'更新必须添加条件','orange'); + }else{ + foreach ($sqlWhereList as $sqlWhere){ + if($sqlWhere[0]=='or'){ + $mdb=$mdb->whereOr($sqlWhere[1],$sqlWhere[2],$sqlWhere[3]); + }else{ + $mdb=$mdb->where($sqlWhere[1],$sqlWhere[2],$sqlWhere[3]); + } + } + $status=$mdb->table($table)->update($tbField); + if($status>0){ + $updateTables[]=$table; + }else{ + + $this->echo_msg('表'.$table.'更新失败','orange'); + } + } + } + } + } + }catch (\Exception $ex){ + $errorMsg=$ex->getMessage(); + $errorTbOp=''; + switch ($dbTable['op']){ + case 'update':$errorTbOp='更新';break; + case 'query':$errorTbOp='查询';break; + default:$errorTbOp='新增';break; + } + $errorTbOp='表'.$table.$errorTbOp.':'; + $errorMsg=$errorTbOp.($errorMsg?$errorMsg:'数据库操作失败'); + break; + } + } + + $returnData=array('id'=>0); + if(!empty($errorMsg)){ + + $mdb->rollback(); + $returnData['error']=$errorMsg; + }else{ + + $mdb->commit(); + $firstTable=''; + $firstId=0; + $firstOp=''; + if(count($insertTables)>0){ + + $firstTable=reset($insertTables); + $firstId=intval($autoIds[$firstTable]); + $firstOp='新增'; + }elseif(count($updateTables)>0){ + + $firstTable=reset($updateTables); + $firstOp='更新'; + } + if($firstTable){ + + $addedNum++; + $returnData['target']="{$db_config['db_type']}:{$db_config['db_name']}@table:{$firstTable}"; + if($firstId>0){ + $returnData['id']=$firstId; + $returnData['target'].="@id:{$firstId}"; + }else{ + $returnData['id']=1; + $returnData['target'].="@".$firstOp; + } + }else{ + $returnData['error']='没有成功的新增或更新操作'; + } + } + $this->record_collected($contUrl,$returnData,$this->release,$contTitle); + + unset($collFieldsList[$collFieldsKey]['fields']); + } + return $addedNum; + } + + /*将发布配置中的数据库参数转换成tp数据库参数*/ public function get_db_config($config_db){ - $db_config=array( - 'db_type' => strtolower($config_db['type']), - 'db_user' => $config_db['user'], - 'db_pwd' => $config_db['pwd'], - 'db_host' => $config_db['host'], - 'db_port' => $config_db['port'], - 'db_charset' => $config_db['charset'], - 'db_name' => $config_db['name'], - - ); - - if(strcasecmp($db_config['db_charset'], 'utf-8')===0){ - $db_config['db_charset']='utf8'; - } - - if('mysqli'==$db_config['db_type']){ - - $db_config['db_type']='mysql'; - }elseif('oracle'==$db_config['db_type']){ - - $db_config['db_dsn']="oci:host={$db_config['db_host']};dbname={$db_config['db_name']};charset={$db_config['db_charset']}"; - }elseif('sqlsrv'==$db_config['db_type']){ - - $db_config['db_dsn']='sqlsrv:Database='.$db_config['db_name'].';Server='.$db_config['db_host']; - } - return $db_config; + $db_config=array( + 'db_type' => strtolower($config_db['type']), + 'db_user' => $config_db['user'], + 'db_pwd' => $config_db['pwd'], + 'db_host' => $config_db['host'], + 'db_port' => $config_db['port'], + 'db_charset' => $config_db['charset'], + 'db_name' => $config_db['name'], + + ); + + if(strcasecmp($db_config['db_charset'], 'utf-8')===0){ + $db_config['db_charset']='utf8'; + } + + if('mysqli'==$db_config['db_type']){ + + $db_config['db_type']='mysql'; + }elseif('oracle'==$db_config['db_type']){ + + $db_config['db_dsn']="oci:host={$db_config['db_host']};dbname={$db_config['db_name']};charset={$db_config['db_charset']}"; + }elseif('sqlsrv'==$db_config['db_type']){ + + $db_config['db_dsn']='sqlsrv:Database='.$db_config['db_name'].';Server='.$db_config['db_host']; + } + return $db_config; } } ?> \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/event/Rdiy.php b/vendor/skycaiji/app/admin/event/Rdiy.php index b1133d2..e7d813b 100644 --- a/vendor/skycaiji/app/admin/event/Rdiy.php +++ b/vendor/skycaiji/app/admin/event/Rdiy.php @@ -27,11 +27,11 @@ class Rdiy extends Release{ if(empty($diy['app'])){ $this->error('请输入插件名称'); } - if(in_array($diy['app'], array('base','code'))){ - $this->error($diy['app'].'为系统保留名称,不能使用'); + if(model('ReleaseApp')->isSystemApp($diy['app'],'diy')){ + $this->error($diy['app'].'为系统保留名称,不能使用'); } - if(!preg_match('/^[a-z][a-z0-9]+$/i', $diy['app'])){ - $this->error('插件名称必须以字母开头且由字母或数字组成'); + if(!model('ReleaseApp')->isRightApp($diy['app'],'diy')){ + $this->error('插件名称必须以字母开头且由字母或数字组成'); } }elseif($diy['type']=='code'){ if(empty($diy['code'])){ diff --git a/vendor/skycaiji/app/admin/event/Release.php b/vendor/skycaiji/app/admin/event/Release.php index b378260..b7e3ebd 100644 --- a/vendor/skycaiji/app/admin/event/Release.php +++ b/vendor/skycaiji/app/admin/event/Release.php @@ -16,9 +16,7 @@ abstract class Release extends ReleaseBase{ public $task; /*发布时初始化*/ public function init($release){ - if(!is_array($release['config'])){ - $release['config']=unserialize($release['config']?:''); - } + $release['config']=model('Release')->compatible_config($release['config']); $this->release=$release; $this->config=$release['config']; diff --git a/vendor/skycaiji/app/admin/event/ReleaseBase.php b/vendor/skycaiji/app/admin/event/ReleaseBase.php index 88b587b..7f3581e 100644 --- a/vendor/skycaiji/app/admin/event/ReleaseBase.php +++ b/vendor/skycaiji/app/admin/event/ReleaseBase.php @@ -46,7 +46,6 @@ class ReleaseBase extends CollectBase{ if(!empty($returnData['error'])){ - if(model('Collected')->collGetNumByUrl($url)<=0){ model('Collected')->insert(array( @@ -61,8 +60,7 @@ class ReleaseBase extends CollectBase{ 'addtime'=>time() )); } - - $this->echo_msg(array('%s:%s',$returnData['error'],$url,$url),'red',$echo); + $this->echo_msg(array('%s',$returnData['error']),'red',$echo); } } @@ -165,14 +163,18 @@ class ReleaseBase extends CollectBase{ return $url; } - $mproxy=model('Proxyip'); + $mproxy=model('ProxyIp'); $options=array(); $proxyDbIp=null; if(!is_empty(g_sc_c('proxy','open'))){ $proxyDbIp=$mproxy->get_usable_ip(); $proxyIp=$mproxy->to_proxy_ip($proxyDbIp); - if(!empty($proxyIp)){ + if(empty($proxyIp)){ + + $this->echo_msg(array('没有可用的代理IP,跳过下载图片',$url)); + return $url; + }else{ $options['proxy']=$proxyIp; } @@ -376,9 +378,9 @@ class ReleaseBase extends CollectBase{ if(!empty($proxyDbIp)){ $this->echo_msg(array('代理IP:%s',$proxyDbIp['ip']),'black',true,'','display:inline;margin-right:5px;'); } - if($retryCur<=0){ - $this->echo_msg(array('
    图片下载失败:%s
    ',$url,$url),'red'); - } + + $this->retry_first_echo($retryCur,'图片下载失败',$url,$imgCodeInfo); + if(!empty($proxyDbIp)){ if($imgCodeInfo['code']!=404){ @@ -389,17 +391,8 @@ class ReleaseBase extends CollectBase{ $this->collect_sleep(g_sc_c('download_img','wait')); - if($retryMax>0){ - - if($retryCur<$retryMax){ - - $retryCur++; - $this->echo_msg(array('%s第%s次',$retryCur>1?' / ':'重试:',$retryCur),'black',true,'','display:inline;'.($retryCur==$retryMax?'margin-right:5px;':'')); - return $this->download_img($retryParams[0]); - }else{ - $retryCur=0; - $this->echo_msg('图片无效','red',true,'','display:inline;margin-right:5px;'); - } + if($this->retry_do_func($retryCur,$retryMax,'图片无效')){ + return $this->download_img($retryParams[0]); } } }catch (\Exception $ex){ @@ -430,7 +423,7 @@ class ReleaseBase extends CollectBase{ } } if(!empty($charset)&&!empty($filename)){ - $filename=iconv('utf-8',$charset.'//IGNORE',$filename); + $filename=\util\Funcs::convert_charset($filename,'utf-8',$charset); } return $filename; } @@ -467,14 +460,16 @@ class ReleaseBase extends CollectBase{ /*utf8转换成其他编码*/ public function utf8_to_charset($charset,$val){ - static $chars=array('utf-8','utf8','utf8mb4'); - if(!in_array(strtolower($charset),$chars)){ - if(!empty($val)){ - $val=iconv('utf-8',$charset.'//IGNORE',$val); - } - } + $val=\util\Funcs::convert_charset($val,'utf-8',$charset); return $val; } + + /*其他编码转换成utf8*/ + public function charset_to_utf8($charset,$val){ + $val=\util\Funcs::convert_charset($val,$charset,'utf-8'); + return $val; + } + /** * 任意编码转换成utf8 * @param mixed $val 字符串或数组 diff --git a/vendor/skycaiji/app/admin/event/Rtoapi.php b/vendor/skycaiji/app/admin/event/Rtoapi.php index ab57259..cf1293d 100644 --- a/vendor/skycaiji/app/admin/event/Rtoapi.php +++ b/vendor/skycaiji/app/admin/event/Rtoapi.php @@ -22,9 +22,6 @@ class Rtoapi extends Release{ if(empty($toapi['url'])){ $this->error('请输入接口地址'); } - if(empty($toapi['response']['id'])){ - $this->error('请输入响应id的健名'); - } $toapi['param_name']=is_array($toapi['param_name'])?$toapi['param_name']:array(); @@ -89,6 +86,11 @@ class Rtoapi extends Release{ $apiResponse=$this->config['toapi']['response']; $apiResponse=is_array($apiResponse)?$apiResponse:array(); + $apiResponse['id']=$apiResponse['id']?:'id'; + $apiResponse['target']=$apiResponse['target']?:'target'; + $apiResponse['desc']=$apiResponse['desc']?:'desc'; + $apiResponse['error']=$apiResponse['error']?:'error'; + $apiCharset=$this->config['toapi']['charset']; if($apiCharset=='custom'){ $apiCharset=$this->config['toapi']['charset_custom']; @@ -97,6 +99,17 @@ class Rtoapi extends Release{ $apiCharset='utf-8'; } + $curlopts=array(); + + $apiEncode=$this->config['toapi']['encode']; + if($apiEncode=='custom'){ + $apiEncode=$this->config['toapi']['encode_custom']; + } + if($apiEncode){ + $curlopts[CURLOPT_ENCODING]=$apiEncode; + } + + $paramVals=array(); $paramFields=array(); if(is_array($this->config['toapi']['param_name'])){ @@ -143,8 +156,8 @@ class Rtoapi extends Release{ } } - $apiWait=intval($this->config['toapi']['wait']); - $apiRetry=intval($this->config['toapi']['retry']); + $retryWait=intval($this->config['toapi']['wait']); + $retryMax=intval($this->config['toapi']['retry']); foreach ($collFieldsList as $collFieldsKey=>$collFields){ $contTitle=$collFields['title']; @@ -177,14 +190,16 @@ class Rtoapi extends Release{ } } - $curRetry=0; + $retryCur=0; do{ $doWhile=false; - $html=get_html($url,$headerData,array(),$apiCharset,$postData); + $htmlInfo=get_html($url,$headerData,array('return_body'=>1,'curlopts'=>$curlopts),$apiCharset,$postData,true); + init_array($htmlInfo); + $html=$htmlInfo['html']?:''; $this->collect_sleep($this->config['toapi']['interval'],true); $json=json_decode($html,true); $returnData=array('id'=>'','target'=>'','desc'=>'','error'=>''); - if(!empty($apiResponse['id'])&&isset($json[$apiResponse['id']])){ + if(!empty($apiResponse['id'])&&is_array($json)&&isset($json[$apiResponse['id']])){ foreach ($returnData as $k=>$v){ @@ -203,25 +218,14 @@ class Rtoapi extends Release{ } }else{ - if($curRetry<=0){ - - $this->echo_msg('发布设置»调用接口失败'); - } - $this->collect_sleep($apiWait); + $this->retry_first_echo($retryCur,'发布设置»调用接口失败',null,$htmlInfo); - if($apiRetry>0){ - - if($curRetry<$apiRetry){ - - $curRetry++; - if($this->is_collecting()){ - $this->echo_msg(array('%s第%s次',$curRetry>1?' / ':'重试:',$curRetry),'black',true,'','display:inline;'.($curRetry==$apiRetry?'margin-right:5px;':'')); - } - $doWhile=true; - }else{ - $curRetry=0; - } + $this->collect_sleep($retryWait); + + if($this->retry_do_func($retryCur,$retryMax,'接口无效')){ + $doWhile=true; } + $returnData['id']=0; $returnData['error']='发布接口无响应状态'; } @@ -230,7 +234,7 @@ class Rtoapi extends Release{ $this->record_collected($contUrl,$returnData,$this->release,$contTitle); if($testToapi){ - $this->echo_msg('

    获取到响应数据:

    ','black'); + $this->echo_msg('

    发布接口响应数据:

    ','black'); } diff --git a/vendor/skycaiji/app/admin/lang/zh-cn.php b/vendor/skycaiji/app/admin/lang/zh-cn.php index d7255ca..b5d2bc3 100644 --- a/vendor/skycaiji/app/admin/lang/zh-cn.php +++ b/vendor/skycaiji/app/admin/lang/zh-cn.php @@ -169,7 +169,7 @@ return array( 'field_module_words'=>'固定文字', 'field_module_num'=>'随机数字', 'field_module_time'=>'时间', - 'field_module_list'=>'随机抽取', + 'field_module_list'=>'列表抽取', 'field_module_json'=>'JSON提取', 'field_module_merge'=>'字段组合', 'field_module_extract'=>'字段提取', diff --git a/vendor/skycaiji/app/admin/model/App.php b/vendor/skycaiji/app/admin/model/App.php index 27176fe..b3da444 100644 --- a/vendor/skycaiji/app/admin/model/App.php +++ b/vendor/skycaiji/app/admin/model/App.php @@ -113,6 +113,7 @@ class App extends \skycaiji\common\model\BaseModel{ } /*清理描述html*/ public function clear_desc($desc){ + $desc=$desc?:''; $desc=strip_tags($desc,'


    '); $desc=preg_replace('/<(p|br|b|i)\s+.*?>/i', "<$1>", $desc); $desc=preg_replace('/[\r\n]+/', ' ', $desc); diff --git a/vendor/skycaiji/app/admin/model/Collector.php b/vendor/skycaiji/app/admin/model/Collector.php index 4678414..5dbe78c 100644 --- a/vendor/skycaiji/app/admin/model/Collector.php +++ b/vendor/skycaiji/app/admin/model/Collector.php @@ -174,7 +174,7 @@ class Collector extends \skycaiji\common\model\BaseModel{ if(is_dir($dir)){ @rmdir($dir); } - CacheModel::getInstance('collecting')->deleteCache($collectorKey); + self::collecting_data($collectorKey,'delete'); }else{ $collFile=self::collecting_file($collectorKey.'/'.$processKey); if(file_exists($collFile)){ @@ -189,18 +189,29 @@ class Collector extends \skycaiji\common\model\BaseModel{ } } - public static function collecting_data($collectorKey){ - $processes=array(); - if(!empty($collectorKey)){ - $processes=CacheModel::getInstance('collecting')->getCache($collectorKey,'data'); + public static function collecting_data($collectorKey,$dataOp=null){ + if($collectorKey){ + $mcache=CacheModel::getInstance('collecting'); + if(!isset($dataOp)){ + + $processes=$mcache->getCache($collectorKey,'data'); + init_array($processes); + return $processes; + }elseif($dataOp==='delete'){ + + $mcache->deleteCache($collectorKey); + }elseif(is_array($dataOp)){ + + $mcache->setCache($collectorKey,$dataOp); + } + }else{ + + if(!isset($dataOp)){ + + return array(); + } } - if(!is_array($processes)){ - $processes=array(); - } - return $processes; } - - public static function collecting_process_status($collectorKey,$processKey){ $status='none'; @@ -247,12 +258,6 @@ class Collector extends \skycaiji\common\model\BaseModel{ } return $lockList; } - /*采集密钥*/ - public static function collect_key($isProcess=false){ - $key=\util\Funcs::uniqid($isProcess?'collect_process':'auto_collect'); - \util\Param::set_temp_cahce_key($key); - return $key; - } /*触发运行自动采集*/ public static function collect_run_auto($rootUrl=''){ try{ @@ -263,7 +268,7 @@ class Collector extends \skycaiji\common\model\BaseModel{ }else{ $url=url('admin/index/auto_collect',null,false,true); } - $url.=(strpos($url, '?')===false?'?':'&').'backstage_run=1&key='.self::collect_key(); + $url.=(strpos($url, '?')===false?'?':'&').'backstage_run=1&key='.\util\Param::set_temp_cahce_key('auto_collect'); get_html($url,null,array('timeout'=>3)); }catch(\Exception $ex){} } @@ -306,7 +311,7 @@ class Collector extends \skycaiji\common\model\BaseModel{ $processes[$k]=$v; } - CacheModel::getInstance('collecting')->setCache($collectorKey,$processes); + self::collecting_data($collectorKey,$processes); $collFile=self::collecting_file($collectorKey.'/main'); write_dir_file($collFile,'1'); @@ -340,7 +345,7 @@ class Collector extends \skycaiji\common\model\BaseModel{ $chList=array(); foreach ($processes as $pkey=>$ptids){ $allParams=array( - 'key'=>self::collect_key(true), + 'key'=>\util\Param::set_temp_cahce_key('collect_process'), 'collector_process'=>$collectorKey.'-'.$pkey, ); if(isset($collectNum)){ @@ -360,7 +365,7 @@ class Collector extends \skycaiji\common\model\BaseModel{ } $allParams=http_build_query($allParams); $url=url('admin/index/collect_process?'.$allParams,null,false,true); - $chList[$pkey]=\util\Curl::get($url,null,array('return_curl'=>1,'timeout'=>3)); + $chList[$pkey]=get_html($url,null,array('return_curl'=>1,'timeout'=>3)); curl_multi_add_handle($mh, $chList[$pkey]); } diff --git a/vendor/skycaiji/app/admin/model/Config.php b/vendor/skycaiji/app/admin/model/Config.php index 4a55825..bf80abc 100644 --- a/vendor/skycaiji/app/admin/model/Config.php +++ b/vendor/skycaiji/app/admin/model/Config.php @@ -276,7 +276,7 @@ class Config extends \skycaiji\common\model\Config { $result=return_result('',false); $phpFile=self::cli_safe_filename($phpFile); $phpFile.=' -v'; - $info=\util\Tools::proc_open_exec($phpFile,'all',10,true); + $info=\util\Tools::proc_open_exec_curl($phpFile,'all',10,true); $info=is_array($info)?$info:array(); $info['output']=trim($info['output']); $info['error']=trim($info['error']); diff --git a/vendor/skycaiji/app/admin/model/DbCommon.php b/vendor/skycaiji/app/admin/model/DbCommon.php index ea122b1..f25f0c8 100644 --- a/vendor/skycaiji/app/admin/model/DbCommon.php +++ b/vendor/skycaiji/app/admin/model/DbCommon.php @@ -11,6 +11,7 @@ namespace skycaiji\admin\model; use think\Db; +use think\db\Query; /*动态操作数据库*/ @@ -36,12 +37,10 @@ class DbCommon{ $this->config['params'][\PDO::ATTR_PERSISTENT]=true; } - if(isset($config['fields_strict'])){ $this->config['fields_strict']=$config['fields_strict']; } - if($this->config['type']=='mysqli'){ $this->config['type']='mysql'; @@ -62,7 +61,7 @@ class DbCommon{ $config=$this->config; if($config['type']=='oracle'){ - $config['type']='\think\oracle\Connection'; + $config['type']='\\skycaiji\\admin\\model\\DbOracleConnection'; } if($compatible){ @@ -187,4 +186,43 @@ class DbCommon{ } } + +class DbOracle extends \think\oracle\Connection{ + public function getLastInsID($sequence = null){ + if ($sequence) { + return parent::getLastInsID($sequence); + } + return 0; + } +} + + +class DbOracleConnection extends \think\oracle\Connection{ + public function getLastInsID($sequence = null){ + if ($sequence) { + return parent::getLastInsID($sequence); + } + return 0; + } + + public function execute($sql, $bind = [], Query $query = null){ + $seqStr='#sequence:'; + foreach ($bind as $dataKey=>$dataVal){ + if(is_array($dataVal)){ + $dataVal=$dataVal[0]; + } + if(strpos($dataVal,$seqStr)===0){ + + $seqVal=''; + if(preg_match('/'.$seqStr.'(.*?)#/i',$dataVal,$seqVal)){ + $seqVal=$seqVal[1]; + $seqVal=$seqVal.'.nextval'; + } + $sql=preg_replace('/\:'.$dataKey.'\b(\s*[\,\)])/',$seqVal."$1",$sql); + unset($bind[$dataKey]); + } + } + return parent::execute($sql,$bind,$query); + } +} ?> \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/model/FuncApp.php b/vendor/skycaiji/app/admin/model/FuncApp.php index 4d83711..3cc4739 100644 --- a/vendor/skycaiji/app/admin/model/FuncApp.php +++ b/vendor/skycaiji/app/admin/model/FuncApp.php @@ -11,8 +11,6 @@ namespace skycaiji\admin\model; class FuncApp extends \skycaiji\common\model\BaseModel{ - protected $tableName='func_app'; - public $funcPath; public $funcModules=array( 'process'=>array ( @@ -67,8 +65,10 @@ class FuncApp extends \skycaiji\common\model\BaseModel{ if(is_array($appData['methods'])){ $methods=''; + $settedList=array(); foreach ($appData['methods']['method'] as $k=>$v){ - if($this->right_method($v)){ + if($this->right_method($v)&&!in_array($v, $settedList)){ + $settedList[]=$v; $methods.="\r\n /**\r\n * ".$this->format_str($appData['methods']['comment'][$k])."\r\n */" ."\r\n public function {$v}(\$val){\r\n return \$val;\r\n }"; @@ -82,7 +82,15 @@ class FuncApp extends \skycaiji\common\model\BaseModel{ $funcTpl=str_replace(array('{$module}','{$classname}','{$name}','{$methods}'), array($module,$app,$appData['name'],$appData['methods']), $funcTpl); if(write_dir_file($funcFile,$funcTpl)){ - return $this->insertApp(array('module'=>$module,'app'=>$app,'name'=>$name,'enable'=>1)); + $funcData=$this->where('app',$app)->find(); + if(!empty($funcData)){ + + $this->where('id',$funcData['id'])->update(array('name'=>$name,'uptime'=>time())); + return $funcData['id']; + }else{ + + return $this->insertApp(array('module'=>$module,'app'=>$app,'name'=>$name,'enable'=>1)); + } }else{ return false; } @@ -142,26 +150,34 @@ class FuncApp extends \skycaiji\common\model\BaseModel{ } /*获取插件文件类的属性*/ public function get_app_class($module,$app,$options=array()){ + $config=array(); + $module=$this->format_module($module); + + $config['module']=$module; + $config['app']=$app; + $config['methods']=array(); + + if(preg_match('/^(\w+?)([A-Z])(\w*)$/',$app,$mapp)){ + $config['identifier']=$mapp[1]; + $config['copyright']=$mapp[2].$mapp[3]; + } + $filename=$this->filename($module,$app); if(file_exists($filename)){ $class=$this->app_classname($module, $app); - if(class_exists($class)){ - $copyright=''; - $identifier=''; - if(preg_match('/^(\w+?)([A-Z])(\w*)$/',$app,$mapp)){ - $identifier=$mapp[1]; - $copyright=$mapp[2].$mapp[3]; - } + if(\util\Funcs::class_exists_clean($class)){ + $config['filename']=$filename; $class=new $class(); - $reClass = new \ReflectionClass($class); + $name=$reClass->getDocComment(); $name=preg_replace('/^[\/\*\s]+/m', '', $name); $name=trim($name); + $config['name']=$name; - $reMethods=$reClass->getMethods(\ReflectionMethod::IS_PUBLIC); $methods=array(); + $reMethods=$reClass->getMethods(\ReflectionMethod::IS_PUBLIC); if(!empty($reMethods)){ $phpCode=array(); if(!empty($options['method_code'])||!empty($options['method_params'])){ @@ -232,19 +248,11 @@ class FuncApp extends \skycaiji\common\model\BaseModel{ } $methods[$methodName]=$methodData; } - } - return array ( - 'module' => $module, - 'app' => $app, - 'filename' => $filename, - 'copyright' => $copyright, - 'identifier' => $identifier, - 'name' => $name, - 'methods' => $methods, - ); + } + $config['methods']=$methods; } } - return array(); + return $config; } public function app_classname($module,$app){ return '\\plugin\\func\\'.$module.'\\'.$app; @@ -299,31 +307,6 @@ class FuncApp extends \skycaiji\common\model\BaseModel{ } } - /*获取所有插件类*/ - public function get_class_list($module){ - $apps=$this->get_app_list($module); - $classList=array(); - foreach($apps as $app){ - $class=$this->get_app_class($module,$app); - if(!empty($class)){ - $classList[$app]=$class; - } - } - return $classList; - } - public function get_app_list($module){ - $apps=scandir($this->funcPath.$module); - $appList=array(); - if(!empty($apps)){ - foreach($apps as $app){ - if(preg_match('/(\w+)\.php/i',$app,$mapp)){ - $appList[$app]=$mapp[1]; - } - } - } - return $appList; - } - public function get_func_module_val($module,$key){ if(is_array($this->funcModules[$module])){ @@ -333,7 +316,6 @@ class FuncApp extends \skycaiji\common\model\BaseModel{ } } - /** * 执行插件函数 * @param string $module 模块 @@ -380,7 +362,7 @@ class FuncApp extends \skycaiji\common\model\BaseModel{ if(!isset($class_list[$className])){ $class=$this->app_classname($module,$className); - if(!class_exists($class)){ + if(!\util\Funcs::class_exists_clean($class)){ $class_list[$className]=1; }else{ $enable=$this->field('enable')->where(array('app'=>$className,'module'=>$module))->value('enable'); diff --git a/vendor/skycaiji/app/admin/model/ProxyGroup.php b/vendor/skycaiji/app/admin/model/ProxyGroup.php new file mode 100644 index 0000000..a2985b7 --- /dev/null +++ b/vendor/skycaiji/app/admin/model/ProxyGroup.php @@ -0,0 +1,26 @@ +where($cond)->order($order)->column('*'); + init_array($list); + return $list; + } + public function getNameById($id){ + $name=$this->where('id',$id)->column('name','id'); + $name=$name[$id]; + $name=$name?:''; + return $name; + } +} +?> \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/model/Proxyip.php b/vendor/skycaiji/app/admin/model/Proxyip.php index 3d04282..8c16503 100644 --- a/vendor/skycaiji/app/admin/model/Proxyip.php +++ b/vendor/skycaiji/app/admin/model/Proxyip.php @@ -11,8 +11,7 @@ namespace skycaiji\admin\model; -class Proxyip extends \skycaiji\common\model\BaseModel { - public $name='proxy_ip'; +class ProxyIp extends \skycaiji\common\model\BaseModel { public function __construct($data=[]){ parent::__construct($data); } @@ -62,6 +61,10 @@ class Proxyip extends \skycaiji\common\model\BaseModel { $cond['num']=array('lt',1); } + if(!empty($config['group_id'])){ + + $cond['group_id']=$config['group_id']; + } $cond['invalid']=0; $proxyipData=$this->where($cond)->order($order)->find(); @@ -70,7 +73,13 @@ class Proxyip extends \skycaiji\common\model\BaseModel { $apiInsert=strtolower($config['api']['insert']); if(empty($apiInsert)){ - if($this->where('invalid',0)->count()<=0){ + $cond2=array(); + if(!empty($config['group_id'])){ + + $cond2['group_id']=$config['group_id']; + } + $cond2['invalid']=0; + if($this->where($cond2)->count()<=0){ $this->add_api_ips(); $proxyipData=$this->where($cond)->order($order)->find(); @@ -166,6 +175,10 @@ class Proxyip extends \skycaiji\common\model\BaseModel { $ips=array(); if(preg_match_all('/'.$format.'/i',$html,$mips)){ + init_array($mips['ip']); + init_array($mips['port']); + init_array($mips['user']); + init_array($mips['pwd']); for($i=0;$i$mips['ip'][$i], @@ -212,6 +225,7 @@ class Proxyip extends \skycaiji\common\model\BaseModel { } $ip['ip']=$ip['ip'].':'.$ip['port']; $ip['type']=$default['type']; + $ip['group_id']=$default['group_id']; $ip['addtime']=$nowTime; unset($ip['port']); $ipList[$k]=$ip; @@ -242,7 +256,8 @@ class Proxyip extends \skycaiji\common\model\BaseModel { $ips = $this->ips_format2db ( $ips, array ( 'type' => $api ['api_type']?$api ['api_type']:'', 'user' => $api ['api_user']?$api ['api_user']:'', - 'pwd' => $api ['api_pwd']?$api ['api_pwd']:'', + 'pwd' => $api ['api_pwd']?$api ['api_pwd']:'', + 'group_id' => $api ['api_group_id']?$api ['api_group_id']:'', ) ); if(!empty($ips)){ diff --git a/vendor/skycaiji/app/admin/model/Release.php b/vendor/skycaiji/app/admin/model/Release.php index 866b961..1e08e27 100644 --- a/vendor/skycaiji/app/admin/model/Release.php +++ b/vendor/skycaiji/app/admin/model/Release.php @@ -12,21 +12,120 @@ namespace skycaiji\admin\model; class Release extends \skycaiji\common\model\BaseModel{ - public function getByTaskid($taskId){ - static $dataList=array(); - if(empty($taskId)){ - return null; - } - if(!isset($dataList[$taskId])){ - $taskData=$this->where(array('task_id'=>$taskId))->find(); - if(!empty($taskData)){ - $dataList[$taskId]=$taskData->toArray(); - }else{ - $dataList[$taskId]=array(); - } - } - return $dataList[$taskId]; - } + + public function compatible_config($config){ + if(!is_array($config)){ + $config=unserialize($config?:''); + } + init_array($config); + foreach (config('release_modules') as $v){ + init_array($config[$v]); + } + + if(!empty($config['db_table'])){ + + $confDbTable=$config['db_table']; + init_array($confDbTable); + init_array($confDbTable['field']); + init_array($confDbTable['custom']); + $dbTables=array(); + foreach ($confDbTable['field'] as $table=>$field){ + $custom=$confDbTable['custom'][$table]; + init_array($field); + init_array($custom); + $sequence=array(); + foreach ($field as $fk=>$fv){ + if(strcasecmp('custom:',$fv)==0){ + + $fv=$custom[$fk]; + + $fv=preg_replace_callback('/auto_id\@([^\s\#]+)[\#]{0,1}/i',function($mname){ + $mname=trim($mname[1]); + return '[自增主键:'.$mname.']'; + },$fv); + + $fv=preg_replace_callback('/sequence\@([^\s]+)/i',function($mname)use(&$sequence,$fk){ + $mname=trim($mname[1]); + $sequence['field']=$fk; + $sequence['seq']=$mname; + $sequence['trigger']=''; + return null; + },$fv); + }elseif(preg_match('/^field\:(.+)$/ui',$fv,$collField)){ + + $fv='[采集字段:'.$collField[1].']'; + } + if(is_null($fv)){ + unset($field[$fk]); + }else{ + $field[$fk]=$fv; + } + } + + $dbTables[]=array( + 'table'=>$table, + 'field'=>$field, + 'sequence'=>$sequence, + ); + } + $dbTables=$this->config_db_tables($dbTables); + unset($config['db_table']); + $config['db_tables']=$dbTables; + } + return $config; + } + + public function config_db_tables($dbTables,$keepIndex=false){ + + init_array($dbTables); + foreach ($dbTables as $tbKey=>$dbTable){ + init_array($dbTable['field']); + + foreach ($dbTable['field'] as $fk=>$fv){ + if(is_null($fv)||$fv===''){ + + unset($dbTable['field'][$fk]); + continue; + } + } + + $tbWhere=$dbTable['where']; + init_array($tbWhere); + \util\Funcs::filter_key_val_list4($tbWhere['field'], $tbWhere['cond'], $tbWhere['logic'], $tbWhere['val']); + \util\Funcs::filter_key_val_list4($tbWhere['cond'], $tbWhere['field'], $tbWhere['logic'], $tbWhere['val']); + \util\Funcs::filter_key_val_list4($tbWhere['logic'], $tbWhere['field'], $tbWhere['cond'], $tbWhere['val']); + $dbTable['where']=$tbWhere; + + $tbQuery=$dbTable['query']; + init_array($tbQuery); + \util\Funcs::filter_key_val_list3($tbQuery['field'], $tbQuery['type'], $tbQuery['sign']); + $dbTable['query']=$tbQuery; + init_array($dbTable['sequence']); + $dbTables[$tbKey]=$dbTable; + } + if(!$keepIndex){ + + $dbTables=array_values($dbTables); + } + return $dbTables; + } + + + public function db_tables_query_sign($type,$field,$sign=''){ + if(empty($sign)){ + $sign=($type?($type.'_'):'').$field; + } + $sign=strtolower($sign); + return $sign; + } + + public function db_has_sequence($dbType){ + $dbType=strtolower($dbType?:''); + if($dbType=='oracle'){ + return true; + } + return false; + } } ?> \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/model/ReleaseApp.php b/vendor/skycaiji/app/admin/model/ReleaseApp.php index 506d059..edf3e61 100644 --- a/vendor/skycaiji/app/admin/model/ReleaseApp.php +++ b/vendor/skycaiji/app/admin/model/ReleaseApp.php @@ -13,8 +13,6 @@ namespace skycaiji\admin\model; use think\Loader; class ReleaseApp extends \skycaiji\common\model\BaseModel{ - protected $tableName='release_app'; - public function addCms($cms,$code='',$tpl=''){ if(empty($cms['app'])){ return false; @@ -23,11 +21,10 @@ class ReleaseApp extends \skycaiji\common\model\BaseModel{ $cms['module']='cms'; $cms['uptime']=$cms['uptime']>0?$cms['uptime']:time(); - if(!preg_match('/^([A-Z][a-z0-9]*){3}$/',$cms['app'])){ - - return false; + if(!$this->isRightApp($cms['app'], 'cms')){ + + return false; } - $codeFmt=\util\Funcs::strip_phpcode_comment($code); if(!preg_match('/^\s*namespace\s+plugin\\\release\b/im',$codeFmt)){ @@ -67,20 +64,46 @@ class ReleaseApp extends \skycaiji\common\model\BaseModel{ return $success; } - public function appFileName($appName,$model='cms'){ - $model=strtolower($model); + public function appFileName($appName,$module='cms'){ + $module=strtolower($module); $appName=ucfirst($appName); - return config('plugin_path').'/release/'.$model.'/'.$appName.'.php'; + return config('plugin_path').'/release/'.$module.'/'.$appName.'.php'; } - public function appFileExists($appName,$model='cms'){ - $fileName=$this->appFileName($appName,$model); + public function appFileExists($appName,$module='cms'){ + $fileName=$this->appFileName($appName,$module); return file_exists($fileName)?true:false; } - public function appImportClass($appName,$model='cms'){ - $cmsClass='\\plugin\\release\\'.strtolower($model).'\\'.ucfirst($appName); - $cmsClass=new $cmsClass(); + public function appImportClass($appName,$module='cms'){ + $cmsClass='\\plugin\\release\\'.strtolower($module).'\\'.ucfirst($appName); + if(\util\Funcs::class_exists_clean($cmsClass)){ + $cmsClass=new $cmsClass(); + }else{ + $cmsClass=null; + } return $cmsClass; } + + public function isSystemApp($appName,$module='cms'){ + static $systemApps = array( + 'cms'=>array('basecms'), + 'diy'=>array('basediy','codediy','base','code'), + ); + $appName=$appName?strtolower($appName):''; + if (is_array($systemApps[$module])&&in_array($appName, $systemApps[$module])) { + return true; + } else { + return false; + } + } + + public function isRightApp($app,$module){ + if($module=='diy'){ + return preg_match('/^[a-z][a-z0-9]+$/i', $app)?true:false; + }elseif($module=='cms'){ + return preg_match('/^([A-Z][a-z0-9]*){3}$/',$app)?true:false; + } + return false; + } /*导入v1.x版本发布插件*/ public function oldImportClass($appName,$model='Cms'){ $model=ucfirst($model); diff --git a/vendor/skycaiji/app/admin/model/Task.php b/vendor/skycaiji/app/admin/model/Task.php index c72f7cc..f3e036f 100644 --- a/vendor/skycaiji/app/admin/model/Task.php +++ b/vendor/skycaiji/app/admin/model/Task.php @@ -77,7 +77,14 @@ class Task extends \skycaiji\common\model\BaseModel{ }else{ set_g_sc(['c','proxy','open'],$config['proxy']=='n'?0:1); } - + + if(!is_numeric($config['proxy_group_id'])){ + + set_g_sc(['c','proxy','group_id'],$original_config['proxy']['group_id']); + }else{ + + set_g_sc(['c','proxy','group_id'],$config['proxy_group_id']); + } static $imgParams=array('img_path','img_url','img_name','name_custom_path','name_custom_name','interval_img','img_func_param'); foreach ($imgParams as $imgParam){ diff --git a/vendor/skycaiji/app/admin/model/TaskTimer.php b/vendor/skycaiji/app/admin/model/TaskTimer.php index f8369cb..fecede3 100644 --- a/vendor/skycaiji/app/admin/model/TaskTimer.php +++ b/vendor/skycaiji/app/admin/model/TaskTimer.php @@ -12,7 +12,6 @@ namespace skycaiji\admin\model; class TaskTimer extends \skycaiji\common\model\BaseModel{ - protected $tableName='task_timer'; public function addTimer($taskId,$timerData){ if(is_array($timerData)){ diff --git a/vendor/skycaiji/app/admin/view/backstage/bk_task_list.html b/vendor/skycaiji/app/admin/view/backstage/bk_task_list.html index 738d1db..253ed4c 100644 --- a/vendor/skycaiji/app/admin/view/backstage/bk_task_list.html +++ b/vendor/skycaiji/app/admin/view/backstage/bk_task_list.html @@ -15,7 +15,7 @@ {/if} - +

    diff --git a/vendor/skycaiji/app/admin/view/backstage/index.html b/vendor/skycaiji/app/admin/view/backstage/index.html index 1ef2c6d..dc71a9e 100644 --- a/vendor/skycaiji/app/admin/view/backstage/index.html +++ b/vendor/skycaiji/app/admin/view/backstage/index.html @@ -110,7 +110,7 @@ PHP版本 - {$serverData['php']} + {$serverData['php']} 上传限制 diff --git a/vendor/skycaiji/app/admin/view/collected/list.html b/vendor/skycaiji/app/admin/view/collected/list.html index a0b8df9..9868ce9 100644 --- a/vendor/skycaiji/app/admin/view/collected/list.html +++ b/vendor/skycaiji/app/admin/view/collected/list.html @@ -12,11 +12,11 @@
    {:html_usertoken()} -
    +
    - + @@ -46,7 +46,7 @@ - + diff --git a/vendor/skycaiji/app/admin/view/collector/export.html b/vendor/skycaiji/app/admin/view/collector/export.html index 811dfd9..10001ab 100644 --- a/vendor/skycaiji/app/admin/view/collector/export.html +++ b/vendor/skycaiji/app/admin/view/collector/export.html @@ -4,9 +4,9 @@ {block name="content"} {/block} \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/common/main.html b/vendor/skycaiji/app/admin/view/common/main.html index 532bcdb..173ac62 100644 --- a/vendor/skycaiji/app/admin/view/common/main.html +++ b/vendor/skycaiji/app/admin/view/common/main.html @@ -145,6 +145,7 @@
  • CMS发布插件
  • 函数插件
  • 应用程序
  • +
  • 插件编辑器
  • 文件管理
  • diff --git a/vendor/skycaiji/app/admin/view/cpattern/browser.html b/vendor/skycaiji/app/admin/view/cpattern/browser.html index 127ce16..53592aa 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/browser.html +++ b/vendor/skycaiji/app/admin/view/cpattern/browser.html @@ -5,7 +5,7 @@ diff --git a/vendor/skycaiji/app/admin/view/cpattern/content_sign.html b/vendor/skycaiji/app/admin/view/cpattern/content_sign.html index 32292c1..89554e6 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/content_sign.html +++ b/vendor/skycaiji/app/admin/view/cpattern/content_sign.html @@ -49,7 +49,7 @@ 表示当前内容标签的值 - ,请按函数传参,否则运行出错!扩展函数 + ,请按函数传参,否则运行出错!扩展函数 diff --git a/vendor/skycaiji/app/admin/view/cpattern/easymode.html b/vendor/skycaiji/app/admin/view/cpattern/easymode.html index bf0221f..bde1214 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/easymode.html +++ b/vendor/skycaiji/app/admin/view/cpattern/easymode.html @@ -9,6 +9,7 @@
    +
    @@ -34,4 +35,8 @@
    + {/block} \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/cpattern/field.html b/vendor/skycaiji/app/admin/view/cpattern/field.html index 92bd50e..03e9fb6 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/field.html +++ b/vendor/skycaiji/app/admin/view/cpattern/field.html @@ -29,8 +29,8 @@ - + @@ -108,8 +108,19 @@
    -
    分页分隔符
    +
    分页分隔符
    {if !empty($isLoop)} diff --git a/vendor/skycaiji/app/admin/view/cpattern/process_module.html b/vendor/skycaiji/app/admin/view/cpattern/process_module.html index 3ab4027..1f7a2dd 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/process_module.html +++ b/vendor/skycaiji/app/admin/view/cpattern/process_module.html @@ -87,31 +87,23 @@
    -
    -
    - - - - -
    - -
    -
    +
    +
    -
    {$Think.lang.select}{$Think.lang.select} 源网址 已发布至 任务名
    +
    - - - - + + + + @@ -195,8 +187,8 @@ -
    - +
    +
    @@ -230,7 +222,7 @@
    - + {foreach $transApiLangs as $k=>$v} {/foreach} @@ -305,7 +297,7 @@
    一行一个值, 可输入任何内容或 - 请按函数传参, 否则运行出错!扩展函数 + 请按函数传参, 否则运行出错!扩展函数
    @@ -318,22 +310,7 @@

    以/开头自动补全为{:config('root_website')}/

    - - -
    - -
    - +
    +
    + +
    +
    + +
    + +
    +
    +
    + + +
    +
    -
    -
    逻辑条件逻辑 条件操作
    +
    +
    + @@ -394,13 +403,13 @@
    -
    -
    名称 值 - 删除删除
    +
    +
    - + diff --git a/vendor/skycaiji/app/admin/view/cpattern/rule_module.html b/vendor/skycaiji/app/admin/view/cpattern/rule_module.html index c2edff1..df9f164 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/rule_module.html +++ b/vendor/skycaiji/app/admin/view/cpattern/rule_module.html @@ -14,7 +14,7 @@
    正则 - +
    @@ -32,12 +32,12 @@
    {elseif $_tpl_type=='xpath' /} @@ -47,34 +47,38 @@
    - - +
    +
    + +
    + +
    {elseif $_tpl_type=='json' /} @@ -97,7 +101,7 @@
    -
    +
    {/if} \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/cpattern/set.html b/vendor/skycaiji/app/admin/view/cpattern/set.html index 0f2cb76..a0cc0bc 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/set.html +++ b/vendor/skycaiji/app/admin/view/cpattern/set.html @@ -35,6 +35,23 @@ +
    + +
    +
    + +
    + +
    +
    @@ -44,7 +61,7 @@

    将所有页面源码中的相对地址转换成绝对地址(包含超链接、图片、JS链接等)

    - +
    @@ -65,7 +82,15 @@
    -

    需先配置页面渲染,可自动加载ajax内容,注意:渲染后的html源码与未渲染时的不相同,原先的规则可能会失效!

    +

    需先配置页面渲染,可自动加载ajax内容,注意:渲染后的html源码与未渲染时的不相同,html的变动可能会导致原来的规则失效!

    +
    +
    + +
    + + +
    +

    通常情况下网址中有锚点(#)和无锚点解析出的内容是相同的,为避免采集到重复内容可去除网址锚点

    @@ -135,8 +160,8 @@
    -
    -
    名称 删除删除
    +
    +
    @@ -173,16 +198,16 @@
    - - - + + +
    -

    默认使用采集器设置»请求头信息»抓取页面中的设置(受全局开启状态影响)

    +

    默认使用采集器设置»请求头信息»抓取页面中的配置(受全局开启状态影响)

    -
    -
    名称
    +
    +
    @@ -270,7 +295,7 @@ {if condition="!empty($collData['config'])&&!empty($collData['config']['front_urls'])"}
  • 测试抓取前置页
  • {/if} -
  • 测试分析网页
  • +
  • 测试分析网页
  • 模拟匹配数据
  • diff --git a/vendor/skycaiji/app/admin/view/cpattern/set_page.html b/vendor/skycaiji/app/admin/view/cpattern/set_page.html index 15dde12..32e3a4b 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/set_page.html +++ b/vendor/skycaiji/app/admin/view/cpattern/set_page.html @@ -27,11 +27,12 @@
    名称
    + +
    +
    @@ -69,18 +91,21 @@
    名称
    +
    - +
    + + + +
    +

    默认使用采集器设置»请求头信息»抓取页面中的配置(受全局开启状态影响)

    - - + +
    +
    @@ -91,6 +116,7 @@
    名称
    +

    添加新的或者覆盖已有的请求头信息

    @@ -98,6 +124,39 @@ +
    + + +
    +{if $_tpl_page_type!='test'}

    @@ -109,7 +168,7 @@
    @@ -118,6 +177,7 @@

    +{/if} {if $_tpl_page_type=='source_url'} {include file="cpattern:set_pagination" _page_type="source_url" /} diff --git a/vendor/skycaiji/app/admin/view/cpattern/set_page_rule.html b/vendor/skycaiji/app/admin/view/cpattern/set_page_rule.html index 94b05bc..6b41472 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/set_page_rule.html +++ b/vendor/skycaiji/app/admin/view/cpattern/set_page_rule.html @@ -41,7 +41,7 @@
    -
    +
  • xpath语法
  • @@ -102,7 +102,7 @@
    -
    +
  • xpath语法
  • diff --git a/vendor/skycaiji/app/admin/view/cpattern/set_pagination.html b/vendor/skycaiji/app/admin/view/cpattern/set_pagination.html index f6cdb83..9f533ab 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/set_pagination.html +++ b/vendor/skycaiji/app/admin/view/cpattern/set_pagination.html @@ -37,6 +37,24 @@

    填0表示不限制会自动循环抓取到最后一页,为防止出现无限循环的情况,最好设置一个数值以便采集到相应数量的分页后跳出循环抓取

    +
    + +
    + + + +
    +

    抓取{$_tpl_vars['title']}分页时发送请求,默认使用{$_tpl_vars['title']}»请求{$_tpl_vars['title']}网址的配置(受开启状态影响)

    +
    +
    + +
    + + + +
    +

    抓取{$_tpl_vars['title']}分页时执行页面渲染,默认使用{$_tpl_vars['title']}»执行页面渲染的配置(受开启状态影响)

    +
    diff --git a/vendor/skycaiji/app/admin/view/cpattern/set_tpl.html b/vendor/skycaiji/app/admin/view/cpattern/set_tpl.html index 71cad4b..90905fa 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/set_tpl.html +++ b/vendor/skycaiji/app/admin/view/cpattern/set_tpl.html @@ -42,6 +42,45 @@ + + + + + +
    + 元素 + +
    + + +
    + 内容 + + +
    + + +
    +
    +
    +
    + + diff --git a/vendor/skycaiji/app/admin/view/cpattern/test_front_urls.html b/vendor/skycaiji/app/admin/view/cpattern/test_front_urls.html index dcd148e..f74bbb7 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/test_front_urls.html +++ b/vendor/skycaiji/app/admin/view/cpattern/test_front_urls.html @@ -9,11 +9,11 @@ {foreach $frontDataList as $frontIx=>$frontData} - 前置页:{$frontData['name']} + 前置页:{$frontData['name']} - + diff --git a/vendor/skycaiji/app/admin/view/cpattern/test_input_url.html b/vendor/skycaiji/app/admin/view/cpattern/test_input_url.html index fe77cc0..0fa05e4 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/test_input_url.html +++ b/vendor/skycaiji/app/admin/view/cpattern/test_input_url.html @@ -6,9 +6,9 @@
    起始页
    - - {if $is_post_list['source_url']} -
    POST
    + + {if $pageOpenedList['source_url']} +
    {$pageOpenedList['source_url']}
    {/if}
    @@ -18,9 +18,9 @@
    多级页:{$level_url['name']}
    - - {if $is_post_list['level_url'][$level_url['name']]} -
    POST
    + + {if $pageOpenedList['level_url'][$level_url['name']]} +
    {$pageOpenedList['level_url'][$level_url['name']]}
    {/if}
    @@ -30,9 +30,9 @@
    内容页
    - - {if $is_post_list['url']} -
    POST
    + + {if $pageOpenedList['url']} +
    {$pageOpenedList['url']}
    {/if}
    @@ -40,6 +40,6 @@ {/if} -{if $is_post} - +{if $pageOpened} + {/if} \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/cpattern/test_match_ajax.html b/vendor/skycaiji/app/admin/view/cpattern/test_match_ajax.html index 6dfc613..0d3bf65 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/test_match_ajax.html +++ b/vendor/skycaiji/app/admin/view/cpattern/test_match_ajax.html @@ -1,141 +1,86 @@ +
    - - {:html_usertoken()} - -
    - - -
    -
    + + + + + + +
    + {:html_usertoken()} +
    - - + +
    -
    -
    网址网址 {$frontData['url']|htmlspecialchars}
    - - - - - - - - - -
    名称删除
    +
    - - + + +
    -
    - - - - - - - - - - - -
    名称删除
    -

    添加新的或者覆盖已有的请求头信息

    + +
    + {include file="cpattern:rule_module" _type="rule" _name="field" _name_pre="" _allow_loop="" /} +
    + + + + +
    +
    -
    -
    - - -
    - -
    - {include file="cpattern:rule_module" _type="rule" _name="field" _name_pre="" _allow_loop="" /} -
    - - - - -
    - -
    - - - - - - - - - - - - -
    +{include file="cpattern:set_tpl" /} + \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/cpattern/test_source_urls.html b/vendor/skycaiji/app/admin/view/cpattern/test_source_urls.html index 19926e8..18b740b 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/test_source_urls.html +++ b/vendor/skycaiji/app/admin/view/cpattern/test_source_urls.html @@ -27,14 +27,14 @@
  • [测试] [分析] - {$sourceUrlIsPost}{$url|htmlspecialchars} + {$sourceUrlOpened}{$url|htmlspecialchars}
  • {/foreach} {else /}
  • [测试] [分析] - {$sourceUrlIsPost}{$urls|htmlspecialchars} + {$sourceUrlOpened}{$urls|htmlspecialchars}
  • {/if} @@ -43,7 +43,7 @@ {else /} {foreach name="source_urls" item="source_url"}
    -

    抓取起始页面:{$sourceUrlIsPost}{$source_url|htmlspecialchars}

    +

    抓取起始页面:{$sourceUrlOpened}{$source_url|htmlspecialchars}

    diff --git a/vendor/skycaiji/app/admin/view/cpattern/test_test_url_ajax.html b/vendor/skycaiji/app/admin/view/cpattern/test_test_url_ajax.html index b944454..764fb43 100644 --- a/vendor/skycaiji/app/admin/view/cpattern/test_test_url_ajax.html +++ b/vendor/skycaiji/app/admin/view/cpattern/test_test_url_ajax.html @@ -25,7 +25,7 @@ body>.wrapper>.content-wrapper{transition:none;} {/foreach}
    -
    POST
    +
    {$urlOpened}
    diff --git a/vendor/skycaiji/app/admin/view/develop/app.html b/vendor/skycaiji/app/admin/view/develop/app.html index 5a1cec7..3951792 100644 --- a/vendor/skycaiji/app/admin/view/develop/app.html +++ b/vendor/skycaiji/app/admin/view/develop/app.html @@ -93,7 +93,7 @@
    - +
    diff --git a/vendor/skycaiji/app/admin/view/develop/editor.html b/vendor/skycaiji/app/admin/view/develop/editor.html new file mode 100644 index 0000000..dd8e2cf --- /dev/null +++ b/vendor/skycaiji/app/admin/view/develop/editor.html @@ -0,0 +1,73 @@ +{extend name="common:main" /} +{block name="cssjs"} +{/block} +{block name="content"} +{if !$isApp} + +{else /} + +
    +
    + +
    + +
    +
    + +
    +
    + +{/if} +{/block} \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/develop/editor_code.html b/vendor/skycaiji/app/admin/view/develop/editor_code.html new file mode 100644 index 0000000..8fd1595 --- /dev/null +++ b/vendor/skycaiji/app/admin/view/develop/editor_code.html @@ -0,0 +1,64 @@ +{extend name="common:main_clean" /} +{block name="cssjs"} + + + + + + + + + + + + + + + + + + + + + +{/block} +{block name="content"} +
    + +
    + +{/block} \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/develop/func.html b/vendor/skycaiji/app/admin/view/develop/func.html index 6151365..dd084a6 100644 --- a/vendor/skycaiji/app/admin/view/develop/func.html +++ b/vendor/skycaiji/app/admin/view/develop/func.html @@ -7,12 +7,12 @@
    {:html_usertoken()} - {if !empty($funcClass)} + {if !empty($funcClass['filename'])}
    - 请在 {$funcClass['filename']} 文件中编辑代码 + 请在 {$funcClass['filename']} 文件中编辑代码 或者 使用插件编辑器
    - {if !empty($funcClass)} + {if !empty($funcClass['filename'])}
      diff --git a/vendor/skycaiji/app/admin/view/develop/releaseCms.html b/vendor/skycaiji/app/admin/view/develop/releaseCms.html index 69c6f91..9ce51b6 100644 --- a/vendor/skycaiji/app/admin/view/develop/releaseCms.html +++ b/vendor/skycaiji/app/admin/view/develop/releaseCms.html @@ -13,9 +13,10 @@ {:lang('release_upgrade')}
    {else/} + {if !$noClass}
    - 请在 {$config['app_file']} 文件中编辑代码 + 请在 {$config['app_file']} 文件中编辑代码 或者 使用插件编辑器
    {/if} + {/if} {/if} @@ -35,22 +37,24 @@

    精简描述该插件

    -
    +
    - - -
    +
    +
    + +
    + +
    +

    自定义时请输入CMS程序的英文全称

    +
    diff --git a/vendor/skycaiji/app/admin/view/mystore/func.html b/vendor/skycaiji/app/admin/view/mystore/func.html index 0def055..a995586 100644 --- a/vendor/skycaiji/app/admin/view/mystore/func.html +++ b/vendor/skycaiji/app/admin/view/mystore/func.html @@ -3,7 +3,7 @@ {/block} {block name="content"} -{php}$orderClass=array($orderKey=>'_'.$sortBy);$orderSort=array($orderKey=>($sortBy=='asc'?'升序':'倒序'));{/php} +{php}$orderClass=array($orderKey=>'_'.$sortBy);$orderSort=array($orderKey=>($sortBy=='asc'?'升序':'降序'));{/php}
    程序操作
    - + @@ -62,7 +62,7 @@ - +
    {$Think.lang.select}{$Think.lang.select} 名称 类和方法 模块
     开发一个 diff --git a/vendor/skycaiji/app/admin/view/mystore/func_method.html b/vendor/skycaiji/app/admin/view/mystore/func_method.html index 561343c..470eeda 100644 --- a/vendor/skycaiji/app/admin/view/mystore/func_method.html +++ b/vendor/skycaiji/app/admin/view/mystore/func_method.html @@ -1,5 +1,11 @@ -
    -{$methodData['comment']|htmlspecialchars}
    -
    -{$methodData['code']|htmlspecialchars} -
    \ No newline at end of file + + + \ No newline at end of file diff --git a/vendor/skycaiji/app/admin/view/mystore/release_app.html b/vendor/skycaiji/app/admin/view/mystore/release_app.html index 6881211..ccb8552 100644 --- a/vendor/skycaiji/app/admin/view/mystore/release_app.html +++ b/vendor/skycaiji/app/admin/view/mystore/release_app.html @@ -3,7 +3,7 @@ {/block} {block name="content"} -{php}$orderClass=array($orderKey=>'_'.$sortBy);$orderSort=array($orderKey=>($sortBy=='asc'?'升序':'倒序'));{/php} +{php}$orderClass=array($orderKey=>'_'.$sortBy);$orderSort=array($orderKey=>($sortBy=='asc'?'升序':'降序'));{/php}