miqro 7.2.7 → 7.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/esm/editor/auth.d.ts +6 -0
- package/build/esm/editor/auth.js +42 -0
- package/build/esm/editor/common/constants.d.ts +4 -0
- package/build/esm/editor/common/constants.js +20 -0
- package/build/esm/editor/common/constants.server.d.ts +2 -0
- package/build/esm/editor/common/constants.server.js +4 -0
- package/build/esm/editor/common/editor-index.d.ts +2 -0
- package/build/esm/editor/common/editor-index.js +14 -0
- package/build/esm/editor/common/html-encode.d.ts +1 -0
- package/build/esm/editor/common/html-encode.js +14 -0
- package/build/esm/editor/common/log-socket.d.ts +15 -0
- package/build/esm/editor/common/log-socket.js +71 -0
- package/build/esm/editor/common/templates.d.ts +11 -0
- package/build/esm/editor/common/templates.js +477 -0
- package/build/esm/editor/components/api-preview.d.ts +11 -0
- package/build/esm/editor/components/api-preview.js +92 -0
- package/build/esm/editor/components/editor.d.ts +16 -0
- package/build/esm/editor/components/editor.js +367 -0
- package/build/esm/editor/components/file-browser.d.ts +37 -0
- package/build/esm/editor/components/file-browser.js +127 -0
- package/build/esm/editor/components/file-editor-toolbar.d.ts +22 -0
- package/build/esm/editor/components/file-editor-toolbar.js +95 -0
- package/build/esm/editor/components/file-editor.d.ts +32 -0
- package/build/esm/editor/components/file-editor.js +61 -0
- package/build/esm/editor/components/filter-query.d.ts +1 -0
- package/build/esm/editor/components/filter-query.js +23 -0
- package/build/esm/editor/components/highlight-text-area.d.ts +11 -0
- package/build/esm/editor/components/highlight-text-area.js +127 -0
- package/build/esm/editor/components/log-viewer.d.ts +6 -0
- package/build/esm/editor/components/log-viewer.js +71 -0
- package/build/esm/editor/components/new-file.d.ts +10 -0
- package/build/esm/editor/components/new-file.js +119 -0
- package/build/esm/editor/components/scroll-query.d.ts +7 -0
- package/build/esm/editor/components/scroll-query.js +22 -0
- package/build/esm/editor/components/start-page.d.ts +13 -0
- package/build/esm/editor/components/start-page.js +32 -0
- package/build/esm/editor/http/admin/editor/api/fs/delete.api.d.ts +4 -0
- package/build/esm/editor/http/admin/editor/api/fs/delete.api.js +30 -0
- package/build/esm/editor/http/admin/editor/api/fs/read.api.d.ts +7 -0
- package/build/esm/editor/http/admin/editor/api/fs/read.api.js +50 -0
- package/build/esm/editor/http/admin/editor/api/fs/rename.api.d.ts +7 -0
- package/build/esm/editor/http/admin/editor/api/fs/rename.api.js +40 -0
- package/build/esm/editor/http/admin/editor/api/fs/scan.api.d.ts +26 -0
- package/build/esm/editor/http/admin/editor/api/fs/scan.api.js +150 -0
- package/build/esm/editor/http/admin/editor/api/fs/write.api.d.ts +3 -0
- package/build/esm/editor/http/admin/editor/api/fs/write.api.js +39 -0
- package/build/esm/editor/http/admin/editor/api/server/reload.api.d.ts +10 -0
- package/build/esm/editor/http/admin/editor/api/server/reload.api.js +46 -0
- package/build/esm/editor/http/admin/editor/api/server/restart.api.d.ts +10 -0
- package/build/esm/editor/http/admin/editor/api/server/restart.api.js +46 -0
- package/build/esm/editor/http/admin/editor/editor.d.ts +1 -0
- package/build/esm/editor/http/admin/editor/editor.js +8 -0
- package/build/esm/editor/http/admin/editor/index.api.d.ts +3 -0
- package/build/esm/editor/http/admin/editor/index.api.js +23 -0
- package/build/esm/editor/server.d.ts +3 -0
- package/build/esm/editor/server.js +49 -0
- package/build/esm/editor/ws.d.ts +3 -0
- package/build/esm/editor/ws.js +12 -0
- package/build/esm/src/common/admin-interface.d.ts +36 -0
- package/build/esm/src/common/admin-interface.js +44 -0
- package/build/esm/src/common/exit.js +18 -2
- package/build/esm/src/common/watch.js +8 -3
- package/build/esm/src/services/app.d.ts +1 -1
- package/build/esm/src/services/editor.d.ts +1 -1
- package/build/esm/src/services/utils/admin-interface.d.ts +1 -1
- package/build/esm/src/services/utils/websocketmanager.d.ts +1 -1
- package/build/lib.cjs +48 -20
- package/editor/auth.ts +52 -0
- package/editor/common/constants.server.ts +5 -0
- package/editor/common/constants.ts +21 -0
- package/editor/common/editor-index.tsx +17 -0
- package/editor/common/html-encode.ts +14 -0
- package/editor/common/log-socket.tsx +87 -0
- package/editor/common/templates.ts +481 -0
- package/editor/components/api-preview.tsx +118 -0
- package/editor/components/editor.tsx +496 -0
- package/editor/components/file-browser.tsx +311 -0
- package/editor/components/file-editor-toolbar.tsx +194 -0
- package/editor/components/file-editor.tsx +125 -0
- package/editor/components/filter-query.tsx +26 -0
- package/editor/components/highlight-text-area.tsx +148 -0
- package/editor/components/log-viewer.tsx +113 -0
- package/editor/components/new-file.tsx +172 -0
- package/editor/components/scroll-query.tsx +25 -0
- package/editor/components/start-page.tsx +52 -0
- package/editor/http/admin/editor/api/fs/delete.api.tsx +32 -0
- package/editor/http/admin/editor/api/fs/read.api.tsx +55 -0
- package/editor/http/admin/editor/api/fs/rename.api.tsx +41 -0
- package/editor/http/admin/editor/api/fs/scan.api.tsx +181 -0
- package/editor/http/admin/editor/api/fs/write.api.tsx +41 -0
- package/editor/http/admin/editor/api/server/reload.api.ts +53 -0
- package/editor/http/admin/editor/api/server/restart.api.tsx +52 -0
- package/editor/http/admin/editor/editor.tsx +10 -0
- package/editor/http/admin/editor/index.api.tsx +43 -0
- package/editor/server.ts +57 -0
- package/editor/ws.ts +17 -0
- package/package.json +2 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import * as jsx from "@miqro/jsx";
|
|
2
|
+
import JSX from "@miqro/jsx";
|
|
3
|
+
|
|
4
|
+
//import hljs from "../lib/highlight/core.min.js"
|
|
5
|
+
import hljs from 'highlight.js/lib/core';
|
|
6
|
+
import javascript from 'highlight.js/lib/languages/javascript';
|
|
7
|
+
import xml from "highlight.js/lib/languages/xml";
|
|
8
|
+
import css from "highlight.js/lib/languages/css";
|
|
9
|
+
import scss from "highlight.js/lib/languages/scss";
|
|
10
|
+
import markdown from "highlight.js/lib/languages/markdown";
|
|
11
|
+
import dockerfile from "highlight.js/lib/languages/dockerfile";
|
|
12
|
+
import yaml from "highlight.js/lib/languages/yaml";
|
|
13
|
+
import typescript from "highlight.js/lib/languages/typescript";
|
|
14
|
+
import c from "highlight.js/lib/languages/c";
|
|
15
|
+
import cpp from "highlight.js/lib/languages/cpp";
|
|
16
|
+
import bash from "highlight.js/lib/languages/bash";
|
|
17
|
+
import python from "highlight.js/lib/languages/python";
|
|
18
|
+
import text from "highlight.js/lib/languages/plaintext";
|
|
19
|
+
import json from "highlight.js/lib/languages/json";
|
|
20
|
+
|
|
21
|
+
// Then register the languages you need
|
|
22
|
+
hljs.registerLanguage('text', text);
|
|
23
|
+
hljs.registerLanguage('dockerfile', dockerfile);
|
|
24
|
+
hljs.registerLanguage('yaml', yaml);
|
|
25
|
+
hljs.registerLanguage('javascript', javascript);
|
|
26
|
+
hljs.registerLanguage('xml', xml);
|
|
27
|
+
hljs.registerLanguage('html', xml);
|
|
28
|
+
hljs.registerLanguage('css', css);
|
|
29
|
+
hljs.registerLanguage('scss', scss);
|
|
30
|
+
hljs.registerLanguage('markdown', markdown);
|
|
31
|
+
hljs.registerLanguage('typescript', typescript);
|
|
32
|
+
hljs.registerLanguage('c', c);
|
|
33
|
+
hljs.registerLanguage('cpp', cpp);
|
|
34
|
+
hljs.registerLanguage('bash', bash);
|
|
35
|
+
hljs.registerLanguage('python', python);
|
|
36
|
+
hljs.registerLanguage('json', json);
|
|
37
|
+
|
|
38
|
+
/*!
|
|
39
|
+
Theme: GitHub Dark
|
|
40
|
+
Description: Dark theme as seen on github.com
|
|
41
|
+
Author: github.com
|
|
42
|
+
Maintainer: @Hirse
|
|
43
|
+
Updated: 2021-05-15
|
|
44
|
+
|
|
45
|
+
Outdated base version: https://github.com/primer/github-syntax-dark
|
|
46
|
+
Current colors taken from GitHub's CSS
|
|
47
|
+
*/
|
|
48
|
+
const STYLE = `pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}code{outline: 0px solid rgba(0, 0, 0, 0.263);caret-color: red;}.hljs-name{color: #e8910d;}.hljs-tag .hljs-attr, .hljs-tag .hljs-name{color: #e8910d;}`;
|
|
49
|
+
const POSTTYLE = "pre{padding:0; margin:0;} code { background-color: #000000; color: #ffffff; }";
|
|
50
|
+
const TAG_NAME = "HIGHLIGHT-TEXT-AREA";
|
|
51
|
+
|
|
52
|
+
function runHighlight(content: string | null, language: string) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
console.log("runHighlight [%s]", language);
|
|
55
|
+
const value = hljs.highlight(String(content), {
|
|
56
|
+
language,
|
|
57
|
+
ignoreIllegals: true
|
|
58
|
+
}).value;
|
|
59
|
+
console.log("runHighlight [%s] took [%s]ms", language, Date.now() - now);
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function emitEvents(elementRef: any, topMost: any, content: string, oncontentchange?: (ev: CustomEvent) => void) {
|
|
64
|
+
if (elementRef.current && topMost.current) {
|
|
65
|
+
const currentContent = elementRef.current.textContent as any;
|
|
66
|
+
const webComponentElement = (topMost.current as HTMLElement).parentNode as HTMLElement;
|
|
67
|
+
const event = new CustomEvent("contentchange", { detail: currentContent });
|
|
68
|
+
if (webComponentElement && webComponentElement instanceof HTMLElement && webComponentElement.tagName === TAG_NAME) {
|
|
69
|
+
try {
|
|
70
|
+
webComponentElement.dispatchEvent(event);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
console.error(e);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (typeof oncontentchange === "function") {
|
|
76
|
+
try {
|
|
77
|
+
oncontentchange(event);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.error(e);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function HighlightTextArea({ content, language, oncontentchange, tabChar, disabled }:
|
|
86
|
+
{ language: string; content: string; disabled?: string; tabChar?: string; oncontentchange?: (ev: CustomEvent) => void }
|
|
87
|
+
) {
|
|
88
|
+
const elementRef = jsx.useRef();
|
|
89
|
+
const topMost = jsx.useRef();
|
|
90
|
+
const [lastLanguage, setlastLanguage] = jsx.useState(language);
|
|
91
|
+
|
|
92
|
+
// effect to watch changes to language
|
|
93
|
+
jsx.useEffect(() => {
|
|
94
|
+
if (elementRef.current && (lastLanguage !== language || elementRef.current.textContent !== content)) {
|
|
95
|
+
setlastLanguage(language);
|
|
96
|
+
elementRef.current.innerHTML = runHighlight(content, language);
|
|
97
|
+
}
|
|
98
|
+
}, [lastLanguage, language, elementRef.current, content])
|
|
99
|
+
|
|
100
|
+
return <pre ref={topMost}>
|
|
101
|
+
<style innerHTML={STYLE + POSTTYLE} />
|
|
102
|
+
<code
|
|
103
|
+
autocomplete="off"
|
|
104
|
+
autocorrect="off"
|
|
105
|
+
autocapitalize="off"
|
|
106
|
+
spellcheck="false"
|
|
107
|
+
contenteditable={disabled ? "false" : "true"}
|
|
108
|
+
ref={elementRef}
|
|
109
|
+
tabindex="0"
|
|
110
|
+
innerHTML={!elementRef.current ? runHighlight(content, language) : null}
|
|
111
|
+
onfocusout={async (ev) => {
|
|
112
|
+
ev.preventDefault();
|
|
113
|
+
if (elementRef.current) {
|
|
114
|
+
if (content !== elementRef.current.textContent) {
|
|
115
|
+
//setlastContent(String(elementRef.current.textContent));
|
|
116
|
+
//elementRef.current.innerHTML = runHighlight(elementRef.current.textContent, language);
|
|
117
|
+
}
|
|
118
|
+
elementRef.current.innerHTML = runHighlight(elementRef.current.textContent, language);
|
|
119
|
+
}
|
|
120
|
+
}}
|
|
121
|
+
onkeydown={ev => {
|
|
122
|
+
if (ev.keyCode === 9) {
|
|
123
|
+
ev.preventDefault();
|
|
124
|
+
// now insert four non-breaking spaces for the tab key
|
|
125
|
+
const sel = document.getSelection();
|
|
126
|
+
if (sel && topMost.current) {
|
|
127
|
+
if ((topMost.current as any).contains(sel.anchorNode)) {
|
|
128
|
+
const range = sel.getRangeAt(0);
|
|
129
|
+
const tabNode = document.createTextNode(tabChar ? tabChar : "\t");
|
|
130
|
+
range.insertNode(tabNode);
|
|
131
|
+
range.setStartAfter(tabNode);
|
|
132
|
+
range.setEndAfter(tabNode);
|
|
133
|
+
sel.removeAllRanges();
|
|
134
|
+
sel.addRange(range);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
emitEvents(elementRef, topMost, content, oncontentchange);
|
|
138
|
+
}
|
|
139
|
+
}}
|
|
140
|
+
oninput={ev => {
|
|
141
|
+
ev.preventDefault();
|
|
142
|
+
emitEvents(elementRef, topMost, content, oncontentchange);
|
|
143
|
+
}} />
|
|
144
|
+
</pre>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
HighlightTextArea.asFragment = true;
|
|
148
|
+
HighlightTextArea.shadowInit = false;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as jsx from "@miqro/jsx";
|
|
2
|
+
import JSX from "@miqro/jsx";
|
|
3
|
+
|
|
4
|
+
import { LogSocket } from "../common/log-socket.js";
|
|
5
|
+
|
|
6
|
+
function getUniqueIdentifiers(lines: {
|
|
7
|
+
out: string;
|
|
8
|
+
identifier: string;
|
|
9
|
+
}[]) {
|
|
10
|
+
const identifiersCache = {};
|
|
11
|
+
return lines.map(l => l.identifier).filter(identifier => {
|
|
12
|
+
const filter = !identifiersCache[identifier];
|
|
13
|
+
identifiersCache[identifier] = true;
|
|
14
|
+
return filter;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const LOG_LEVEL_MAP =
|
|
19
|
+
{
|
|
20
|
+
"none": 0,
|
|
21
|
+
"error": 1,
|
|
22
|
+
"warn": 2,
|
|
23
|
+
"info": 3,
|
|
24
|
+
"debug": 4,
|
|
25
|
+
"trace": 5,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface LogViewerProps {
|
|
29
|
+
socket: LogSocket;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function LogViewer(props: LogViewerProps) {
|
|
33
|
+
const { lines, clearLog, getMaxlogsize, setMaxLogSize } = props.socket;
|
|
34
|
+
|
|
35
|
+
const maxLogSize = getMaxlogsize();
|
|
36
|
+
|
|
37
|
+
const refresh = jsx.useRefresh();
|
|
38
|
+
|
|
39
|
+
jsx.useEffect(() => {
|
|
40
|
+
refresh();
|
|
41
|
+
}, [maxLogSize]);
|
|
42
|
+
|
|
43
|
+
const [identifier, setidentifier] = jsx.useState<string>("");
|
|
44
|
+
const [level, setlevel] = jsx.useState<string>("debug");
|
|
45
|
+
const [filter, setfilter] = jsx.useState<string>("");
|
|
46
|
+
const identifiers = getUniqueIdentifiers(lines).sort();
|
|
47
|
+
//console.log(identifiers);
|
|
48
|
+
|
|
49
|
+
return <div class="log-viewer">
|
|
50
|
+
<div class="log-viewer-toolbar">
|
|
51
|
+
<button
|
|
52
|
+
class="btn"
|
|
53
|
+
onclick={e => {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
clearLog();
|
|
56
|
+
}}>clear log</button>
|
|
57
|
+
<select
|
|
58
|
+
value={maxLogSize}
|
|
59
|
+
oninput={ev => {
|
|
60
|
+
ev.preventDefault();
|
|
61
|
+
if (ev.target.value === "unlimited") {
|
|
62
|
+
setMaxLogSize("unlimited");
|
|
63
|
+
} else {
|
|
64
|
+
setMaxLogSize(parseInt(ev.target.value, 10));
|
|
65
|
+
}
|
|
66
|
+
}}
|
|
67
|
+
style="margin: 0; padding: 0; margin-left:auto; margin-right: 0;">
|
|
68
|
+
{["10", "1000", "5000", "10000", "15000", "20000", "25000", "50000", "100000", "500000", "1000000", "1500000", "5000000"].map(l => <option value={l}>{l}</option>)}
|
|
69
|
+
<option value="unlimited">unlimited</option>
|
|
70
|
+
</select>
|
|
71
|
+
<select
|
|
72
|
+
value={level}
|
|
73
|
+
oninput={ev => {
|
|
74
|
+
ev.preventDefault();
|
|
75
|
+
setlevel(ev.target.value);
|
|
76
|
+
}}
|
|
77
|
+
style="margin: 0; padding: 0; margin-left:var(--file-browser-separation); margin-right: 0;">
|
|
78
|
+
{["error", "warn", "info", "debug", "trace"].map(l => <option value={l}>{l}</option>)}
|
|
79
|
+
<option value="">all</option>
|
|
80
|
+
</select>
|
|
81
|
+
<select
|
|
82
|
+
value={identifier}
|
|
83
|
+
oninput={ev => {
|
|
84
|
+
ev.preventDefault();
|
|
85
|
+
setidentifier(ev.target.value);
|
|
86
|
+
}}
|
|
87
|
+
style="margin: 0; padding: 0; margin-left:var(--file-browser-separation); margin-right: 0;">
|
|
88
|
+
{identifiers.map(identifier => <option value={identifier}>{identifier}</option>)}
|
|
89
|
+
<option value=""></option>
|
|
90
|
+
</select>
|
|
91
|
+
<input
|
|
92
|
+
style="margin: 0; padding: 0; margin-left:var(--file-browser-separation); margin-right: 0;"
|
|
93
|
+
value={filter}
|
|
94
|
+
oninput={ev => {
|
|
95
|
+
ev.preventDefault();
|
|
96
|
+
setfilter(ev.target.value);
|
|
97
|
+
}}
|
|
98
|
+
type="text"
|
|
99
|
+
placeholder="..filter.." />
|
|
100
|
+
</div>
|
|
101
|
+
<div class="log-viewer-log">
|
|
102
|
+
{lines
|
|
103
|
+
.filter(line => identifier === "" || line.identifier === identifier)
|
|
104
|
+
.filter(line => filter === "" || line.out.indexOf(filter) !== -1)
|
|
105
|
+
.filter(line => {
|
|
106
|
+
return (level === "" || LOG_LEVEL_MAP[level] >= LOG_LEVEL_MAP[line.level]);
|
|
107
|
+
})
|
|
108
|
+
.map(line =>
|
|
109
|
+
<p style="margin: 0; padding: 0; border-radius: 0; font-size: 12px;" class={line.level === "error" ? "info-danger" : line.level === "warn" ? "info-warn" : line.level === "trace" ? "info-success" : line.level === "debug" ? "info-info" : ""}>{line.out}</p>
|
|
110
|
+
).reverse()}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as jsx from "@miqro/jsx";
|
|
2
|
+
import JSX from "@miqro/jsx";
|
|
3
|
+
|
|
4
|
+
import { BASEEDITOR_PATH } from "../common/constants.js";
|
|
5
|
+
import { TEMPLATES } from "../common/templates.js";
|
|
6
|
+
import { HighlightTextArea } from "./highlight-text-area.js";
|
|
7
|
+
|
|
8
|
+
export function NewFile(props: { migrations: string[]; services: string[]; open: boolean; ondone: (file?: string) => void; }) {
|
|
9
|
+
|
|
10
|
+
const [template, settemplate] = jsx.useState("EMPTY");
|
|
11
|
+
|
|
12
|
+
const refresh = jsx.useRefresh();
|
|
13
|
+
|
|
14
|
+
const [service, setservice] = jsx.useState(props.services[0]);
|
|
15
|
+
|
|
16
|
+
const nameInput = jsx.useRef();
|
|
17
|
+
//const open = dialog ? dialog.open : false;
|
|
18
|
+
|
|
19
|
+
/*jsx.useEffect(() => {
|
|
20
|
+
if (dialog) {
|
|
21
|
+
//console.log((dialogRef.current as any).open);
|
|
22
|
+
if (open !== props.open) {
|
|
23
|
+
if (nameInput.current) {
|
|
24
|
+
nameInput.current.value = "";
|
|
25
|
+
nameInput.current.focus();
|
|
26
|
+
}
|
|
27
|
+
if (props.open) {
|
|
28
|
+
console.log("showModal");
|
|
29
|
+
dialog.showModal();
|
|
30
|
+
//(dialogRef.current as any).show();
|
|
31
|
+
} else {
|
|
32
|
+
console.log("closeModal");
|
|
33
|
+
//dialog.close();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, [dialog, open, props.open]);*/
|
|
38
|
+
jsx.useEffect(() => {
|
|
39
|
+
|
|
40
|
+
if (nameInput.current) {
|
|
41
|
+
nameInput.current.value = "";
|
|
42
|
+
nameInput.current.focus();
|
|
43
|
+
}
|
|
44
|
+
}, [nameInput.current]);
|
|
45
|
+
|
|
46
|
+
function close(file?: string) {
|
|
47
|
+
//console.log("closeModal");
|
|
48
|
+
if (props.ondone) {
|
|
49
|
+
props.ondone(file);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
let nextMigrationNumber = 1;
|
|
55
|
+
if (props.migrations.length > 0) {
|
|
56
|
+
const lastMigration = props.migrations[props.migrations.length - 1];
|
|
57
|
+
const indexOfDash = lastMigration.indexOf("-");
|
|
58
|
+
if (indexOfDash !== -1) {
|
|
59
|
+
nextMigrationNumber = parseInt(lastMigration.substring(0, indexOfDash), 10) + 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const httpPath = (TEMPLATES[template].filename ? TEMPLATES[template].filename : nameInput.current?.value) + (TEMPLATES[template].httpSufix ? TEMPLATES[template].httpSufix : "");
|
|
64
|
+
|
|
65
|
+
const filename = (service ? service + "/" : "") + (service && TEMPLATES[template].prefix ? TEMPLATES[template].prefix + "/" : "") + (template === "MIGRATION" ? nextMigrationNumber + "-" : "") + (TEMPLATES[template].filename ? TEMPLATES[template].filename : nameInput.current?.value) + (TEMPLATES[template].sufix ? TEMPLATES[template].sufix : "");
|
|
66
|
+
|
|
67
|
+
async function createNewFile() {
|
|
68
|
+
if ((nameInput.current && nameInput.current.value) || TEMPLATES[template].filename) {
|
|
69
|
+
const t = TEMPLATES[template];
|
|
70
|
+
await fetch(BASEEDITOR_PATH + "/api/fs/write", {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
["content-type"]: "application/json"
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
path: filename,
|
|
77
|
+
contents: t.template ? t.template(filename, httpPath) : ""
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
return filename;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return <>
|
|
85
|
+
{props.open ? <dialog
|
|
86
|
+
class="new-dialog"
|
|
87
|
+
open="">
|
|
88
|
+
<div class="dialog-header">
|
|
89
|
+
<h1>Create new File</h1>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="dialog-body">
|
|
92
|
+
<select
|
|
93
|
+
value={service}
|
|
94
|
+
oninput={ev => {
|
|
95
|
+
ev.preventDefault();
|
|
96
|
+
ev.stopPropagation();
|
|
97
|
+
console.log(ev.target.value);
|
|
98
|
+
setservice(ev.target.value);
|
|
99
|
+
}}
|
|
100
|
+
style="margin: 0; padding: 0; margin-left:auto; margin-right: var(--file-browser-separation); width: 100%; margin-top: var(--file-browser-separation); margin-bottom: var(--file-browser-separation);">
|
|
101
|
+
<option value={""}></option>
|
|
102
|
+
{props.services.map(service => <option value={service}>{service}</option>)}
|
|
103
|
+
</select>
|
|
104
|
+
<p
|
|
105
|
+
style="margin: 0; margin-bottom: var(--file-browser-separation);"
|
|
106
|
+
>{filename}</p>
|
|
107
|
+
{!TEMPLATES[template].filename ? <form
|
|
108
|
+
style="width: 100%;"
|
|
109
|
+
onsubmit={async (ev) => {
|
|
110
|
+
ev.preventDefault();
|
|
111
|
+
ev.stopPropagation();
|
|
112
|
+
const path = await createNewFile();
|
|
113
|
+
close(path);
|
|
114
|
+
}}>
|
|
115
|
+
<input
|
|
116
|
+
style="width: 100%;"
|
|
117
|
+
onkeydown={ev => {
|
|
118
|
+
if (ev.keyCode === 27) {
|
|
119
|
+
// catch esc
|
|
120
|
+
ev.stopPropagation();
|
|
121
|
+
ev.preventDefault();
|
|
122
|
+
close();
|
|
123
|
+
}
|
|
124
|
+
refresh();
|
|
125
|
+
}}
|
|
126
|
+
ref={nameInput}
|
|
127
|
+
type="text"
|
|
128
|
+
placeholder="...filename..." />
|
|
129
|
+
</form> : <></>}
|
|
130
|
+
<select
|
|
131
|
+
value={template}
|
|
132
|
+
oninput={ev => {
|
|
133
|
+
ev.preventDefault();
|
|
134
|
+
ev.stopPropagation();
|
|
135
|
+
console.log(ev.target.value);
|
|
136
|
+
settemplate(ev.target.value);
|
|
137
|
+
}}
|
|
138
|
+
style="margin: 0; padding: 0; margin-left:auto; margin-right: var(--file-browser-separation); width: 100%; margin-top: var(--file-browser-separation);">
|
|
139
|
+
{Object.keys(TEMPLATES).map(templateName => <option value={templateName}>{TEMPLATES[templateName].displayName}</option>)}
|
|
140
|
+
</select>
|
|
141
|
+
{TEMPLATES[template].template ? <div
|
|
142
|
+
class="new-dialog-preview">
|
|
143
|
+
<HighlightTextArea
|
|
144
|
+
content={TEMPLATES[template].template(filename, httpPath)}
|
|
145
|
+
language={TEMPLATES[template].language}
|
|
146
|
+
disabled="true" />
|
|
147
|
+
</div> : <></>}
|
|
148
|
+
</div>
|
|
149
|
+
<div class="dialog-footer">
|
|
150
|
+
<button
|
|
151
|
+
class="btn danger"
|
|
152
|
+
onclick={ev => {
|
|
153
|
+
ev.preventDefault();
|
|
154
|
+
ev.stopPropagation();
|
|
155
|
+
close();
|
|
156
|
+
}}>cancel</button>
|
|
157
|
+
<button
|
|
158
|
+
style="margin-left: auto;"
|
|
159
|
+
class="btn active"
|
|
160
|
+
onclick={async (ev) => {
|
|
161
|
+
ev.preventDefault();
|
|
162
|
+
ev.stopPropagation();
|
|
163
|
+
const path = await createNewFile();
|
|
164
|
+
close(path);
|
|
165
|
+
}}>create</button>
|
|
166
|
+
</div>
|
|
167
|
+
</dialog> : <></>}
|
|
168
|
+
</>
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
NewFile.asFragment = true;
|
|
172
|
+
NewFile.shadowInit = false;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as jsx from "@miqro/jsx";
|
|
2
|
+
import JSX from "@miqro/jsx";
|
|
3
|
+
|
|
4
|
+
let scrollTimeout2: any = null;
|
|
5
|
+
|
|
6
|
+
export function useScroll(): [{ scrollTop: string; scrollLeft: string }, (newScroll: { scrollTop: number; scrollLeft: number; }, inmediate?: boolean) => void] {
|
|
7
|
+
const [scrollTop, setscrollTop] = jsx.useQuery("scrollTop", "0");
|
|
8
|
+
const [scrollLeft, setscrollLeft] = jsx.useQuery("scrollLeft", "0");
|
|
9
|
+
|
|
10
|
+
return [{
|
|
11
|
+
scrollTop: scrollTop as string,
|
|
12
|
+
scrollLeft: scrollLeft as string
|
|
13
|
+
}, (newScroll: { scrollTop: number; scrollLeft: number; }, inmediate?: boolean) => {
|
|
14
|
+
clearTimeout(scrollTimeout2);
|
|
15
|
+
if (inmediate) {
|
|
16
|
+
setscrollTop(String(newScroll.scrollTop));
|
|
17
|
+
setscrollLeft(String(newScroll.scrollLeft));
|
|
18
|
+
} else {
|
|
19
|
+
scrollTimeout2 = setTimeout(() => {
|
|
20
|
+
setscrollTop(String(newScroll.scrollTop));
|
|
21
|
+
setscrollLeft(String(newScroll.scrollLeft));
|
|
22
|
+
}, 1000);
|
|
23
|
+
}
|
|
24
|
+
}]
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as jsx from "@miqro/jsx";
|
|
2
|
+
import JSX from "@miqro/jsx";
|
|
3
|
+
|
|
4
|
+
interface StartPageProps {
|
|
5
|
+
togglePanel: (panel: string) => void;
|
|
6
|
+
isPanelVisible: (panel: string) => boolean;
|
|
7
|
+
disableLog?: boolean; disablePreview?: boolean; disableReload?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function StartPage(props: StartPageProps) {
|
|
11
|
+
|
|
12
|
+
jsx.useEffect(() => {
|
|
13
|
+
if (!props.isPanelVisible("left")) {
|
|
14
|
+
props.togglePanel("left");
|
|
15
|
+
}
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
return <div class={`start-page`}>
|
|
19
|
+
<div class="row center">
|
|
20
|
+
<h1>Start Page</h1>
|
|
21
|
+
</div>
|
|
22
|
+
<br />
|
|
23
|
+
<div class="row center">
|
|
24
|
+
<div class="row">
|
|
25
|
+
<div class={`toggle-panel-button left-side-panel-button ${props.isPanelVisible("left") ? "active" : ""}`}
|
|
26
|
+
onclick={ev => {
|
|
27
|
+
ev.preventDefault();
|
|
28
|
+
props.togglePanel("left");
|
|
29
|
+
}}
|
|
30
|
+
></div>
|
|
31
|
+
<br />
|
|
32
|
+
{props.disableLog ? <></> : <div class={`toggle-panel-button bottom-side-panel-button ${props.isPanelVisible("bottom") ? "active" : ""}`}
|
|
33
|
+
onclick={ev => {
|
|
34
|
+
ev.preventDefault();
|
|
35
|
+
props.togglePanel("bottom");
|
|
36
|
+
}}
|
|
37
|
+
></div>}
|
|
38
|
+
<br />
|
|
39
|
+
{props.disablePreview ? <></> : <div class={`toggle-panel-button right-side-panel-button ${props.isPanelVisible("right") ? "active" : ""}`}
|
|
40
|
+
onclick={ev => {
|
|
41
|
+
ev.preventDefault();
|
|
42
|
+
props.togglePanel("right");
|
|
43
|
+
}}
|
|
44
|
+
></div>}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<br />
|
|
48
|
+
</div>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
StartPage.asFragment = true;
|
|
52
|
+
StartPage.shadowInit = false;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineRoute, JSONParser } from "@miqro/core";
|
|
2
|
+
import { unlinkSync } from "node:fs";
|
|
3
|
+
import { getPath } from "./read.api.js";
|
|
4
|
+
|
|
5
|
+
export default defineRoute({
|
|
6
|
+
description: "admin editor file deletion endpoint",
|
|
7
|
+
method: "POST",
|
|
8
|
+
path: "/delete",
|
|
9
|
+
middleware: [JSONParser()],
|
|
10
|
+
request: {
|
|
11
|
+
body: {
|
|
12
|
+
path: "string"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
response: {
|
|
16
|
+
status: [200, 400],
|
|
17
|
+
body: {
|
|
18
|
+
message: "string"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
handler: async (req, res) => {
|
|
22
|
+
const { path } = req.body;
|
|
23
|
+
await deleteFile(path);
|
|
24
|
+
return res?.json({
|
|
25
|
+
message: "OK"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function deleteFile(path: string) {
|
|
31
|
+
unlinkSync(getPath(path));
|
|
32
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { APIRoute, defineRoute, JSONParser } from "@miqro/core";
|
|
2
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { SUPPORTED_LANGUAGES } from "../../../../../common/constants.js";
|
|
4
|
+
import { relative, resolve } from "node:path";
|
|
5
|
+
import { getLanguage } from "./scan.api.js";
|
|
6
|
+
import { BASE_PATH } from "../../../../../common/constants.server.js";
|
|
7
|
+
|
|
8
|
+
export default defineRoute({
|
|
9
|
+
method: "POST",
|
|
10
|
+
path: "/read",
|
|
11
|
+
description: "admin editor file read endpoint",
|
|
12
|
+
middleware: [JSONParser()],
|
|
13
|
+
request: {
|
|
14
|
+
body: {
|
|
15
|
+
path: "string"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
response: {
|
|
19
|
+
status: [200, 400],
|
|
20
|
+
body: {
|
|
21
|
+
contents: "string",
|
|
22
|
+
path: "string"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
handler: async (req, res) => {
|
|
26
|
+
const { path } = req.body;
|
|
27
|
+
const contents = readFile(path);
|
|
28
|
+
return res?.json({
|
|
29
|
+
contents,
|
|
30
|
+
path
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export function readFile(path: string) {
|
|
36
|
+
const filePath = getPath(path);
|
|
37
|
+
const language = getLanguage(filePath);
|
|
38
|
+
if (SUPPORTED_LANGUAGES.includes(language)) {
|
|
39
|
+
const contents = readFileSync(filePath).toString();
|
|
40
|
+
return contents;
|
|
41
|
+
} else {
|
|
42
|
+
throw new Error("unsupported file format");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getPath(path: string) {
|
|
48
|
+
const realPath = realpathSync(resolve(BASE_PATH, path));
|
|
49
|
+
|
|
50
|
+
if (relative(BASE_PATH, realPath).startsWith("..")) {
|
|
51
|
+
throw new Error("invalid path! [" + path + "]");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return realPath;
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { APIRoute, defineRoute, JSONParser } from "@miqro/core";
|
|
2
|
+
import { existsSync, mkdirSync, renameSync } from "node:fs";
|
|
3
|
+
import { getPath } from "./read.api.js";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
|
|
6
|
+
export default defineRoute({
|
|
7
|
+
middleware: [JSONParser()],
|
|
8
|
+
method: "POST",
|
|
9
|
+
description: "admin editor file rename endpoint",
|
|
10
|
+
path: "/rename",
|
|
11
|
+
request: {
|
|
12
|
+
body: {
|
|
13
|
+
path: "string",
|
|
14
|
+
newName: "string"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
response: {
|
|
18
|
+
status: [200, 400],
|
|
19
|
+
body: {
|
|
20
|
+
message: "string"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
handler: async (req, res) => {
|
|
24
|
+
const { path, newName } = req.body;
|
|
25
|
+
await rename(path, newName);
|
|
26
|
+
return res?.json({
|
|
27
|
+
message: "OK"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export async function rename(path: string, newName: string) {
|
|
33
|
+
if (existsSync(path) && !existsSync(newName)) {
|
|
34
|
+
mkdirSync(dirname(getPath(newName)), {
|
|
35
|
+
recursive: true
|
|
36
|
+
});
|
|
37
|
+
renameSync(getPath(path), getPath(newName));
|
|
38
|
+
} else {
|
|
39
|
+
throw new Error("invalid paths");
|
|
40
|
+
}
|
|
41
|
+
}
|