markupeditor 0.9.49 → 0.9.50
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/bin/muedit.js +9 -48
- package/bin/static-server.js +159 -0
- package/dist/custom-elements.json +5210 -0
- package/package.json +1 -2
package/bin/muedit.js
CHANGED
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import {argv} from 'node:process'
|
|
7
7
|
import {parseArgs} from 'node:util'
|
|
8
|
-
import express from 'express'
|
|
9
8
|
import path from 'node:path'
|
|
9
|
+
import {createMueditServer} from './static-server.js'
|
|
10
10
|
|
|
11
11
|
const __dirname = import.meta.dirname
|
|
12
12
|
|
|
13
|
-
const app = express()
|
|
14
|
-
|
|
15
13
|
const options = {
|
|
16
14
|
'port': {
|
|
17
15
|
type: 'string',
|
|
@@ -31,55 +29,18 @@ if ((values.length > 1) || (!values.port) || (positionals.length > 1)) {
|
|
|
31
29
|
} else {
|
|
32
30
|
let port = parseInt(values.port)
|
|
33
31
|
let filename = (positionals.length > 0) ? positionals[0] : null
|
|
34
|
-
let filenameAttribute = (filename) ? `filename="${filename}"` : '' // Empty for a new document
|
|
35
|
-
// Note that `base` is set to the filename path automatically
|
|
36
|
-
let placeholder = 'Edit document...'
|
|
37
32
|
|
|
38
33
|
// The web component is always located relative to node's `__dirname`,
|
|
39
|
-
// the location where muedit.js resides, which
|
|
34
|
+
// the location where muedit.js resides, which serving binDir statically
|
|
40
35
|
// ensures is available no matter what `process.cwd()` is.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Allow the userscript and userstyles to load
|
|
47
|
-
app.use(express.static(__dirname))
|
|
48
|
-
|
|
49
|
-
// Allow the relative references inside of the edited document to work
|
|
50
|
-
app.use(express.static(`${process.cwd()}/`, {index: false}))
|
|
51
|
-
|
|
52
|
-
// For parsing application/json
|
|
53
|
-
app.use(express.json())
|
|
54
|
-
|
|
55
|
-
// Load when loading http://localhost:${port}
|
|
56
|
-
app.get('/', (req, res) => {
|
|
57
|
-
res.send(
|
|
58
|
-
`
|
|
59
|
-
<!DOCTYPE html>
|
|
60
|
-
<html lang="en">
|
|
61
|
-
<head>
|
|
62
|
-
<title>${filename ?? "MarkupEditor"}</title>
|
|
63
|
-
<meta name="viewport" charset="utf-8" content="width=device-width, initial-scale=1.0">
|
|
64
|
-
</head>
|
|
65
|
-
<body>
|
|
66
|
-
<markup-editor
|
|
67
|
-
placeholder="${placeholder}"
|
|
68
|
-
${filenameAttribute}
|
|
69
|
-
userscript="/filetoolbar.js"
|
|
70
|
-
userstyle="/filetoolbar.css"
|
|
71
|
-
prepend="FileToolbar"
|
|
72
|
-
>
|
|
73
|
-
</markup-editor>
|
|
74
|
-
<input id="docpicker" type="file" accept=".html" style="display: none">
|
|
75
|
-
<script src="${webcomponent}" type="module"></script>
|
|
76
|
-
</body>
|
|
77
|
-
</html>
|
|
78
|
-
`
|
|
79
|
-
)
|
|
36
|
+
const server = createMueditServer({
|
|
37
|
+
distDir: path.join(__dirname, "../dist"), // Allow references to dist from bin to work, to load the web component
|
|
38
|
+
binDir: __dirname, // Allow the userscript and userstyles to load
|
|
39
|
+
cwd: process.cwd(), // Allow the relative references inside of the edited document to work
|
|
40
|
+
filename,
|
|
80
41
|
})
|
|
81
42
|
|
|
82
|
-
|
|
43
|
+
server.listen(port, () => {
|
|
83
44
|
console.log(`Server listening at http://localhost:${port}`)
|
|
84
45
|
})
|
|
85
|
-
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-free static file serving on node:http, shared by muedit.js
|
|
3
|
+
* (createMueditServer) and docs/index.js (createStaticServer). Previously
|
|
4
|
+
* each had its own express-based server.
|
|
5
|
+
*/
|
|
6
|
+
import { createServer } from 'node:http'
|
|
7
|
+
import { readFile, stat } from 'node:fs/promises'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
|
|
10
|
+
export const CONTENT_TYPES = {
|
|
11
|
+
'.html': 'text/html; charset=utf-8',
|
|
12
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
13
|
+
'.css': 'text/css; charset=utf-8',
|
|
14
|
+
'.json': 'application/json; charset=utf-8',
|
|
15
|
+
'.svg': 'image/svg+xml',
|
|
16
|
+
'.png': 'image/png',
|
|
17
|
+
'.jpg': 'image/jpeg',
|
|
18
|
+
'.jpeg': 'image/jpeg',
|
|
19
|
+
'.gif': 'image/gif',
|
|
20
|
+
'.webp': 'image/webp',
|
|
21
|
+
'.ico': 'image/vnd.microsoft.icon',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function contentTypeFor(filePath) {
|
|
25
|
+
return CONTENT_TYPES[path.extname(filePath).toLowerCase()] ?? 'application/octet-stream'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve `urlPath` against `root`, refusing to resolve outside of it.
|
|
30
|
+
* Returns the absolute file path if a regular file is found, or null.
|
|
31
|
+
*/
|
|
32
|
+
export async function resolveStaticFile(root, urlPath, { index = true } = {}) {
|
|
33
|
+
const resolvedRoot = path.resolve(root)
|
|
34
|
+
const decoded = decodeURIComponent(urlPath.split('?')[0])
|
|
35
|
+
let candidate = path.resolve(resolvedRoot, '.' + decoded)
|
|
36
|
+
if (candidate !== resolvedRoot && !candidate.startsWith(resolvedRoot + path.sep)) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
let stats = await stat(candidate)
|
|
41
|
+
if (stats.isDirectory()) {
|
|
42
|
+
if (!index) return null
|
|
43
|
+
candidate = path.join(candidate, 'index.html')
|
|
44
|
+
stats = await stat(candidate)
|
|
45
|
+
}
|
|
46
|
+
return stats.isFile() ? candidate : null
|
|
47
|
+
} catch {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function renderIndexHtml({ filename, webcomponent }) {
|
|
53
|
+
const filenameAttribute = filename ? `filename="${filename}"` : '' // Empty for a new document
|
|
54
|
+
// Note that `base` is set to the filename path automatically
|
|
55
|
+
const placeholder = 'Edit document...'
|
|
56
|
+
return `
|
|
57
|
+
<!DOCTYPE html>
|
|
58
|
+
<html lang="en">
|
|
59
|
+
<head>
|
|
60
|
+
<title>${filename ?? "MarkupEditor"}</title>
|
|
61
|
+
<meta name="viewport" charset="utf-8" content="width=device-width, initial-scale=1.0">
|
|
62
|
+
</head>
|
|
63
|
+
<body>
|
|
64
|
+
<markup-editor
|
|
65
|
+
placeholder="${placeholder}"
|
|
66
|
+
${filenameAttribute}
|
|
67
|
+
userscript="/filetoolbar.js"
|
|
68
|
+
userstyle="/filetoolbar.css"
|
|
69
|
+
prepend="FileToolbar"
|
|
70
|
+
>
|
|
71
|
+
</markup-editor>
|
|
72
|
+
<input id="docpicker" type="file" accept=".html" style="display: none">
|
|
73
|
+
<script src="${webcomponent}" type="module"></script>
|
|
74
|
+
</body>
|
|
75
|
+
</html>
|
|
76
|
+
`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Serve `req`/`res` from the first matching root in `staticRoots`
|
|
81
|
+
* (`[{root, index}]`, checked in order), 404 if none match. Shared by
|
|
82
|
+
* createMueditServer and createStaticServer.
|
|
83
|
+
*/
|
|
84
|
+
async function serveFromRoots(staticRoots, req, res) {
|
|
85
|
+
const urlPath = req.url.split('?')[0]
|
|
86
|
+
|
|
87
|
+
for (const { root, index } of staticRoots) {
|
|
88
|
+
const filePath = await resolveStaticFile(root, urlPath, { index })
|
|
89
|
+
if (filePath) {
|
|
90
|
+
try {
|
|
91
|
+
const data = await readFile(filePath)
|
|
92
|
+
res.writeHead(200, { 'Content-Type': contentTypeFor(filePath) })
|
|
93
|
+
res.end(req.method === 'HEAD' ? undefined : data)
|
|
94
|
+
} catch {
|
|
95
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' })
|
|
96
|
+
res.end('Internal Server Error')
|
|
97
|
+
}
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
|
103
|
+
res.end('Not Found')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build (but do not start) the muedit HTTP server. Static roots are checked
|
|
108
|
+
* in the same priority order the previous express.static() chain used:
|
|
109
|
+
* distDir (the built web component), binDir (userscript/userstyle pair),
|
|
110
|
+
* then cwd (the edited document's own relative assets, no directory index).
|
|
111
|
+
* `/` is special-cased ahead of the static chain to serve the dynamic,
|
|
112
|
+
* templated editor shell instead of a file.
|
|
113
|
+
*/
|
|
114
|
+
export function createMueditServer({ distDir, binDir, cwd, filename, webcomponent = '/markup-editor.js' }) {
|
|
115
|
+
const staticRoots = [
|
|
116
|
+
{ root: distDir, index: true },
|
|
117
|
+
{ root: binDir, index: true },
|
|
118
|
+
{ root: cwd, index: false },
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
return createServer(async (req, res) => {
|
|
122
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
123
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' })
|
|
124
|
+
res.end('Method Not Allowed')
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const urlPath = req.url.split('?')[0]
|
|
129
|
+
|
|
130
|
+
if (urlPath === '/') {
|
|
131
|
+
const body = renderIndexHtml({ filename, webcomponent })
|
|
132
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
133
|
+
res.end(req.method === 'HEAD' ? undefined : body)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await serveFromRoots(staticRoots, req, res)
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Build (but do not start) a plain single-root static file server, with the
|
|
143
|
+
* default directory index (root/index.html served for `/`) -- what
|
|
144
|
+
* docs/index.js needs, as distinct from createMueditServer's multi-root
|
|
145
|
+
* chain and dynamic templated `/` shell.
|
|
146
|
+
*/
|
|
147
|
+
export function createStaticServer({ root }) {
|
|
148
|
+
const staticRoots = [{ root, index: true }]
|
|
149
|
+
|
|
150
|
+
return createServer(async (req, res) => {
|
|
151
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
152
|
+
res.writeHead(405, { 'Content-Type': 'text/plain' })
|
|
153
|
+
res.end('Method Not Allowed')
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await serveFromRoots(staticRoots, req, res)
|
|
158
|
+
})
|
|
159
|
+
}
|