1
1
Fork 0
mirror of https://github.com/boxgaming/qbjs.git synced 2024-05-12 08:00:12 +00:00

added support for search and export

This commit is contained in:
boxgaming 2022-04-18 11:25:08 -05:00
parent 72a3757f01
commit b819f79cc9
9 changed files with 1033 additions and 54 deletions

72
codemirror/active-line.js Normal file
View file

@ -0,0 +1,72 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
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);
}
});

View file

@ -0,0 +1,128 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineExtension("annotateScrollbar", function(options) {
if (typeof options == "string") options = {className: options};
return new Annotation(this, options);
});
CodeMirror.defineOption("scrollButtonHeight", 0);
function Annotation(cm, options) {
this.cm = cm;
this.options = options;
this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
this.annotations = [];
this.doRedraw = this.doUpdate = null;
this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
this.computeScale();
function scheduleRedraw(delay) {
clearTimeout(self.doRedraw);
self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
}
var self = this;
cm.on("refresh", this.resizeHandler = function() {
clearTimeout(self.doUpdate);
self.doUpdate = setTimeout(function() {
if (self.computeScale()) scheduleRedraw(20);
}, 100);
});
cm.on("markerAdded", this.resizeHandler);
cm.on("markerCleared", this.resizeHandler);
if (options.listenForChanges !== false)
cm.on("changes", this.changeHandler = function() {
scheduleRedraw(250);
});
}
Annotation.prototype.computeScale = function() {
var cm = this.cm;
var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
cm.getScrollerElement().scrollHeight
if (hScale != this.hScale) {
this.hScale = hScale;
return true;
}
};
Annotation.prototype.update = function(annotations) {
this.annotations = annotations;
this.redraw();
};
Annotation.prototype.redraw = function(compute) {
if (compute !== false) this.computeScale();
var cm = this.cm, hScale = this.hScale;
var frag = document.createDocumentFragment(), anns = this.annotations;
var wrapping = cm.getOption("lineWrapping");
var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
var curLine = null, curLineObj = null;
function getY(pos, top) {
if (curLine != pos.line) {
curLine = pos.line
curLineObj = cm.getLineHandle(pos.line)
var visual = cm.getLineHandleVisualStart(curLineObj)
if (visual != curLineObj) {
curLine = cm.getLineNumber(visual)
curLineObj = visual
}
}
if ((curLineObj.widgets && curLineObj.widgets.length) ||
(wrapping && curLineObj.height > singleLineH))
return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
var topY = cm.heightAtLine(curLineObj, "local");
return topY + (top ? 0 : curLineObj.height);
}
var lastLine = cm.lastLine()
if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
var ann = anns[i];
if (ann.to.line > lastLine) continue;
var top = nextTop || getY(ann.from, true) * hScale;
var bottom = getY(ann.to, false) * hScale;
while (i < anns.length - 1) {
if (anns[i + 1].to.line > lastLine) break;
nextTop = getY(anns[i + 1].from, true) * hScale;
if (nextTop > bottom + .9) break;
ann = anns[++i];
bottom = getY(ann.to, false) * hScale;
}
if (bottom == top) continue;
var height = Math.max(bottom - top, 3);
var elt = frag.appendChild(document.createElement("div"));
elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
+ (top + this.buttonHeight) + "px; height: " + height + "px";
elt.className = this.options.className;
if (ann.id) {
elt.setAttribute("annotation-id", ann.id);
}
}
this.div.textContent = "";
this.div.appendChild(frag);
};
Annotation.prototype.clear = function() {
this.cm.off("refresh", this.resizeHandler);
this.cm.off("markerAdded", this.resizeHandler);
this.cm.off("markerCleared", this.resizeHandler);
if (this.changeHandler) this.cm.off("changes", this.changeHandler);
this.div.parentNode.removeChild(this.div);
};
});

View file

@ -0,0 +1,8 @@
.CodeMirror-search-match {
background: gold;
border-top: 1px solid orange;
border-bottom: 1px solid orange;
-moz-box-sizing: border-box;
box-sizing: border-box;
opacity: .5;
}

View file

@ -0,0 +1,97 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) {
if (typeof options == "string") options = {className: options};
if (!options) options = {};
return new SearchAnnotation(this, query, caseFold, options);
});
function SearchAnnotation(cm, query, caseFold, options) {
this.cm = cm;
this.options = options;
var annotateOptions = {listenForChanges: false};
for (var prop in options) annotateOptions[prop] = options[prop];
if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
this.annotation = cm.annotateScrollbar(annotateOptions);
this.query = query;
this.caseFold = caseFold;
this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};
this.matches = [];
this.update = null;
this.findMatches();
this.annotation.update(this.matches);
var self = this;
cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); });
}
var MAX_MATCHES = 1000;
SearchAnnotation.prototype.findMatches = function() {
if (!this.gap) return;
for (var i = 0; i < this.matches.length; i++) {
var match = this.matches[i];
if (match.from.line >= this.gap.to) break;
if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
}
var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline});
var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
while (cursor.findNext()) {
var match = {from: cursor.from(), to: cursor.to()};
if (match.from.line >= this.gap.to) break;
this.matches.splice(i++, 0, match);
if (this.matches.length > maxMatches) break;
}
this.gap = null;
};
function offsetLine(line, changeStart, sizeChange) {
if (line <= changeStart) return line;
return Math.max(changeStart, line + sizeChange);
}
SearchAnnotation.prototype.onChange = function(change) {
var startLine = change.from.line;
var endLine = CodeMirror.changeEnd(change).line;
var sizeChange = endLine - change.to.line;
if (this.gap) {
this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);
this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);
} else {
this.gap = {from: change.from.line, to: endLine + 1};
}
if (sizeChange) for (var i = 0; i < this.matches.length; i++) {
var match = this.matches[i];
var newFrom = offsetLine(match.from.line, startLine, sizeChange);
if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);
var newTo = offsetLine(match.to.line, startLine, sizeChange);
if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);
}
clearTimeout(this.update);
var self = this;
this.update = setTimeout(function() { self.updateAfterChange(); }, 250);
};
SearchAnnotation.prototype.updateAfterChange = function() {
this.findMatches();
this.annotation.update(this.matches);
};
SearchAnnotation.prototype.clear = function() {
this.cm.off("change", this.changeHandler);
this.annotation.clear();
};
});

View file

@ -3,47 +3,50 @@
src: url(lp-dosvga.ttf);
} */
.cm-s-lesser-dark {
.cm-s-qbjs {
line-height: 1em;
}
.cm-s-lesser-dark.CodeMirror { background: rgb(0, 0, 39); color: rgb(216, 216, 216); text-shadow: 0 -1px 1px #262626; font-family: dosvga; /*letter-spacing: -1px*/}
.cm-s-lesser-dark div.CodeMirror-selected { background: #45443B; } /* 33322B*/
.cm-s-lesser-dark .CodeMirror-line::selection, .cm-s-lesser-dark .CodeMirror-line > span::selection, .cm-s-lesser-dark .CodeMirror-line > span > span::selection { background: rgba(0, 49, 78, .99); }
.cm-s-lesser-dark .CodeMirror-line::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span::-moz-selection, .cm-s-lesser-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); }
.cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid #fff; }
.cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/
.cm-s-qbjs.CodeMirror { background: rgb(0, 0, 39); color: rgb(216, 216, 216); text-shadow: 0 -1px 1px #262626; font-family: dosvga; /*letter-spacing: -1px*/}
.cm-s-qbjs div.CodeMirror-selected { background: #45443B; } /* 33322B*/
.cm-s-qbjs .CodeMirror-line::selection, .cm-s-qbjs .CodeMirror-line > span::selection, .cm-s-qbjs .CodeMirror-line > span > span::selection { background: rgba(0, 49, 78, .99); }
.cm-s-qbjs .CodeMirror-line::-moz-selection, .cm-s-qbjs .CodeMirror-line > span::-moz-selection, .cm-s-qbjs .CodeMirror-line > span > span::-moz-selection { background: rgba(69, 68, 59, .99); }
.cm-s-qbjs .CodeMirror-cursor { border-left: 1px solid #fff; }
.cm-s-qbjs pre { padding: 0 8px; }/*editable code holder*/
.cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/
.cm-s-qbjs.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/
.cm-s-lesser-dark .CodeMirror-gutters { background: rgb(0, 49, 78); border-right:1px solid #aaa; }
.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; }
.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; }
.cm-s-lesser-dark .CodeMirror-linenumber { color: rgb(175, 175, 175); }
.cm-s-qbjs .CodeMirror-gutters { background: rgb(0, 49, 78); border-right:1px solid #aaa; }
.cm-s-qbjs .CodeMirror-guttermarker { color: #599eff; }
.cm-s-qbjs .CodeMirror-guttermarker-subtle { color: #777; }
.cm-s-qbjs .CodeMirror-linenumber { color: rgb(175, 175, 175); }
.cm-s-lesser-dark span.cm-header { color: #a0a; }
.cm-s-lesser-dark span.cm-quote { color: #090; }
.cm-s-lesser-dark span.cm-keyword { color: rgb(69, 118, 147); }
.cm-s-lesser-dark span.cm-atom { color: #C2B470; }
.cm-s-lesser-dark span.cm-number { color: rgb(216, 98, 78); }
.cm-s-lesser-dark span.cm-def { color: white; }
.cm-s-lesser-dark span.cm-variable { color:rgb(216, 216, 216); }
.cm-s-lesser-dark span.cm-variable-2 { color: #669199; }
.cm-s-lesser-dark span.cm-variable-3, .cm-s-lesser-dark span.cm-type { color: white; }
.cm-s-lesser-dark span.cm-property { color: #92A75C; }
.cm-s-lesser-dark span.cm-operator { color: #92A75C; }
.cm-s-lesser-dark span.cm-comment { color: rgb(98, 98, 98); }
.cm-s-lesser-dark span.cm-string { color: rgb(255, 167, 0); }
.cm-s-lesser-dark span.cm-string-2 { color: #f50; }
.cm-s-lesser-dark span.cm-meta { color: #738C73; }
.cm-s-lesser-dark span.cm-qualifier { color: #555; }
.cm-s-lesser-dark span.cm-builtin { color: rgb(69, 118, 147); }
.cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; }
.cm-s-lesser-dark span.cm-tag { color: #669199; }
.cm-s-lesser-dark span.cm-attribute { color: #81a4d5; }
.cm-s-lesser-dark span.cm-hr { color: #999; }
.cm-s-lesser-dark span.cm-link { color: #7070E6; }
/*.cm-s-lesser-dark span.cm-error { color: #9d1e15; }*/
.cm-s-lesser-dark span.cm-error { color: #999; }
.cm-s-qbjs span.cm-header { color: #a0a; }
.cm-s-qbjs span.cm-quote { color: #090; }
.cm-s-qbjs span.cm-keyword { color: rgb(69, 118, 147); }
.cm-s-qbjs span.cm-atom { color: #C2B470; }
.cm-s-qbjs span.cm-number { color: rgb(216, 98, 78); }
.cm-s-qbjs span.cm-def { color: white; }
.cm-s-qbjs span.cm-variable { color:rgb(216, 216, 216); }
.cm-s-qbjs span.cm-variable-2 { color: #669199; }
.cm-s-qbjs span.cm-variable-3, .cm-s-qbjs span.cm-type { color: white; }
.cm-s-qbjs span.cm-property { color: #92A75C; }
.cm-s-qbjs span.cm-operator { color: #92A75C; }
.cm-s-qbjs span.cm-comment { color: rgb(98, 98, 98); }
.cm-s-qbjs span.cm-string { color: rgb(255, 167, 0); }
.cm-s-qbjs span.cm-string-2 { color: #f50; }
.cm-s-qbjs span.cm-meta { color: #738C73; }
.cm-s-qbjs span.cm-qualifier { color: #555; }
.cm-s-qbjs span.cm-builtin { color: rgb(69, 118, 147); }
.cm-s-qbjs span.cm-bracket { color: #EBEFE7; }
.cm-s-qbjs span.cm-tag { color: #669199; }
.cm-s-qbjs span.cm-attribute { color: #81a4d5; }
.cm-s-qbjs span.cm-hr { color: #999; }
.cm-s-qbjs span.cm-link { color: #7070E6; }
/*.cm-s-qbjs span.cm-error { color: #9d1e15; }*/
.cm-s-qbjs span.cm-error { color: #999; }
.cm-s-lesser-dark .CodeMirror-activeline-background { background: rgb(0, 49, 78); }
.cm-s-lesser-dark .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
.cm-s-qbjs .CodeMirror-activeline-background { background: rgb(0, 49, 78); }
.cm-s-qbjs .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
.cm-s-qbjs .CodeMirror-search-field { font-family: dosvga; font-size: 1em; }
.cm-s-qbjs .cm-searching { color: #333 !important; background-color: #ccc; }

View file

@ -23,8 +23,8 @@ CodeMirror.defineMode("qbjs", function(conf, parserConf) {
var brakets = new RegExp('^[\\(\\)]');
var identifiers = new RegExp("^[A-Za-z][_A-Za-z0-9]*");
var openingKeywords = ['sub','select','while','if','function', 'property', 'with', 'for', 'type'];
var middleKeywords = ['else','elseif','case'];
var openingKeywords = ['sub','select','while','if','function', 'property', 'with', 'for', 'type\\s'];
var middleKeywords = ['else','elseif','case','then'];
var endKeywords = ['next','loop','wend'];
var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'is', 'mod', 'eqv', 'imp']);
@ -45,8 +45,8 @@ CodeMirror.defineMode("qbjs", function(conf, parserConf) {
'print', 'pset', 'right', 'rtrim', 'rnd', 'screen', 'shared', 'sgn', 'sin', 'sleep', 'space', 'sqr', 'str', 'swap', 'tan',
'timer', 'ubound', 'ucase', 'val',
// QBJS-specific
'alert', 'confirm', 'domadd', 'domcontainer', 'domcreate', 'domevent','domget', 'domgetimage', 'domremove', 'prompt',
'storageclear', 'storageget', 'storagekey', 'storagelength', 'storageset', 'storageremove'];
'alert', 'confirm', 'domadd', 'domcontainer', 'domcreate', 'domevent','domget', 'domgetimage', 'domremove', 'export',
'from', 'prompt', 'import', 'storageclear', 'storageget', 'storagekey', 'storagelength', 'storageset', 'storageremove'];
var builtinConsts = ['gx_true', 'gx_false', 'gxevent_init', 'gxevent_update', 'gxevent_drawbg', 'gxevent_drawmap', 'gxevent_drawscreen',
'gxevent_mouseinput', 'gxevent_paintbefore', 'gxevent_paintafter', 'gxevent_collision_tile', 'gxevent_collision_entity',
@ -72,7 +72,7 @@ CodeMirror.defineMode("qbjs", function(conf, parserConf) {
'gxdevice_axis', 'gxdevice_wheel', 'gxtype_entity', 'gxtype_font', 'gx_cr', 'gx_lf', 'gx_crlf',
'local', 'session'];
var builtinObjsWords = ['\\$if', '\\$end if'];
var builtinObjsWords = ['\\$if', '\\$end if', '\\$touchmouse'];
// TODO: adjust for QB
var knownProperties = ['description', 'firstindex', 'global', 'helpcontext', 'helpfile', 'ignorecase', 'length', 'number', 'pattern', 'source', 'value', 'count'];

292
codemirror/search.js Normal file
View file

@ -0,0 +1,292 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.
// Replace works a little oddly -- it will do the replace on the next
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
// replace by making sure the match is no longer selected when hitting
// Ctrl-G.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
// default search panel location
CodeMirror.defineOption("search", {bottom: false});
function searchOverlay(query, caseInsensitive) {
if (typeof query == "string")
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
else if (!query.global)
query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
return {token: function(stream) {
query.lastIndex = stream.pos;
var match = query.exec(stream.string);
if (match && match.index == stream.pos) {
stream.pos += match[0].length || 1;
return "searching";
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
}};
}
function SearchState() {
this.posFrom = this.posTo = this.lastQuery = this.query = null;
this.overlay = null;
}
function getSearchState(cm) {
return cm.state.search || (cm.state.search = new SearchState());
}
function queryCaseInsensitive(query) {
return typeof query == "string" && query == query.toLowerCase();
}
function getSearchCursor(cm, query, pos) {
// Heuristic: if the query string is all lowercase, do a case insensitive search.
return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
}
function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
cm.openDialog(text, onEnter, {
value: deflt,
selectValueOnOpen: true,
closeOnEnter: false,
onClose: function() { clearSearch(cm); },
onKeyDown: onKeyDown,
bottom: cm.options.search.bottom
});
}
function dialog(cm, text, shortText, deflt, f) {
if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true, bottom: cm.options.search.bottom});
else f(prompt(shortText, deflt));
}
function confirmDialog(cm, text, shortText, fs) {
if (cm.openConfirm) cm.openConfirm(text, fs);
else if (confirm(shortText)) fs[0]();
}
function parseString(string) {
return string.replace(/\\([nrt\\])/g, function(match, ch) {
if (ch == "n") return "\n"
if (ch == "r") return "\r"
if (ch == "t") return "\t"
if (ch == "\\") return "\\"
return match
})
}
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
catch(e) {} // Not a regular expression after all, do a string search
} else {
query = parseString(query)
}
if (typeof query == "string" ? query == "" : query.test(""))
query = /x^/;
return query;
}
function startSearch(cm, state, query) {
state.queryText = query;
state.query = parseQuery(query);
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
cm.addOverlay(state.overlay);
if (cm.showMatchesOnScrollbar) {
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
}
}
function doSearch(cm, rev, persistent, immediate) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
var q = cm.getSelection() || state.lastQuery;
if (q instanceof RegExp && q.source == "x^") q = null
if (persistent && cm.openDialog) {
var hiding = null
var searchNext = function(query, event) {
CodeMirror.e_stop(event);
if (!query) return;
if (query != state.queryText) {
startSearch(cm, state, query);
state.posFrom = state.posTo = cm.getCursor();
}
if (hiding) hiding.style.opacity = 1
findNext(cm, event.shiftKey, function(_, to) {
var dialog
if (to.line < 3 && document.querySelector &&
(dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
(hiding = dialog).style.opacity = .4
})
};
persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) {
var keyName = CodeMirror.keyName(event)
var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
if (cmd == "findNext" || cmd == "findPrev" ||
cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
CodeMirror.e_stop(event);
startSearch(cm, getSearchState(cm), query);
cm.execCommand(cmd);
} else if (cmd == "find" || cmd == "findPersistent") {
CodeMirror.e_stop(event);
searchNext(query, event);
}
});
if (immediate && q) {
startSearch(cm, state, q);
findNext(cm, rev);
}
} else {
dialog(cm, getQueryDialog(cm), "Search for:", q, function(query) {
if (query && !state.query) cm.operation(function() {
startSearch(cm, state, query);
state.posFrom = state.posTo = cm.getCursor();
findNext(cm, rev);
});
});
}
}
function findNext(cm, rev, callback) {cm.operation(function() {
var state = getSearchState(cm);
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) {
cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
if (!cursor.find(rev)) return;
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
state.posFrom = cursor.from(); state.posTo = cursor.to();
if (callback) callback(cursor.from(), cursor.to())
});}
function clearSearch(cm) {cm.operation(function() {
var state = getSearchState(cm);
state.lastQuery = state.query;
if (!state.query) return;
state.query = state.queryText = null;
cm.removeOverlay(state.overlay);
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
});}
function el(tag, attrs) {
var element = tag ? document.createElement(tag) : document.createDocumentFragment();
for (var key in attrs) {
element[key] = attrs[key];
}
for (var i = 2; i < arguments.length; i++) {
var child = arguments[i]
element.appendChild(typeof child == "string" ? document.createTextNode(child) : child);
}
return element;
}
function getQueryDialog(cm) {
return el("", null,
el("span", {className: "CodeMirror-search-label"}, cm.phrase("Search:")), " ",
el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"}), " ",
el("span", {style: "color: #888", className: "CodeMirror-search-hint"},
cm.phrase("(Use /re/ syntax for regexp search)")));
}
function getReplaceQueryDialog(cm) {
return el("", null, " ",
el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"}), " ",
el("span", {style: "color: #888", className: "CodeMirror-search-hint"},
cm.phrase("(Use /re/ syntax for regexp search)")));
}
function getReplacementQueryDialog(cm) {
return el("", null,
el("span", {className: "CodeMirror-search-label"}, cm.phrase("With:")), " ",
el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"}));
}
function getDoReplaceConfirm(cm) {
return el("", null,
el("span", {className: "CodeMirror-search-label"}, cm.phrase("Replace?")), " ",
el("button", {}, cm.phrase("Yes")), " ",
el("button", {}, cm.phrase("No")), " ",
el("button", {}, cm.phrase("All")), " ",
el("button", {}, cm.phrase("Stop")));
}
function replaceAll(cm, query, text) {
cm.operation(function() {
for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
if (typeof query != "string") {
var match = cm.getRange(cursor.from(), cursor.to()).match(query);
cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
} else cursor.replace(text);
}
});
}
function replace(cm, all) {
if (cm.getOption("readOnly")) return;
var query = cm.getSelection() || getSearchState(cm).lastQuery;
var dialogText = all ? cm.phrase("Replace all:") : cm.phrase("Replace:")
var fragment = el("", null,
el("span", {className: "CodeMirror-search-label"}, dialogText),
getReplaceQueryDialog(cm))
dialog(cm, fragment, dialogText, query, function(query) {
if (!query) return;
query = parseQuery(query);
dialog(cm, getReplacementQueryDialog(cm), cm.phrase("Replace with:"), "", function(text) {
text = parseString(text)
if (all) {
replaceAll(cm, query, text)
} else {
clearSearch(cm);
var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
var advance = function() {
var start = cursor.from(), match;
if (!(match = cursor.findNext())) {
cursor = getSearchCursor(cm, query);
if (!(match = cursor.findNext()) ||
(start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase("Replace?"),
[function() {doReplace(match);}, advance,
function() {replaceAll(cm, query, text)}]);
};
var doReplace = function(match) {
cursor.replace(typeof query == "string" ? text :
text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
advance();
};
advance();
}
});
});
}
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
CodeMirror.commands.findNext = doSearch;
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
CodeMirror.commands.clearSearch = clearSearch;
CodeMirror.commands.replace = replace;
CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
});

305
codemirror/searchcursor.js Normal file
View file

@ -0,0 +1,305 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"))
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod)
else // Plain browser env
mod(CodeMirror)
})(function(CodeMirror) {
"use strict"
var Pos = CodeMirror.Pos
function regexpFlags(regexp) {
var flags = regexp.flags
return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
+ (regexp.global ? "g" : "")
+ (regexp.multiline ? "m" : "")
}
function ensureFlags(regexp, flags) {
var current = regexpFlags(regexp), target = current
for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
target += flags.charAt(i)
return current == target ? regexp : new RegExp(regexp.source, target)
}
function maybeMultiline(regexp) {
return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
}
function searchRegexpForward(doc, regexp, start) {
regexp = ensureFlags(regexp, "g")
for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
regexp.lastIndex = ch
var string = doc.getLine(line), match = regexp.exec(string)
if (match)
return {from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
match: match}
}
}
function searchRegexpForwardMultiline(doc, regexp, start) {
if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
regexp = ensureFlags(regexp, "gm")
var string, chunk = 1
for (var line = start.line, last = doc.lastLine(); line <= last;) {
// This grows the search buffer in exponentially-sized chunks
// between matches, so that nearby matches are fast and don't
// require concatenating the whole document (in case we're
// searching for something that has tons of matches), but at the
// same time, the amount of retries is limited.
for (var i = 0; i < chunk; i++) {
if (line > last) break
var curLine = doc.getLine(line++)
string = string == null ? curLine : string + "\n" + curLine
}
chunk = chunk * 2
regexp.lastIndex = start.ch
var match = regexp.exec(string)
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
return {from: Pos(startLine, startCh),
to: Pos(startLine + inside.length - 1,
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
match: match}
}
}
}
function lastMatchIn(string, regexp, endMargin) {
var match, from = 0
while (from <= string.length) {
regexp.lastIndex = from
var newMatch = regexp.exec(string)
if (!newMatch) break
var end = newMatch.index + newMatch[0].length
if (end > string.length - endMargin) break
if (!match || end > match.index + match[0].length)
match = newMatch
from = newMatch.index + 1
}
return match
}
function searchRegexpBackward(doc, regexp, start) {
regexp = ensureFlags(regexp, "g")
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
var string = doc.getLine(line)
var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch)
if (match)
return {from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
match: match}
}
}
function searchRegexpBackwardMultiline(doc, regexp, start) {
if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start)
regexp = ensureFlags(regexp, "gm")
var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch
for (var line = start.line, first = doc.firstLine(); line >= first;) {
for (var i = 0; i < chunkSize && line >= first; i++) {
var curLine = doc.getLine(line--)
string = string == null ? curLine : curLine + "\n" + string
}
chunkSize *= 2
var match = lastMatchIn(string, regexp, endMargin)
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
var startLine = line + before.length, startCh = before[before.length - 1].length
return {from: Pos(startLine, startCh),
to: Pos(startLine + inside.length - 1,
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
match: match}
}
}
}
var doFold, noFold
if (String.prototype.normalize) {
doFold = function(str) { return str.normalize("NFD").toLowerCase() }
noFold = function(str) { return str.normalize("NFD") }
} else {
doFold = function(str) { return str.toLowerCase() }
noFold = function(str) { return str }
}
// Maps a position in a case-folded line back to a position in the original line
// (compensating for codepoints increasing in number during folding)
function adjustPos(orig, folded, pos, foldFunc) {
if (orig.length == folded.length) return pos
for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
if (min == max) return min
var mid = (min + max) >> 1
var len = foldFunc(orig.slice(0, mid)).length
if (len == pos) return mid
else if (len > pos) max = mid
else min = mid + 1
}
}
function searchStringForward(doc, query, start, caseFold) {
// Empty string would match anything and never progress, so we
// define it to match nothing instead.
if (!query.length) return null
var fold = caseFold ? doFold : noFold
var lines = fold(query).split(/\r|\n\r?/)
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
var orig = doc.getLine(line).slice(ch), string = fold(orig)
if (lines.length == 1) {
var found = string.indexOf(lines[0])
if (found == -1) continue search
var start = adjustPos(orig, string, found, fold) + ch
return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
} else {
var cutFrom = string.length - lines[0].length
if (string.slice(cutFrom) != lines[0]) continue search
for (var i = 1; i < lines.length - 1; i++)
if (fold(doc.getLine(line + i)) != lines[i]) continue search
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
if (endString.slice(0, lastLine.length) != lastLine) continue search
return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
}
}
}
function searchStringBackward(doc, query, start, caseFold) {
if (!query.length) return null
var fold = caseFold ? doFold : noFold
var lines = fold(query).split(/\r|\n\r?/)
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
var orig = doc.getLine(line)
if (ch > -1) orig = orig.slice(0, ch)
var string = fold(orig)
if (lines.length == 1) {
var found = string.lastIndexOf(lines[0])
if (found == -1) continue search
return {from: Pos(line, adjustPos(orig, string, found, fold)),
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
} else {
var lastLine = lines[lines.length - 1]
if (string.slice(0, lastLine.length) != lastLine) continue search
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
if (fold(doc.getLine(start + i)) != lines[i]) continue search
var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
}
}
}
function SearchCursor(doc, query, pos, options) {
this.atOccurrence = false
this.afterEmptyMatch = false
this.doc = doc
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
this.pos = {from: pos, to: pos}
var caseFold
if (typeof options == "object") {
caseFold = options.caseFold
} else { // Backwards compat for when caseFold was the 4th argument
caseFold = options
options = null
}
if (typeof query == "string") {
if (caseFold == null) caseFold = false
this.matches = function(reverse, pos) {
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
}
} else {
query = ensureFlags(query, "gm")
if (!options || options.multiline !== false)
this.matches = function(reverse, pos) {
return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
}
else
this.matches = function(reverse, pos) {
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
}
}
}
SearchCursor.prototype = {
findNext: function() {return this.find(false)},
findPrevious: function() {return this.find(true)},
find: function(reverse) {
var head = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
if (this.afterEmptyMatch && this.atOccurrence) {
// do not return the same 0 width match twice
head = Pos(head.line, head.ch)
if (reverse) {
head.ch--;
if (head.ch < 0) {
head.line--;
head.ch = (this.doc.getLine(head.line) || "").length;
}
} else {
head.ch++;
if (head.ch > (this.doc.getLine(head.line) || "").length) {
head.ch = 0;
head.line++;
}
}
if (CodeMirror.cmpPos(head, this.doc.clipPos(head)) != 0) {
return this.atOccurrence = false
}
}
var result = this.matches(reverse, head)
this.afterEmptyMatch = result && CodeMirror.cmpPos(result.from, result.to) == 0
if (result) {
this.pos = result
this.atOccurrence = true
return this.pos.match || true
} else {
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
this.pos = {from: end, to: end}
return this.atOccurrence = false
}
},
from: function() {if (this.atOccurrence) return this.pos.from},
to: function() {if (this.atOccurrence) return this.pos.to},
replace: function(newText, origin) {
if (!this.atOccurrence) return
var lines = CodeMirror.splitLines(newText)
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
}
}
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this.doc, query, pos, caseFold)
})
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
return new SearchCursor(this, query, pos, caseFold)
})
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
var ranges = []
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
while (cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
ranges.push({anchor: cur.from(), head: cur.to()})
}
if (ranges.length)
this.setSelections(ranges, 0)
})
});

View file

@ -1,6 +1,7 @@
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@font-face {
font-family: dosvga;
@ -25,7 +26,6 @@
top: 10px;
}
#code {
/*width: 600px;*/
margin-bottom: 5px;
border: 1px solid #666;
}
@ -163,13 +163,31 @@
#share-dialog a { color: #333; }
#share-dialog a:hover { color: #000; }
#logo {
position: absolute;
right: 1px;
top: 1px;
cursor: pointer;
opacity: 0.5;
}
#logo:hover { opacity: 1.0; }
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/codemirror.min.css"></link>
<link rel="stylesheet" href="codemirror/codemirror.min.css"></link>
<link rel="stylesheet" href="codemirror/dialog.css">
<link rel="stylesheet" href="codemirror/matchesonscrollbar.css">
<link rel="stylesheet" href="codemirror/qb-ide.css"></link>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/codemirror.min.js"></script>
<script type="text/javascript" src="codemirror/codemirror.min.js"></script>
<script type="text/javascript" src="codemirror/qb-lang.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.52.2/addon/selection/active-line.js"></script>
<script type="text/javascript" src="codemirror/active-line.js"></script>
<script type="text/javascript" src="util/shorty.min.js"></script>
<script type="text/javascript" src="util/jszip.min.js"></script>
<script type="text/javascript" src="codemirror/dialog.js"></script>
<script type="text/javascript" src="codemirror/searchcursor.js"></script>
<script type="text/javascript" src="codemirror/search.js"></script>
<script type="text/javascript" src="codemirror/annotatescrollbar.js"></script>
<script type="text/javascript" src="codemirror/matchesonscrollbar.js"></script>
</head>
<body>
<div id="code-container">
@ -185,6 +203,7 @@
<div id="game-container">
<div id="gx-container">
<a href="javascript:runProgram()" id="gx-load-screen">&nbsp;</a>
<img id="logo" src="logo.png" onclick="window.open('https://github.com/boxgaming/qbjs/wiki', '_blank')">
</div>
<div id="output-container">
<div id="tabs">
@ -195,7 +214,9 @@
<div id="js-code"></div>
</div>
</div>
<div id="show-js-container"><a id="toggle-console" href="javascript:showConsole()">Show Console</a></div> <!-- <input type="checkbox" id="show-js" onclick="window.onresize()"/> Show Javascript</div> -->
<div id="show-js-container">
<a id="toggle-console" href="javascript:showConsole()">Show Console</a>
</div>
</div>
<div id="gx-footer"></div>
@ -209,14 +230,16 @@
<option value="auto">Auto</option>
</select>
</div>
<a href="javascript:closeDialog()" style="display:block; float:right; margin-top: 5px">Close</a>
<a href="javascript:testShare()" style="display: block; float:right; margin-top: 5px; margin-right: 10px">Test</a>
<a id="export-button" href="javascript:exportProgram()" style="display: none; float:left; margin-top: 7px; margin-left: 10px">Export</a>
<a href="javascript:closeDialog()" style="display:block; float:right; margin-top: 7px">Close</a>
<a href="javascript:testShare()" style="display: block; float:right; margin-top: 7px; margin-right: 10px">Test</a>
</dialog>
</body>
<script language="javascript" src="gx/gx.js"></script>
<script language="javascript" src="qb.js"></script>
<script language="javascript" src="qb2js.js"></script>
<script language="javascript">
var QBCompiler = null;
// if code has been passed on the query string load it into the editor
var qbcode = "";
@ -236,6 +259,9 @@
qbcode = zin.inflate(atob(nv[1]));
break;
}
else if (nv[0] == "code") {
qbcode = LZUTF8.decompress(nv[1], { inputEncoding: "Base64" });
}
else if (nv[0] == "mode") {
appMode = nv[1];
}
@ -252,9 +278,10 @@
indentUnit: 4,
value: qbcode,
module: "qbjs",
theme: "lesser-dark",
theme: "qbjs",
height: "auto",
styleActiveLine: true,
smartIndent: false,
extraKeys: {
"Tab": function(cm) {
cm.replaceSelection(" ", "end");
@ -319,7 +346,7 @@
function shareProgram() {
var zout = new Shorty();
var b64 = btoa(zout.deflate(editor.getValue()));
var b64 = LZUTF8.compress(editor.getValue(), { outputEncoding: "Base64" });
var baseUrl = location.href.split('?')[0];
var mode = document.getElementById("share-mode").value;
@ -328,11 +355,57 @@
if (mode) {
url += "mode=" + mode + "&";
}
url += "qbcode=" + b64;
url += "code=" + b64;
codeShare.value = url;
document.getElementById("share-dialog").showModal();
var shareDialog = document.getElementById("share-dialog");
if (!shareDialog.open) {
shareDialog.showModal();
}
codeShare.focus();
codeShare.select();
var exportVisible = (mode == "play" || mode == "auto");
document.getElementById("export-button").style.display = (exportVisible) ? "block" : "none";
}
async function exportProgram() {
var zip = new JSZip();
var qbCode = editor.getValue();
if (!QBCompiler) { QBCompiler = await _QBCompiler(); }
var jsCode = "async function __qbjs_run() {\n" + await QBCompiler.compile(qbCode) + "\n}";
var mode = document.getElementById("share-mode").value;
zip.file("index.html", await getFile("export/" + mode + ".html", "text"));
zip.file("program.js", jsCode);
zip.file("fullscreen.png", await getFile("export/fullscreen.png", "blob"));
zip.file("logo.png", await getFile("logo.png", "blob"));
zip.file("lp-dosvga.ttf", await getFile("lp-dosvga.ttf", "blob"));
zip.file("play.png", await getFile("play.png", "blob"));
zip.file("qbjs.css", await getFile("export/qbjs.css", "text"));
zip.file("qb.js", await getFile("qb.js", "text"));
zip.file("gx/gx.js", await getFile("gx/gx.js", "text"));
zip.file("gx/__gx_font_default.png", await getFile("gx/__gx_font_default.png", "blob"));
zip.file("gx/__gx_font_default_black.png", await getFile("gx/__gx_font_default_black.png", "blob"));
zip.generateAsync({type:"blob"}).then(function(content) {
const link = document.createElement("a");
link.href = URL.createObjectURL(content);
link.download = "program.zip";
link.click();
link.remove();
});
}
async function getFile(path, type, contentType) {
var file = await fetch(path);
if (type == "text") {
return await file.text();
}
else if (type == "blob") {
return await file.blob();
}
}
function testShare() {
@ -432,6 +505,7 @@
document.getElementById("game-container").style.left = "0px";
document.getElementById("game-container").style.top = "0px";
jsDiv.style.display = "none";
document.getElementById("logo").style.display = "none";
}
else {
var cmwidth = 600;