scripts/docbrowser/docbrowser.js
/**
documentation browser, with editing support
note that you must set window.TINYMCE_PATH
*/
//import {pushModalLight, popModalLight, Icons, UIBase, nstructjs, util, Vector2, Matrix4} from '../../pathux.js';
import {pushModalLight, popModalLight} from "../path-controller/util/simple_events.js";
import * as cconst from '../config/const.js';
import nstructjs from "../path-controller/util/struct.js";
import {UIBase, Icons} from "../core/ui_base.js";
import {platform} from '../platforms/platform.js';
let tinymceLoaded = false;
import('../lib/tinymce/tinymce.js').then(mod => {
tinymceLoaded = true;
});
import * as util from '../util/util.js';
import {Vector2, Matrix4} from '../util/vectormath.js';
let countstr = function(buf, s) {
let count = 0;
while (buf.length > 0) {
let i = buf.search(s);
if (i < 0) {
break;
}
buf = buf.slice(i+1, buf.length);
count++;
}
return count;
}
function basename(path) {
while (path.length > 0 && path.trim().endsWith("/")) {
path = path.slice(0, path.length-1);
}
path = path.replace(/\/+/g, "/");
path = path.split("/");
return path[path.length-1];
}
function dirname(path) {
while (path.length > 0 && path.trim().endsWith("/")) {
path = path.slice(0, path.length-1);
}
path = path.split("/");
path.length--;
let s = "";
for (let t of path) {
s += t + "/";
}
while (s.endsWith("/")) {
s = s.slice(0, s.length - 1);
}
return s;
}
function relative(a1, b1) {
let a = a1, b = b1;
let i = 1;
while (i <= a.length && b.startsWith(a.slice(0, i+1))) {
i++;
}
i--
let pref = "";
a = a.slice(i, a.length).trim();
b = b.slice(i, b.length).trim();
let s = "";
for (let i = 0; i < countstr(a, "/"); i++) {
s += "../";
}
if (s.endsWith("/") && b.startsWith("/")) {
s = s.slice(0, s.length-1);
}
return s + b;
}
window._relative = relative;
export class DocsAPI {
updateDoc(relpath, data) {
//returns a promise
}
uploadImage(blobInfo, success, onError) {
}
newDoc(relpath, data) {
//returns a promise
}
hasDoc(relpath, data) {
//returns a promise
}
uploadImage(relpath, blobInfo, success, onError) {
//returns a promise
}
}
window.PATHUX_DOCPATH = "../../simple_docsys/docsys_base.js";
window.PATHUX_DOC_CONFIG = "../simple_docsys/docs.config.js";
window.PATHUX_DOCPATH_PREFIX = "../simple_docsys/doc_build";
export class ElectronAPI extends DocsAPI {
constructor() {
super();
this.first = true;
this.ready = false;
}
_doinit() {
if (!this.first) {
return this.ready;
}
this.first = false;
import(PATHUX_DOCPATH).then(docsys => {
let fs = require('fs');
let marked = require('marked');
let parse5 = require('parse5');
let pathmod = require('path');
let jsdiff = require('diff');
docsys = docsys.default(fs, marked, parse5, pathmod, jsdiff);
this.config = docsys.readConfig(PATHUX_DOC_CONFIG);
this.ready = true;
});
return this.ready;
}
start() {
this._doinit();
}
checkInit() {
if (!this.ready) {
this._doinit();
}
if (!this.ready) {
console.warn("Could not connect to docs server");
}
return this.ready;
}
uploadImage(relpath, blobInfo, success, onError) {
return new Promise((accept, reject) => {
if (!this.checkInit()) {
return;
}
let blob = blobInfo.blob();
return blob.arrayBuffer().then((data) => {
let path = this.config.uploadImage(relpath, blobInfo.filename(), data);
success(path);
});
}).catch((error) => {
onError(""+error);
});
}
hasDoc(relpath) {
if (!this.checkInit()) {
return;
}
return new Promise((accept, reject) => {
accept(this.config.hasDoc(relpath));
});
}
updateDoc(relpath, data) {
if (!this.checkInit()) {
return;
}
return new Promise((accept, reject) => {
accept(this.config.updateDoc(relpath, data));
});
}
newDoc(relpath, data) {
if (!this.checkInit()) {
return;
}
return new Promise((accept, reject) => {
accept(this.config.newDoc(relpath, data));
});
}
}
export class ServerAPI extends DocsAPI {
constructor() {
super();
}
start() {
}
hasDoc(relpath) {
return this.callAPI("hasDoc", relpath);
}
updateDoc(relpath, data) {
return this.callAPI("updateDoc", relpath, data);
}
newDoc(relpath, data) {
return this.callAPI("newDoc", relpath, data);
}
uploadImage(relpath, blobInfo, success, onError) {
return new Promise((accept, reject) => {
let blob = blobInfo.blob();
return blob.arrayBuffer().then((data) => {
console.log("data!", data);
let uint8 = new Uint8Array(data);
let data2 = [];
for (let i=0; i<uint8.length; i++) {
data2.push(uint8[i]);
}
console.log("data2", data2);
this.callAPI("uploadImage", relpath, blobInfo.filename(), data2).then((path) => {
success(path);
});
});
}).catch((error) => {
onError(""+error);
});
}
callAPI() {
let key = arguments[0];
let args = [];
for (let i=1; i<arguments.length; i++) {
args.push(arguments[i]);
}
console.log(args, arguments.length);
let path = location.origin + "/api/" + key;
console.log(path);
return new Promise((accept, reject) => {
fetch(path, {
headers: {
"Content-Type" : "application/json"
},
method : "POST",
cache : "no-cache",
body : JSON.stringify(args)
}).then((res) => {
//console.log(res.text, res.json, "res", res);
console.log(path);
if (res.ok || res.status < 300) {
res.text().then((data) => {
console.log("got json", data);
data = JSON.parse(data);
accept(data.result);
}).catch((error) => {
console.log("ERROR!", error);
reject(error);
});
} else {
res.text().then((data) => {
console.log(data);
reject(data);
});
}
//let json = res.json();
}).catch((error) => {
reject(error);
});
});
}
}
export class DocHistoryItem {
constructor(url, title) {
this.url = url;
this.title = "" + title;
}
loadSTRUCT(reader) {
reader(this);
}
}
DocHistoryItem.STRUCT = `
DocHistoryItem {
url : string;
title : string;
}
`;
nstructjs.register(DocHistoryItem);
export class DocHistory extends Array {
constructor() {
super();
this.cur = 0;
}
push(url, title=url) {
console.warn("history push", url);
this.length = this.cur+1;
this[this.length-1] = new DocHistoryItem(url, title);
this.cur++;
return this;
}
go(dir) {
dir = Math.sign(dir);
this.cur = Math.min(Math.max(this.cur + dir, 0), this.length-1);
return this[this.cur];
}
loadSTRUCT(reader) {
reader(this);
this.length = 0;
let cur = this.cur;
this.cur = 0;
for (let item of this._items) {
this.push(item);
}
this.cur = cur;
}
}
DocHistory.STRUCT = `
DocHistory {
_items : array(DocHistoryItem) | this;
cur : int;
}
`;
nstructjs.register(DocHistory);
export class DocsBrowser extends UIBase {
constructor() {
super();
this.pathuxBaseURL = location.href;
this.editMode = false;
this.history = new DocHistory();
//"../simple_docsys/doc_build/";
this._prefix = cconst.docManualPath || PATHUX_DOCPATH_PREFIX;
this.saveReq = 0;
this.saveReqStart = util.time_ms();
this._last_save = util.time_ms();
this.header = document.createElement("rowframe-x");
this.shadow.appendChild(this.header);
this.doOnce(this.makeHeader);
this.root = document.createElement("iframe");
this.shadow.appendChild(this.root);
this.root.onload = () => {
this.initDoc();
}
if (window.haveElectron) {
this.serverapi = new ElectronAPI();
} else {
this.serverapi = new ServerAPI();
}
this.serverapi.start();
this.root.style["margin"] = this.root.style["padding"] = this.root.style["border"] = "0px";
this.root.style["width"] = "100%";
this.root.style["height"] = "100%";
this.currentPath = "";
this._doDocInit = true;
this.contentDiv = undefined; //inside of iframe
}
setEditMode(state) {
this.editMode = state;
if (this.tinymce && this.editMode) {
this.disableLinks();
this.tinymce.show();
} else if (this.tinymce && !this.editMode) {
this.tinymce.hide();
this.enableLinks();
}
this.makeHeader();
if (state && this.oneditstart) {
this.oneditstart(this);
} else if (!state && this.oneditend) {
this.queueSave();
this.oneditend(this);
}
}
go(dir) {
//hrm, use iframe history?
if (!this.root.contentWindow) {
return;
}
if (dir > 0) {
this.root.contentWindow.history.forward();
} else {
this.root.contentWindow.history.back();
}
}
makeHeader() {
this.makeHeader_intern();
//this.flushUpdate();
}
makeHeader_intern() {
console.log("making header");
this.header.clear();
let check = this.header.check(undefined, "Edit Enabled");
check.value = this.editMode;
check.onchange = () => {
console.log("check click!", check.checked);
this.setEditMode(check.checked);
}
if (!this.editMode) {
this.header.iconbutton(Icons.LEFT_ARROW, "Back", () => {
this.go(-1);
});
this.header.iconbutton(Icons.RIGHT_ARROW, "Forward", () => {
this.go(1);
});
return;
}
if (!this.contentDiv || !this.contentDiv.contentEditable) {
setTimeout(() => {
this.makeHeader();
});
return;
}
this.header.button("NoteBox", () => {
this.undoPre("Note Box");
this.execCommand("formatBlock", undefined, "p");
let sel = this.root.contentDocument.getSelection();
let p = sel.anchorNode;
if (!(p instanceof HTMLElement)) {
p = p.parentElement;
}
p.setAttribute("class", "notebox");
this.undoPost("Note Box");
console.log(p);
})
let indexOf = (list, item) => {
for (let i=0; i<list.length; i++) {
if (list[i] === item) {
return i;
}
}
return -1;
}
this.header.button("Remove", () => {
let sel = this.root.contentDocument.getSelection();
let p = sel.anchorNode;
if (!p) return;
if (!(p instanceof HTMLElement)) {
p = p.parentElement;
}
if (!p) {
return;
}
let parent = p.parentNode;
let i = indexOf(p.parentNode.childNodes, p);
if (p === this.contentDiv || p === this.contentDiv.parentNode || p === this.root.contentDocument.body) {
return;
}
p.remove();
console.log(p, i);
let add = parent.childNodes.length > 0 ? parent.childNodes[i] : undefined;
for (let i=0; i<p.childNodes.length; i++) {
if (!add) {
parent.appendChild(p.childNodes[i]);
} else {
parent.insertBefore(p.childNodes[i], add);
}
}
});
this.header.iconbutton(Icons.BOLD, "Bold", () => {
this.execCommand("bold");
console.log("ACTIVE", this.root.contentDocument.activeElement);
}).iconsheet = 0;
this.header.iconbutton(Icons.ITALIC, "Italic", () => {
this.execCommand("italic");
}).iconsheet = 0;
this.header.iconbutton(Icons.UNDERLINE, "Underline", () => {
this.execCommand("underline");
}).iconsheet = 0;
this.header.iconbutton(Icons.STRIKETHRU, "StrikeThrough", () => {
this.execCommand("strikeThrough");
}).iconsheet = 0;
this.header.button("PRE", () => {
this.execCommand("formatBlock", false, "pre");
}).iconsheet = 0;
this.header.listenum(undefined, "Style", {
Paragraph : "P",
"Heading 1" : "H1",
"Heading 2" : "H2",
"Heading 3" : "H3",
"Heading 4" : "H4",
"Heading 5" : "H5",
}).onselect = (e) => {
this.execCommand("formatBlock", false, e.toLowerCase());
}
}
init() {
super.init();
this.setCSS();
}
execCommand() {
this.undoPre(arguments[0]);
this.root.contentDocument.execCommand(...arguments);
this.undoPost(arguments[0]);
}
loadSource(data) {
this.saveReq = 0;
let cb = () => {
if (this.root.readyState !== 'loading') {
this.initDoc();
} else {
window.setTimeout(cb, 5);
}
};
this.root.setAttribute("srcDoc", data);
this.root.onload = cb;
this._doDocInit = true;
this.contentDiv = undefined;
}
load(url) {
this.history.push(url, url);
this.saveReq = 0;
this.currentPath = url;
this.root.setAttribute("src", url);
this.root.onload = () => {
this.initDoc();
}
this._doDocInit = true;
this.contentDiv = undefined;
}
initDoc() {
if (!tinymceLoaded) {
this.doOnce(this.initDoc);
return;
}
this._doDocInit = false;
this.contentDiv = undefined;
console.log("doc loaded");
let visit = (n) => {
if (n.getAttribute) {
//console.log(n, n.getAttribute("class"))
}
if (n.getAttribute && n.getAttribute("class") === "contents") {
this.contentDiv = n;
console.log("found content div");
return;
}
//console.log(n.childNodes)
for (let c of n.childNodes) {
visit(c);
}
}
if (!this.root.contentDocument) {
return;
}
visit(this.root.contentDocument.body);
if (this.contentDiv) {
if (this.editMode) {
this.disableLinks();
}
let globals = this.root.contentWindow;
console.log("tinymce globals:", globals.document, globals);
window.tinymce = undefined;
//*
window.tinyMCEPreInit = {
suffix : "",
baseURL : this.currentPath,
documentBaseURL : location.href
};
//*/
let loc = globals.document.location;
if (loc.href === "about:srcdoc") {
loc.href = document.location.href;// this.currentPath;
}
let base = this.pathuxBaseURL;
let base_url = platform.resolveURL("scripts/lib/tinymce", base);
console.warn(window.haveElectron, "haveElectron", base_url);
let tinymce = this.tinymce = globals.tinymce = window.tinymce = _tinymce(globals);
let fixletter = () => {
//fix drive letter on windows
if (window.haveElectron) {
if (process.platform === "win32") {
console.warn("Fixing drive letter", tinymce.baseURI);
tinymce.baseURI.host += ":";
tinymce.baseURL = tinymce.baseURI.source = tinymce.baseURI.toAbsolute();
}
}
}
let _baseuri = tinymce.baseURI;
Object.defineProperty(tinymce, "baseURI", {
get() {
return _baseuri;
},
set(v) {
_baseuri = v;
if (v) {
fixletter();
}
}
});
fixletter();
tinymce.init({
selector: "div.contents",
base_url: base_url,
paste_data_images : true,
allow_html_data_urls : true,
plugins: [ 'quickbars', 'paste' ],
toolbar: true,
menubar: true,
inline: true,
images_upload_handler : (blobInfo, success, onError) => {
console.log("uploading image!", blobInfo);
this.serverapi.uploadImage(this.getDocPath(), blobInfo, success, onError);
},
setup : function(editor) {
console.log("tinymce editor setup!", editor);
}
}).then((arg) => {
fixletter();
this.tinymce = arg[0];
if (!this.editMode) {
this.tinymce.hide();
} else {
this.disableLinks();
}
});
fixletter();
let onchange = (e) => {
console.log("Input event!");
this.queueSave();
}
this.contentDiv.addEventListener("input", onchange);
this.contentDiv.addEventListener("change", onchange);
//this.contentDiv.contentEditable = true;
}
}
queueSave() {
if (!this.saveReq) {
this.saveReqStart = util.time_ms();
}
this.saveReq = 1;
}
undoPre() {
let undo = this.tinymce.editors[0].undoManager;
undo.beforeChange();
}
undoPost(label) {
let undo = this.tinymce.editors[0].undoManager;
undo.add();
}
enableLinks() {
let visit = (n) => {
if (n.getAttribute && n.getAttribute("class") === "contents") {
return;
}
if (n.tagName === "A") {
if (n.getAttribute("_href")) {
n.setAttribute("href", n.getAttribute("_href"));
}
}
for (let c of n.childNodes) {
visit(c);
}
}
visit(this.root.contentDocument.body);
}
disableLinks() {
let visit = (n) => {
if (n.getAttribute && n.getAttribute("class") === "contents") {
return;
}
if (n.tagName === "A") {
n.setAttribute("_href", n.getAttribute("href"));
n.removeAttribute("href");
}
for (let c of n.childNodes) {
visit(c);
}
}
visit(this.root.contentDocument.body);
}
patchImageTags() {
console.log("patching image tags");
if (!this.contentDiv) {
return;
}
let tags = [];
let traverse = (n) => {
if (n.tagName === "IMG" && !n.getAttribute("_PATCHED_")) {
tags.push(n);
}
for (let c of n.children) {
traverse(c);
}
}
traverse(this.contentDiv);
console.log("Image Tags found:", tags);
for (let t of tags) {
this.patchImage(t);
t.setAttribute("_PATCHED_", true);
}
}
patchImage(img) {
//OKAY, this isn't going to work
img.style.float = "right";
return; //XXX
console.warn("Patching image!");
let grab = (i, vs) => {
console.log("Transform Modal start");
let horiz = i % 2 != 0 ? 1 : 0;
let update = () => {
let x = vs[0][0], y = vs[0][1];
let w = vs[2][0] - vs[0][0];
let h = vs[1][1] - vs[0][1];
img.style["display"] = "float";
img.style["left"] = x + "px";
img.style["top"] = y + "px";
img.style["width"] = w + "px";
img.style["height"] = h + "px";
}
update();
let modaldata;
let first = true;
let last_mpos = new Vector2();
let start_mpos = new Vector2();
let end = () => {
if (modaldata) {
console.log("done.");
popModalLight(modaldata);
modaldata = undefined;
}
}
let ghandlers = {
on_mousedown(e) {
},
on_mousemove(e) {
console.log("modal move");
if (first) {
first = false;
start_mpos[0] = last_mpos[0] = e.x;
start_mpos[1] = last_mpos[1] = e.y;
return;
}
let dx = e.x - last_mpos[0], dy = last_mpos[1] - e.y;
console.log(dx.toFixed(2), dy.toFixed(2));
last_mpos[0] = e.x;
last_mpos[1] = e.y;
},
on_mouseup(e) {
end();
},
on_keydown(e) {
console.log(e.keyCode);
if (e.keyCode === 27) {
end();
}
}
}
modaldata = pushModalLight(ghandlers);
console.warn("grab!", modaldata);
}
let mpos = new Vector2();
let first = true;
let tdown = true;
let mdown = false;
let ix = 0, iy = 0;
let width = img.width, height = img.height;
img.setAttribute("draggable", "false");
let getsize = () => {
let r = img.getClientRects[0]
if (!r) {
setTimeout(getsize, 2)
return;
}
console.log("got image size", width, height, img.width, img.height);
width = r.width;
height = r.height;
}
let resizing = false;
let moving = false;
let handlers = {
pointerover(e) {
console.log("mouse over!")
},
pointerleave(e) {
console.log("mouse leave!")
},
pointerdown(e, x=e.x, y=e.y, button=e.button) {
//this.contentDiv.contentEditable = false;
mpos[0] = x;
mpos[1] = y;
mdown = true;
resizing = false;
moving = false;
img.setPointerCapture(e.pointerId);
},
pointermove(e, x=e.x, y=e.y, button=e.button) {
if (first) {
mpos[0] = x;
mpos[1] = y;
first = false;
return;
}
console.log(moving);
if (moving) {
let dx = x - mpos[0], dy = y - mpos[1];
console.log("mdown!", dx, dy);
ix += dx;
iy += dy;
img.style["position"] = "relative";
img.style["display"] = "inline";
img.style["left"] = ix + "px";
img.style.float = "right";
img.style["top"] = iy + "px";
}
if (resizing) {
let dx = x - mpos[0], dy = y - mpos[1];
console.log("mdown!", dx, dy);
width += dy;
height += dy;
img.style["width"] = width + "px";
img.style["height"] = height+ "px";
console.log("ix", ix);
}
mpos[0] = x;
mpos[1] = y;
//console.log(x.toFixed(2), y.toFixed(2));
let r = img.getBoundingClientRect();
if (!r) {
return;
}
let verts = [
new Vector2([r.x, r.y]),
new Vector2([r.x, r.y+r.height]),
new Vector2([r.x+r.width, r.y+r.height]),
new Vector2([r.x+r.width, r.y]),
]
let ret = undefined;
let mindis = 1e17;
for (let i=0; i<4; i++) {
let i1 = i, i2 = (i + 1) % 4;
let v1 = verts[i1], v2 = verts[i2];
let horiz = i % 2 !== 0.0 ? 1 : 0;
let dv = mpos[horiz] - v1[horiz];
if (Math.abs(dv) < 15 && Math.abs(dv) < mindis) {
mindis = Math.abs(dv);
ret = i;
console.log("border!", i);
}
}
if (ret !== undefined && !moving) {
img.setAttribute("draggable", "false");
if (mdown) {
resizing = true;
img.setPointerCapture(e.pointerId);
}
} else if (!resizing && mdown) {
moving = true;
img.setPointerCapture(e.pointerId);
//img.setAttribute("draggable", "true");
}
},
pointerup(e, x=e.x, y=e.y, button=e.button) {
mpos[0] = x;
mpos[1] = y;
mdown = false;
if (resizing) {
this.releasePointerCapture(e.pointerId);
}
resizing = false;
moving = false;
},
pointercancel(e) {
mdown = false;
moving = false;
},
}
window.setInterval(() => {
if (1||!mdown) {
let val = img.getAttribute("draggable");
img.setAttribute("draggable", "false");
img.setAttribute("draggable", val);
}
}, 200)
for (let k in handlers) {
img.addEventListener(k, handlers[k].bind(this), true);
}
}
toMarkdown() {
if (this.contentDiv === undefined) {
return;
}
let buf = "";
let visit;
let liststack = [];
let image_idgen = 0;
let getlist = () => {
if (liststack.length > 0)
return liststack[liststack.length-1];
}
let handlers = {
TEXT(n) {
console.log("Text data:", n.data);
buf += n.textContent;
},
H1(n) {
buf += "\n# " + n.innerHTML.trim() + "\n\n";
},
H2(n) {
buf += "\n## " + n.innerHTML.trim() + "\n\n";
},
H3(n) {
buf += "\n### " + n.innerHTML.trim() + "\n\n";
},
H4(n) {
buf += "\n#### " + n.innerHTML.trim() + "\n\n";
},
H5(n) {
buf += "\n##### " + n.innerHTML.trim() + "\n\n";
},
IMG(n) {
buf += `<!--$IMG${image_idgen++}-->`;
buf += n.outerHTML;
buf += `<!--/$IMG${image_idgen++}-->`;
visit();
},
TABLE(n) {
buf += n.outerHTML;
visit();
},
P(n) {
buf += "\n";
visit();
},
BR(n) {
buf += "\n";
},
A(n) {
buf += `[${n.innerHTML}](${n.getAttribute("href")})`
},
B(n) {
buf += "<b>"
visit();
buf += "</b>"
},
STRONG(n) {
buf += "<strong>"
visit();
buf += "</strong>"
},
EM(n) {
buf += "<em>"
visit();
buf += "</em>"
},
STRIKE(n) {
buf += "<strike>"
visit();
buf += "</strike>"
},
I(n) {
buf += "<i>"
visit();
buf += "</i>"
},
U(n) {
buf += "<u>"
visit();
buf += "</u>"
},
UL(n) {
liststack.push(["UL", 0]);
visit();
liststack.pop();
},
OL(n) {
liststack.push(["OL", 1]);
visit();
liststack.pop();
},
LI(n) {
let head = getlist();
if (head && head[0] === "OL") {
buf += head[1] + ". ";
head[1]++;
} else {
buf += "* "
}
visit()
},
PRE(n) {
let start = buf;
visit();
let data = buf.slice(start.length, buf.length);
let lines = data.split("\n");
let bad = false;
for (let l of lines) {
if (!l.startsWith(" ")) {
bad = true;
break;
}
}
if (bad) {
buf = start + "<pre>" + data + "</pre>\n"
} else {
buf = start + data;
}
}
}
let traverse = (n) => {
visit = () => {
for (let c of n.childNodes) {
traverse(c);
}
}
if (n.constructor.name === "Text") {
handlers.TEXT(n);
} else {
let tag = n.tagName;
if (tag in handlers) {
handlers[tag](n);
} else {
visit();
}
}
}
traverse(this.contentDiv);
return buf;
}
getDocPath() {
if (!this.root.contentDocument) {
return undefined;
}
let href = this.root.contentDocument.location.href;
//console.log(document.location.href, this.root.src);
let path = relative(dirname(document.location.href), href).trim();
while (path.startsWith("/")) {
path = path.slice(1, path.length);
}
console.log("PATH", path, this._prefix);
if (!path) return;
if (path.startsWith(this._prefix)) {
path = path.slice(this._prefix.length, path.length).trim();
}
if (!path.startsWith("/")) {
path = "/" + path;
}
return path;
/*
let path = this.currentPath;
if (path.startsWith(this._prefix)) {
path = path.slice(this._prefix.length, path.length).trim();
}
if (!path.startsWith("/")) {
path = "/" + path;
}
return path;//*/
}
save() {
if (!this.contentDiv) {
this.report("Save Error", "red");
return;
}
if (this.saveReq === 2) {
if (util.time_ms() - this.saveReqStart > 13000) {
this.saveReqStart = util.time_ms();
this.saveReq = 1;
console.log("save timeout");
} else {
return;
}
}
this.report("Saving...", "yellow", 400);
let path = this.getDocPath();
console.log("saving " + path);
this.saveReq = 2;
this.serverapi.updateDoc(path, this.contentDiv.innerHTML).then((result) => {
this.saveReq = 0;
console.log("Sucess! Saved document", result);
if (result) {
console.log("Server changed final document; reloading...");
this.contentDiv.innerHTML = result;
}
this.report("Saved", "green", 750)
}).catch((error) => {
console.error(error);
});
}
updateCurrentPath() {
if (!this.contentDiv) {
return;
}
let href = this.root.contentDocument.location.href;
href = relative(dirname(location.href), href).trim();
if (href !== this.currentPath) {
console.log("path change detected", href);
this.history.push(href, href);
this.currentPath = href;
}
}
//send notifications to user
report(message, color=undefined, timeout=undefined) {
if (this.ctx.report) {
console.warn("%c"+message, "color : " + color + ";");
this.ctx.report(message, color, timeout);
} else {
console.warn("%c"+message, "color : " + color + ";");
}
}
update() {
if (this.saveReq) {
if (util.pollTimer(this._id, 500)) {
this.report("saving...", "yellow", 400);
}
}
this.updateCurrentPath();
if (this._doDocInit && this.root.contentDocument && this.root.contentDocument.readyState === "complete") {
//this.initDoc();
} else if (!this._doDocInit && this.saveReq) {
if (util.time_ms() - this._last_save > 500) {
this.save();
this._last_save = util.time_ms();
}
}
}
setCSS() {
if (!this.root) {
return;
}
this.style.width = "100%";
this.style.height = "max-contents";
this.root.style["background-color"] = "grey";
}
static newSTRUCT() {
return document.createElement("docs-browser-x");
}
loadSTRUCT(reader) {
reader(this);
this.doOnce(this.makeHeader);
this.root.setAttribute("src", this.currentPath);
}
static define() {return {
tagname : "docs-browser-x",
style : "docsbrowser"
}}
}
DocsBrowser.STRUCT = `
DocsBrowser {
currentPath : string;
savedDocument : string;
editMode : bool;
history : DocHistory;
}
`;
UIBase.internalRegister(DocsBrowser);
nstructjs.register(DocsBrowser);