diff --git a/README.md b/README.md index 3b78bd05..15530a67 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,10 @@ turn your phone or raspi into a portable file server with resumable uploads/down * [ ] download as zip * [x] volumes * [x] accounts +* [x] markdown viewer +* [ ] markdown editor? w -summary: close to beta +summary: it works! you can use it! (but technically not even close to beta) # dependencies @@ -82,6 +84,7 @@ in the `scripts` folder: roughly sorted by priority +* sortable browser columns * up2k handle filename too long * up2k fails on empty files? alert then stuck * unexpected filepath on dupe up2k diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 8b62937e..cf2d52f1 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -706,6 +706,48 @@ class HttpCli(object): self.log(logmsg) return True + def tx_md(self, fs_path): + logmsg = "{:4} {} ".format("", self.req) + html_path = os.path.join(E.mod, "web/md.html") + + st = os.stat(fsenc(fs_path)) + sz_md = st.st_size + ts_md = st.st_mtime + + st = os.stat(fsenc(html_path)) + ts_html = st.st_mtime + + file_ts = max(ts_md, ts_html) + file_lastmod, do_send = self._chk_lastmod(file_ts) + self.out_headers["Last-Modified"] = file_lastmod + status = 200 if do_send else 304 + + targs = { + "title": html_escape(self.vpath, quote=False), + "md": "", + } + sz_html = len(self.conn.tpl_md.render(**targs).encode("utf-8")) + self.send_headers(sz_html + sz_md, status) + + logmsg += str(status) + if self.mode == "HEAD" or not do_send: + self.log(logmsg) + return True + + with open(fsenc(fs_path), "rb") as f: + md = f.read() + + targs["md"] = md.decode("utf-8", "replace") + html = self.conn.tpl_md.render(**targs).encode("utf-8") + try: + self.s.sendall(html) + except: + self.log(logmsg + " \033[31md/c\033[0m") + return False + + self.log(logmsg + " " + str(len(html))) + return True + def tx_mounts(self): rvol = [x + "/" if x else x for x in self.rvol] wvol = [x + "/" if x else x for x in self.wvol] @@ -735,6 +777,9 @@ class HttpCli(object): raise Pebkac(404) if not os.path.isdir(fsenc(abspath)): + if abspath.endswith(".md") and "raw" not in self.uparam: + return self.tx_md(abspath) + return self.tx_file(abspath) fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname) diff --git a/copyparty/httpconn.py b/copyparty/httpconn.py index 704f1b4c..e1bda616 100644 --- a/copyparty/httpconn.py +++ b/copyparty/httpconn.py @@ -35,6 +35,7 @@ class HttpConn(object): self.tpl_mounts = env.get_template("splash.html") self.tpl_browser = env.get_template("browser.html") self.tpl_msg = env.get_template("msg.html") + self.tpl_md = env.get_template("md.html") def respath(self, res_name): return os.path.join(E.mod, "web", res_name) diff --git a/copyparty/web/md.css b/copyparty/web/md.css new file mode 100644 index 00000000..58f5543c --- /dev/null +++ b/copyparty/web/md.css @@ -0,0 +1,375 @@ +html, body { + color: #333; + background: #eee; + font-family: sans-serif; + line-height: 1.5em; +} +#mw { + width: 48.5em; + margin: 0 auto; + margin-bottom: 6em; +} +pre, code, a { + color: #480; + background: #f7f7f7; + border: .07em solid #ddd; + border-radius: .2em; + padding: .1em .3em; + margin: 0 .1em; +} +code { + font-size: .96em; +} +pre, code { + font-family: monospace, monospace; + white-space: pre-wrap; + word-break: break-all; +} +pre { + counter-reset: precode; +} +pre code { + counter-increment: precode; + display: inline-block; + margin: 0 -.3em; + padding: .4em .5em; + border: none; + border-bottom: 1px solid #cdc; + min-width: calc(100% - .6em); + line-height: 1.1em; +} +pre code:last-child { + border-bottom: none; +} +pre code:before { + content: counter(precode); + -webkit-user-select: none; + display: inline-block; + text-align: right; + font-size: .75em; + color: #48a; + width: 4em; + padding-right: 1.5em; + margin-left: -5.5em; +} +pre code:hover { + background: #fec; + color: #360; +} +h1, h2 { + line-height: 1.5em; +} +h1 { + font-size: 1.7em; + text-align: center; + border: 1em solid #777; + border-width: .05em 0; + margin: 3em 0; +} +h2 { + font-size: 1.5em; + font-weight: normal; + background: #f7f7f7; + border-top: .07em solid #fff; + border-bottom: .07em solid #bbb; + border-radius: .5em .5em 0 0; + padding-left: .4em; + margin-top: 3em; +} +h1 a, h3 a, h5 a, +h2 a, h4 a, h6 a { + color: inherit; + background: none; + border: none; + padding: 0; + margin: 0; +} +#m ul, +#m ol { + border-left: .3em solid #ddd; +} +#m>ul, +#m>ol { + border-color: #bbb; +} +#m ul>li { + list-style-type: disc; +} +#m ul>li, +#m ol>li { + margin: .7em 0; +} +p>em, +li>em { + color: #c50; + padding: .1em; + border-bottom: .1em solid #bbb; +} +blockquote { + font-family: serif; + background: #f7f7f7; + border: .07em dashed #ccc; + padding: 0 2em; + margin: 1em 0; +} +small { + opacity: .8; +} +#toc { + width: 48.5em; + margin: 0 auto; +} +#toc ul { + padding-left: 1em; +} +#toc>ul { + text-align: left; + padding-left: .5em; +} +#toc li { + list-style-type: none; + line-height: 1.2em; + margin: .5em 0; +} +#toc a { + color: #057; + border: none; + background: none; + display: block; + margin-left: -.3em; + padding: .2em .3em; +} +#toc a.act { + color: #fff; + background: #07a; +} +.todo_pend, +.todo_done { + z-index: 99; + position: relative; + display: inline-block; + font-family: monospace, monospace; + font-weight: bold; + font-size: 1.3em; + line-height: .1em; + margin: -.5em 0 -.5em -.85em; + top: .1em; + color: #b29; +} +.todo_done { + color: #6b3; + text-shadow: .02em 0 0 #6b3; +} +table { + border-collapse: collapse; +} +td { + padding: .2em .5em; + border: .12em solid #aaa; +} +th { + border: .12em solid #aaa; +} +blink { + animation: blinker .7s cubic-bezier(.9, 0, .1, 1) infinite; +} +@keyframes blinker { + 10% { + opacity: 0; + } + 60% { + opacity: 1; + } +} +@media screen { + a { + color: #fff; + background: #39b; + text-decoration: none; + padding: 0 .3em; + border: none; + border-bottom: .07em solid #079; + } + h2 { + color: #fff; + background: #555; + margin-top: 2em; + border-bottom: .22em solid #999; + border-top: none; + } + h1 { + color: #fff; + background: #444; + font-weight: normal; + border-top: .4em solid #fb0; + border-bottom: .4em solid #777; + border-radius: 0 1em 0 1em; + margin: 3em 0 1em 0; + padding: .5em 0; + } + #mn { + text-shadow: 1px 1px 0 #000; + xfont-variant: small-caps; + font-weight: normal; + margin: 1.3em 0 0 0; + font-size: 1.4em; + } + #mn a { + background: #2c2c2c; + margin: 0 0 0 -.2em; + padding: 0 0 0 .4em; + /* ie: */ + border-bottom: .1em solid #777\9; + margin-right: 1em\9; + } + #mn a:first-child { + padding-left: .5em; + } + #mn a:last-child { + padding-right: .5em; + } + #mn a:not(:last-child):after { + content: ''; + width: 1.05em; + height: 1.05em; + margin: -.2em .3em -.2em -.4em; + display: inline-block; + border: 1px solid rgba(255,224,192,0.3); + border-width: .05em .05em 0 0; + transform: rotate(45deg); + background: linear-gradient(45deg, rgba(0,0,0,0) 40%, rgba(0,0,0,0.25) 75%, rgba(0,0,0,0.35)); + } + #mn a:hover { + color: #fff; + background: linear-gradient(90deg, rgba(0,0,0,0), rgba(0,0,0,0.2), rgba(0,0,0,0)); + } + #mh { + margin: 1.5em 0; + } + + + + html.dark, + html.dark body { + background: #222; + color: #ccc; + } + html.dark #toc a { + color: #ccc; + border-left: .4em solid #444; + border-bottom: .1em solid #333; + } + html.dark #toc a.act { + color: #fff; + border-left: .4em solid #3ad; + } + html.dark #toc li { + border-width: 0; + } + html.dark #m a, + html.dark #mh a { + background: #057; + } + html.dark #m h1 a, html.dark #m h4 a, + html.dark #m h2 a, html.dark #m h5 a, + html.dark #m h3 a, html.dark #m h6 a { + color: inherit; + background: none; + } + html.dark pre, + html.dark code { + color: #8c0; + background: #1a1a1a; + border: .07em solid #333; + } + html.dark #m ul, + html.dark #m ol { + border-color: #444; + } + html.dark #m>ul, + html.dark #m>ol { + border-color: #555; + } + html.dark p>em, + html.dark li>em { + color: #f94; + border-color: #666; + } + html.dark h1 { + background: #383838; + border-top: .4em solid #b80; + border-bottom: .4em solid #4c4c4c; + } + html.dark h2 { + background: #444; + border-bottom: .22em solid #555; + } + html.dark td, + html.dark th { + border-color: #444; + } + html.dark blockquote { + background: #282828; + border: .07em dashed #444; + } +} +@media screen and (min-width: 64em) { + #mw { + margin-left: 14em; + margin-left: calc(100% - 50em); + } + #toc { + width: 13em; + width: calc(100% - 52.3em); + background: #eee; + position: fixed; + top: 0; + left: 0; + height: 100%; + overflow-y: auto; + padding: 0; + margin: 0; + box-shadow: 0 0 1em #ccc; + scrollbar-color: #eb0 #f7f7f7; + xscrollbar-width: thin; + } + #toc li { + border-left: .3em solid #ccc; + } + #toc::-webkit-scrollbar-track { + background: #f7f7f7; + } + #toc::-webkit-scrollbar { + background: #f7f7f7; + width: .8em; + } + #toc::-webkit-scrollbar-thumb { + background: #eb0; + } + + + + html.dark #toc { + background: #282828; + box-shadow: 0 0 1em #181818; + scrollbar-color: #b80 #282828; + } +} +@media screen and (min-width: 84em) { + #toc { width: 30em } + #mw { margin-left: 32em } +} +@media print { + a { + color: #079; + text-decoration: none; + border-bottom: .07em solid #4ac; + padding: 0 .3em; + } + #toc>ul { + border-left: .1em solid #84c4dd; + } + #mn, #mh { + display: none; + } +} \ No newline at end of file diff --git a/copyparty/web/md.html b/copyparty/web/md.html new file mode 100644 index 00000000..9cbb39fa --- /dev/null +++ b/copyparty/web/md.html @@ -0,0 +1,47 @@ +
+ +');
+ }
+
+ if (!is_header && anchor)
+ anchor.kids.push(elm);
+ }
+ dom_toc.innerHTML = html.join('\n');
+ if (anchor != null)
+ anchors.push(anchor);
+
+ // copy toc links into the toc list
+ var atoc = dom_toc.getElementsByTagName('a');
+ for (var a = 0, aa = anchors.length; a < aa; a++)
+ anchors[a].lnk = atoc[a];
+
+ // collect vertical position of all toc items (headers in document)
+ function freshen_offsets() {
+ var top = window.pageYOffset || document.documentElement.scrollTop;
+ for (var a = anchors.length - 1; a >= 0; a--) {
+ var y = top + anchors[a].elm.getBoundingClientRect().top;
+ y = Math.round(y * 10.0) / 10;
+ if (anchors[a].y === y)
+ break;
+
+ anchors[a].y = y;
+ }
+ }
+
+ // hilight the correct toc items + scroll into view
+ function freshen_toclist() {
+ if (anchors.length == 0)
+ return;
+
+ var ptop = window.pageYOffset || document.documentElement.scrollTop;
+ var hit = -1;
+ for (var a = 0; a < anchors.length; a++) {
+ if (anchors[a].y >= ptop - 8) { //???
+ hit = a;
+ break;
+ }
+ }
+
+ var links = dom_toc.getElementsByTagName('a');
+ if (!anchors[hit].active) {
+ for (var a = 0; a < anchors.length; a++) {
+ if (anchors[a].active) {
+ anchors[a].active = false;
+ links[a].setAttribute('class', '');
+ }
+ }
+ anchors[hit].active = true;
+ links[hit].setAttribute('class', 'act');
+ }
+
+ var pane_height = parseInt(getComputedStyle(dom_toc).height);
+ var link_bounds = links[hit].getBoundingClientRect();
+ var top = link_bounds.top - (pane_height / 6);
+ var btm = link_bounds.bottom + (pane_height / 6);
+ if (top < 0)
+ dom_toc.scrollTop -= -top;
+ else if (btm > pane_height)
+ dom_toc.scrollTop += btm - pane_height;
+ }
+
+ function refresh() {
+ freshen_offsets();
+ freshen_toclist();
+ }
+
+ return { "refresh": refresh }
+}
+
+
+// "main" :p
+convert_markdown(dom_md.value);
+var toc = init_toc();
+
+
+// scroll handler
+(function () {
+ var timer_active = false;
+ var final = null;
+
+ function onscroll() {
+ clearTimeout(final);
+ timer_active = false;
+ toc.refresh();
+ }
+ onscroll();
+
+ window.onscroll = function () {
+ // long timeout: scroll ended
+ clearTimeout(final);
+ final = setTimeout(onscroll, 100);
+
+ // short timeout: continuous updates
+ if (timer_active)
+ return;
+
+ timer_active = true;
+ setTimeout(onscroll, 10);
+ };
+})();
diff --git a/scripts/deps-docker/Dockerfile b/scripts/deps-docker/Dockerfile
index bce1e573..b7c64ca6 100644
--- a/scripts/deps-docker/Dockerfile
+++ b/scripts/deps-docker/Dockerfile
@@ -1,25 +1,50 @@
FROM alpine:3.11
WORKDIR /z
ENV ver_asmcrypto=2821dd1dedd1196c378f5854037dda5c869313f3 \
+ ver_markdownit=10.0.0 \
+ ver_showdown=1.9.1 \
+ ver_marked=1.0.0 \
ver_ogvjs=1.6.1
# download
RUN apk add make g++ git bash npm patch wget tar pigz brotli gzip unzip \
- && wget https://github.com/brion/ogv.js/releases/download/$ver_ogvjs/ogvjs-$ver_ogvjs.zip \
- && wget https://github.com/asmcrypto/asmcrypto.js/archive/$ver_asmcrypto.tar.gz \
- && unzip ogvjs-$ver_ogvjs.zip \
- && tar -xf $ver_asmcrypto.tar.gz \
- && cd asmcrypto.js-$ver_asmcrypto \
- && npm install \
+ && wget https://github.com/brion/ogv.js/releases/download/$ver_ogvjs/ogvjs-$ver_ogvjs.zip -O ogvjs.zip \
+ && wget https://github.com/asmcrypto/asmcrypto.js/archive/$ver_asmcrypto.tar.gz -O asmcrypto.tgz \
+ && wget https://github.com/markedjs/marked/archive/v$ver_marked.tar.gz -O marked.tgz \
+ && unzip ogvjs.zip \
+ && (tar -xf asmcrypto.tgz \
+ && cd asmcrypto.js-$ver_asmcrypto \
+ && npm install ) \
+ && (tar -xf marked.tgz \
+ && cd marked-$ver_marked \
+ && npm install \
+ && npm i grunt uglify-js -g ) \
&& mkdir /z/dist
+
+# uncomment if you wanna test the abandoned markdown converters
+#ENV build_abandoned=1
+
+
+RUN [ $build_abandoned ] || exit 0; \
+ git clone --depth 1 --branch $ver_showdown https://github.com/showdownjs/showdown/ \
+ && wget https://github.com/markdown-it/markdown-it/archive/$ver_markdownit.tar.gz -O markdownit.tgz \
+ && (cd showdown \
+ && npm install \
+ && npm i grunt -g ) \
+ && (tar -xf markdownit.tgz \
+ && cd markdown-it-$ver_markdownit \
+ && npm install )
+
+
# build asmcrypto
RUN cd asmcrypto.js-$ver_asmcrypto \
&& echo "export { Sha512 } from './hash/sha512/sha512';" > src/entry-export_all.ts \
&& node -r esm build.js \
&& mv asmcrypto.all.es5.js /z/dist/sha512.js
+
# build ogvjs
RUN cd ogvjs-$ver_ogvjs \
&& cp -pv \
@@ -40,8 +65,90 @@ RUN cd ogvjs-$ver_ogvjs \
dynamicaudio.swf \
/z/dist
+
+# build marked
+COPY marked.patch /z/
+RUN cd marked-$ver_marked \
+ && patch -p1 < /z/marked.patch \
+ && npm run build \
+ && cp -pv marked.min.js /z/dist/marked.js \
+ && cp -pv lib/marked.js /z/dist/marked.full.js
+# && npm run test \
+
+
+# build showdown (abandoned; disabled by default)
+COPY showdown.patch /z/
+RUN [ $build_abandoned ] || exit 0; \
+ cd showdown \
+ && rm -rf bin dist \
+# # remove ellipsis plugin \
+ && rm \
+ src/subParsers/ellipsis.js \
+ test/cases/ellipsis* \
+# # remove html-to-md converter \
+ && rm \
+ test/node/testsuite.makemd.js \
+ test/node/showdown.Converter.makeMarkdown.js \
+# # remove emojis \
+ && rm src/subParsers/emoji.js \
+ && awk '/^showdown.helper.emojis/ {o=1} !o; /^\}/ {o=0}' \
+ >f marked.patch; make && printf '%d ' $(wc -c <$f) $(gzip -d <$f | wc -c); echo
diff --git a/scripts/deps-docker/markdown-it.patch b/scripts/deps-docker/markdown-it.patch
new file mode 100644
index 00000000..bf096e7c
--- /dev/null
+++ b/scripts/deps-docker/markdown-it.patch
@@ -0,0 +1,10 @@
+diff -NarU1 markdown-it-10.0.0-orig/lib/common/entities.js markdown-it-10.0.0-edit/lib/common/entities.js
+--- markdown-it-10.0.0-orig/lib/common/entities.js 2019-09-10 21:39:58.000000000 +0000
++++ markdown-it-10.0.0-edit/lib/common/entities.js 2020-04-26 10:24:33.043023331 +0000
+@@ -5,2 +5,5 @@
+ /*eslint quotes:0*/
+-module.exports = require('entities/lib/maps/entities.json');
++//module.exports = require('entities/lib/maps/entities.json');
++module.exports = {
++ "amp": "&", "quot": "\"", "gt": ">", "lt": "<"
++}
diff --git a/scripts/deps-docker/marked.patch b/scripts/deps-docker/marked.patch
new file mode 100644
index 00000000..e308cf2e
--- /dev/null
+++ b/scripts/deps-docker/marked.patch
@@ -0,0 +1,269 @@
+diff -NarU1 marked-1.0.0-orig/src/defaults.js marked-1.0.0-edit/src/defaults.js
+--- marked-1.0.0-orig/src/defaults.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/src/defaults.js 2020-04-25 19:16:56.124621393 +0000
+@@ -9,10 +9,6 @@
+ langPrefix: 'language-',
+- mangle: true,
+ pedantic: false,
+ renderer: null,
+- sanitize: false,
+- sanitizer: null,
+ silent: false,
+ smartLists: false,
+- smartypants: false,
+ tokenizer: null,
+diff -NarU1 marked-1.0.0-orig/src/helpers.js marked-1.0.0-edit/src/helpers.js
+--- marked-1.0.0-orig/src/helpers.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/src/helpers.js 2020-04-25 18:58:43.001320210 +0000
+@@ -65,16 +65,3 @@
+ const originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
+-function cleanUrl(sanitize, base, href) {
+- if (sanitize) {
+- let prot;
+- try {
+- prot = decodeURIComponent(unescape(href))
+- .replace(nonWordAndColonTest, '')
+- .toLowerCase();
+- } catch (e) {
+- return null;
+- }
+- if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
+- return null;
+- }
+- }
++function cleanUrl(base, href) {
+ if (base && !originIndependentUrl.test(href)) {
+@@ -224,8 +211,2 @@
+
+-function checkSanitizeDeprecation(opt) {
+- if (opt && opt.sanitize && !opt.silent) {
+- console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options');
+- }
+-}
+-
+ module.exports = {
+@@ -240,4 +221,3 @@
+ rtrim,
+- findClosingBracket,
+- checkSanitizeDeprecation
++ findClosingBracket
+ };
+diff -NarU1 marked-1.0.0-orig/src/Lexer.js marked-1.0.0-edit/src/Lexer.js
+--- marked-1.0.0-orig/src/Lexer.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/src/Lexer.js 2020-04-25 22:46:54.107584066 +0000
+@@ -6,3 +6,3 @@
+ * smartypants text replacement
+- */
++ *
+ function smartypants(text) {
+@@ -27,3 +27,3 @@
+ * mangle email addresses
+- */
++ *
+ function mangle(text) {
+@@ -388,3 +388,3 @@
+ // autolink
+- if (token = this.tokenizer.autolink(src, mangle)) {
++ if (token = this.tokenizer.autolink(src)) {
+ src = src.substring(token.raw.length);
+@@ -395,3 +395,3 @@
+ // url (gfm)
+- if (!inLink && (token = this.tokenizer.url(src, mangle))) {
++ if (!inLink && (token = this.tokenizer.url(src))) {
+ src = src.substring(token.raw.length);
+@@ -402,3 +402,3 @@
+ // text
+- if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
++ if (token = this.tokenizer.inlineText(src, inRawBlock)) {
+ src = src.substring(token.raw.length);
+diff -NarU1 marked-1.0.0-orig/src/marked.js marked-1.0.0-edit/src/marked.js
+--- marked-1.0.0-orig/src/marked.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/src/marked.js 2020-04-25 22:42:55.140924439 +0000
+@@ -8,3 +8,2 @@
+ merge,
+- checkSanitizeDeprecation,
+ escape
+@@ -37,3 +36,2 @@
+ opt = merge({}, marked.defaults, opt || {});
+- checkSanitizeDeprecation(opt);
+ const highlight = opt.highlight;
+@@ -101,6 +99,5 @@
+ opt = merge({}, marked.defaults, opt || {});
+- checkSanitizeDeprecation(opt);
+ return Parser.parse(Lexer.lex(src, opt), opt);
+ } catch (e) {
+- e.message += '\nPlease report this to https://github.com/markedjs/marked.';
++ e.message += '\nmake issue @ https://github.com/9001/copyparty';
+ if ((opt || marked.defaults).silent) {
+diff -NarU1 marked-1.0.0-orig/src/Renderer.js marked-1.0.0-edit/src/Renderer.js
+--- marked-1.0.0-orig/src/Renderer.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/src/Renderer.js 2020-04-25 18:59:15.091319265 +0000
+@@ -134,3 +134,3 @@
+ link(href, title, text) {
+- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
++ href = cleanUrl(this.options.baseUrl, href);
+ if (href === null) {
+@@ -147,3 +147,3 @@
+ image(href, title, text) {
+- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
++ href = cleanUrl(this.options.baseUrl, href);
+ if (href === null) {
+diff -NarU1 marked-1.0.0-orig/src/Tokenizer.js marked-1.0.0-edit/src/Tokenizer.js
+--- marked-1.0.0-orig/src/Tokenizer.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/src/Tokenizer.js 2020-04-25 22:47:07.610917004 +0000
+@@ -256,9 +256,6 @@
+ return {
+- type: this.options.sanitize
+- ? 'paragraph'
+- : 'html',
+- raw: cap[0],
+- pre: !this.options.sanitizer
+- && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
+- text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]
++ type: 'html',
++ raw: cap[0],
++ pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style',
++ text: cap[0]
+ };
+@@ -382,5 +379,3 @@
+ return {
+- type: this.options.sanitize
+- ? 'text'
+- : 'html',
++ type: 'html',
+ raw: cap[0],
+@@ -388,7 +383,3 @@
+ inRawBlock,
+- text: this.options.sanitize
+- ? (this.options.sanitizer
+- ? this.options.sanitizer(cap[0])
+- : escape(cap[0]))
+- : cap[0]
++ text: cap[0]
+ };
+@@ -504,3 +495,3 @@
+
+- autolink(src, mangle) {
++ autolink(src) {
+ const cap = this.rules.inline.autolink.exec(src);
+@@ -509,3 +500,3 @@
+ if (cap[2] === '@') {
+- text = escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
++ text = escape(cap[1]);
+ href = 'mailto:' + text;
+@@ -532,3 +523,3 @@
+
+- url(src, mangle) {
++ url(src) {
+ let cap;
+@@ -537,3 +528,3 @@
+ if (cap[2] === '@') {
+- text = escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
++ text = escape(cap[0]);
+ href = 'mailto:' + text;
+@@ -569,3 +560,3 @@
+
+- inlineText(src, inRawBlock, smartypants) {
++ inlineText(src, inRawBlock) {
+ const cap = this.rules.inline.text.exec(src);
+@@ -574,5 +565,5 @@
+ if (inRawBlock) {
+- text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0];
++ text = cap[0];
+ } else {
+- text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
++ text = escape(cap[0]);
+ }
+diff -NarU1 marked-1.0.0-orig/test/bench.js marked-1.0.0-edit/test/bench.js
+--- marked-1.0.0-orig/test/bench.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/test/bench.js 2020-04-25 19:02:27.227980287 +0000
+@@ -34,3 +34,2 @@
+ pedantic: false,
+- sanitize: false,
+ smartLists: false
+@@ -46,3 +45,2 @@
+ pedantic: false,
+- sanitize: false,
+ smartLists: false
+@@ -59,3 +57,2 @@
+ pedantic: false,
+- sanitize: false,
+ smartLists: false
+@@ -71,3 +68,2 @@
+ pedantic: false,
+- sanitize: false,
+ smartLists: false
+@@ -84,3 +80,2 @@
+ pedantic: true,
+- sanitize: false,
+ smartLists: false
+@@ -96,3 +91,2 @@
+ pedantic: true,
+- sanitize: false,
+ smartLists: false
+diff -NarU1 marked-1.0.0-orig/test/specs/run-spec.js marked-1.0.0-edit/test/specs/run-spec.js
+--- marked-1.0.0-orig/test/specs/run-spec.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/test/specs/run-spec.js 2020-04-25 19:05:24.321308408 +0000
+@@ -21,6 +21,2 @@
+ }
+- if (spec.options.sanitizer) {
+- // eslint-disable-next-line no-eval
+- spec.options.sanitizer = eval(spec.options.sanitizer);
+- }
+ (spec.only ? fit : (spec.skip ? xit : it))('should ' + passFail + example, async() => {
+@@ -49,2 +45 @@
+ runSpecs('ReDOS', './redos');
+-runSpecs('Security', './security', false, { silent: true }); // silent - do not show deprecation warning
+diff -NarU1 marked-1.0.0-orig/test/unit/Lexer-spec.js marked-1.0.0-edit/test/unit/Lexer-spec.js
+--- marked-1.0.0-orig/test/unit/Lexer-spec.js 2020-04-21 01:03:48.000000000 +0000
++++ marked-1.0.0-edit/test/unit/Lexer-spec.js 2020-04-25 22:47:27.170916427 +0000
+@@ -464,3 +464,3 @@
+
+- it('sanitize', () => {
++ /*it('sanitize', () => {
+ expectTokens({
+@@ -482,3 +482,3 @@
+ });
+- });
++ });*/
+ });
+@@ -586,3 +586,3 @@
+
+- it('html sanitize', () => {
++ /*it('html sanitize', () => {
+ expectInlineTokens({
+@@ -596,3 +596,3 @@
+ });
+- });
++ });*/
+
+@@ -825,3 +825,3 @@
+
+- it('autolink mangle email', () => {
++ /*it('autolink mangle email', () => {
+ expectInlineTokens({
+@@ -845,3 +845,3 @@
+ });
+- });
++ });*/
+
+@@ -882,3 +882,3 @@
+
+- it('url mangle email', () => {
++ /*it('url mangle email', () => {
+ expectInlineTokens({
+@@ -902,3 +902,3 @@
+ });
+- });
++ });*/
+ });
+@@ -918,3 +918,3 @@
+
+- describe('smartypants', () => {
++ /*describe('smartypants', () => {
+ it('single quotes', () => {
+@@ -988,3 +988,3 @@
+ });
+- });
++ });*/
+ });
diff --git a/scripts/deps-docker/showdown.patch b/scripts/deps-docker/showdown.patch
new file mode 100644
index 00000000..c66c2e71
--- /dev/null
+++ b/scripts/deps-docker/showdown.patch
@@ -0,0 +1,214 @@
+diff -NarU1 showdown-orig/Gruntfile.js showdown-mod/Gruntfile.js
+--- showdown-orig/Gruntfile.js 2020-04-23 06:22:01.486676149 +0000
++++ showdown-mod/Gruntfile.js 2020-04-23 08:03:56.700219788 +0000
+@@ -27,3 +27,2 @@
+ 'src/subParsers/*.js',
+- 'src/subParsers/makeMarkdown/*.js',
+ 'src/loader.js'
+diff -NarU1 showdown-orig/src/converter.js showdown-mod/src/converter.js
+--- showdown-orig/src/converter.js 2020-04-23 06:22:01.496676150 +0000
++++ showdown-mod/src/converter.js 2020-04-23 08:20:11.056920123 +0000
+@@ -84,5 +84,5 @@
+
+- if (options.extensions) {
++ /*if (options.extensions) {
+ showdown.helper.forEach(options.extensions, _parseExtension);
+- }
++ }*/
+ }
+@@ -95,3 +95,3 @@
+ */
+- function _parseExtension (ext, name) {
++ /*function _parseExtension (ext, name) {
+
+@@ -159,3 +159,3 @@
+ */
+- function legacyExtensionLoading (ext, name) {
++ /*function legacyExtensionLoading (ext, name) {
+ if (typeof ext === 'function') {
+@@ -351,3 +351,3 @@
+ */
+- this.makeMarkdown = this.makeMd = function (src, HTMLParser) {
++ /*this.makeMarkdown = this.makeMd = function (src, HTMLParser) {
+
+@@ -482,3 +482,3 @@
+ */
+- this.addExtension = function (extension, name) {
++ /*this.addExtension = function (extension, name) {
+ name = name || null;
+@@ -491,3 +491,3 @@
+ */
+- this.useExtension = function (extensionName) {
++ /*this.useExtension = function (extensionName) {
+ _parseExtension(extensionName);
+@@ -526,3 +526,3 @@
+ */
+- this.removeExtension = function (extension) {
++ /*this.removeExtension = function (extension) {
+ if (!showdown.helper.isArray(extension)) {
+@@ -549,3 +549,3 @@
+ */
+- this.getAllExtensions = function () {
++ /*this.getAllExtensions = function () {
+ return {
+diff -NarU1 showdown-orig/src/options.js showdown-mod/src/options.js
+--- showdown-orig/src/options.js 2020-04-23 06:22:01.496676150 +0000
++++ showdown-mod/src/options.js 2020-04-23 08:24:29.176929018 +0000
+@@ -118,3 +118,3 @@
+ },
+- ghMentions: {
++ /*ghMentions: {
+ defaultValue: false,
+@@ -127,3 +127,3 @@
+ type: 'string'
+- },
++ },*/
+ encodeEmails: {
+diff -NarU1 showdown-orig/src/showdown.js showdown-mod/src/showdown.js
+--- showdown-orig/src/showdown.js 2020-04-23 06:22:01.496676150 +0000
++++ showdown-mod/src/showdown.js 2020-04-23 08:25:01.976930148 +0000
+@@ -7,3 +7,2 @@
+ parsers = {},
+- extensions = {},
+ globalOptions = getDefaultOpts(true),
+@@ -25,5 +24,4 @@
+ ghCompatibleHeaderId: true,
+- ghMentions: true,
++ //ghMentions: true,
+ backslashEscapesHTMLTags: true,
+- emoji: true,
+ splitAdjacentBlockquotes: true
+@@ -48,3 +46,3 @@
+ requireSpaceBeforeHeadingText: true,
+- ghMentions: false,
++ //ghMentions: false,
+ encodeEmails: true
+@@ -65,3 +63,2 @@
+ */
+-showdown.extensions = {};
+
+@@ -193,3 +190,3 @@
+ */
+-showdown.extension = function (name, ext) {
++/*showdown.extension = function (name, ext) {
+ 'use strict';
+@@ -235,3 +232,3 @@
+ */
+-showdown.getAllExtensions = function () {
++/*showdown.getAllExtensions = function () {
+ 'use strict';
+@@ -244,3 +241,3 @@
+ */
+-showdown.removeExtension = function (name) {
++/*showdown.removeExtension = function (name) {
+ 'use strict';
+@@ -252,3 +249,3 @@
+ */
+-showdown.resetExtensions = function () {
++/*showdown.resetExtensions = function () {
+ 'use strict';
+@@ -263,3 +260,3 @@
+ */
+-function validate (extension, name) {
++/*function validate (extension, name) {
+ 'use strict';
+@@ -370,3 +367,3 @@
+ */
+-showdown.validateExtension = function (ext) {
++/*showdown.validateExtension = function (ext) {
+ 'use strict';
+@@ -380 +377,2 @@
+ };
++*/
+diff -NarU1 showdown-orig/src/subParsers/anchors.js showdown-mod/src/subParsers/anchors.js
+--- showdown-orig/src/subParsers/anchors.js 2020-04-23 06:22:01.496676150 +0000
++++ showdown-mod/src/subParsers/anchors.js 2020-04-23 08:25:26.880264347 +0000
+@@ -76,3 +76,3 @@
+ // Lastly handle GithubMentions if option is enabled
+- if (options.ghMentions) {
++ /*if (options.ghMentions) {
+ text = text.replace(/(^|\s)(\\)?(@([a-z\d]+(?:[a-z\d.-]+?[a-z\d]+)*))/gmi, function (wm, st, escape, mentions, username) {
+@@ -93,3 +93,3 @@
+ });
+- }
++ }*/
+
+diff -NarU1 showdown-orig/src/subParsers/spanGamut.js showdown-mod/src/subParsers/spanGamut.js
+--- showdown-orig/src/subParsers/spanGamut.js 2020-04-23 06:22:01.496676150 +0000
++++ showdown-mod/src/subParsers/spanGamut.js 2020-04-23 08:07:50.460227880 +0000
+@@ -22,3 +22,2 @@
+ text = showdown.subParser('simplifiedAutoLinks')(text, options, globals);
+- text = showdown.subParser('emoji')(text, options, globals);
+ text = showdown.subParser('underline')(text, options, globals);
+@@ -26,3 +25,2 @@
+ text = showdown.subParser('strikethrough')(text, options, globals);
+- text = showdown.subParser('ellipsis')(text, options, globals);
+
+diff -NarU1 showdown-orig/test/node/showdown.Converter.js showdown-mod/test/node/showdown.Converter.js
+--- showdown-orig/test/node/showdown.Converter.js 2020-04-23 06:22:01.520009484 +0000
++++ showdown-mod/test/node/showdown.Converter.js 2020-04-23 08:14:58.086909318 +0000
+@@ -29,3 +29,3 @@
+
+- describe('Converter.options extensions', function () {
++ /*describe('Converter.options extensions', function () {
+ var runCount;
+@@ -48,3 +48,3 @@
+ });
+- });
++ });*/
+
+@@ -115,3 +115,3 @@
+
+- describe('extension methods', function () {
++ /*describe('extension methods', function () {
+ var extObjMock = {
+@@ -145,3 +145,3 @@
+ });
+- });
++ });*/
+
+diff -NarU1 showdown-orig/test/node/showdown.js showdown-mod/test/node/showdown.js
+--- showdown-orig/test/node/showdown.js 2020-04-23 06:22:01.523342816 +0000
++++ showdown-mod/test/node/showdown.js 2020-04-23 08:14:31.733575073 +0000
+@@ -25,3 +25,3 @@
+
+-describe('showdown.extension()', function () {
++/*describe('showdown.extension()', function () {
+ 'use strict';
+@@ -110,3 +110,3 @@
+ });
+-});
++});*/
+
+diff -NarU1 showdown-orig/test/node/testsuite.features.js showdown-mod/test/node/testsuite.features.js
+--- showdown-orig/test/node/testsuite.features.js 2020-04-23 06:22:01.523342816 +0000
++++ showdown-mod/test/node/testsuite.features.js 2020-04-23 08:25:48.880265106 +0000
+@@ -13,3 +13,2 @@
+ rawPrefixHeaderIdSuite = bootstrap.getTestSuite('test/features/rawPrefixHeaderId/'),
+- emojisSuite = bootstrap.getTestSuite('test/features/emojis/'),
+ underlineSuite = bootstrap.getTestSuite('test/features/underline/'),
+@@ -69,4 +68,4 @@
+ converter = new showdown.Converter({ghCompatibleHeaderId: true});
+- } else if (testsuite[i].name === 'ghMentions') {
+- converter = new showdown.Converter({ghMentions: true});
++ //} else if (testsuite[i].name === 'ghMentions') {
++ // converter = new showdown.Converter({ghMentions: true});
+ } else if (testsuite[i].name === 'disable-email-encoding') {
+@@ -185,17 +184,2 @@
+ it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
+- }
+- });
+-
+- /** test emojis support **/
+- describe('emojis support', function () {
+- var converter,
+- suite = emojisSuite;
+- for (var i = 0; i < suite.length; ++i) {
+- if (suite[i].name === 'simplifiedautolinks') {
+- converter = new showdown.Converter({emoji: true, simplifiedAutoLink: true});
+- } else {
+- converter = new showdown.Converter({emoji: true});
+- }
+-
+- it(suite[i].name.replace(/-/g, ' '), assertion(suite[i], converter));
+ }
diff --git a/srv/test.md b/srv/test.md
new file mode 100644
index 00000000..3611220a
--- /dev/null
+++ b/srv/test.md
@@ -0,0 +1,155 @@
+*fails marked/showdown/tui/simplemde (just italics), **OK: markdown-it/simplemde:***
+testing just google.com and underscored _google.com_ also with _google.com,_ trailing comma and _google.com_, comma after
+
+*fails tui (just italics), **OK: marked/showdown/markdown-it/simplemde:***
+testing just https://google.com and underscored _https://google.com_ links like that
+
+*fails marked (no markup) and showdown/tui/simplemde (no links at all), **OK: markdown-it:***
+let's try bracketed and __ underscored bracketed
+
+*fails marked (literal underscore), **OK: showdown/markdown-it/simplemde:***
+let's try bracketed and __ underscored bracketed
+
+*fails none:*
+and then [google](google.com) verbose and _[google](google.com)_ underscored
+
+*fails none:*
+and then [google](https://google.com/) verbose and _[google](https://google.com/)_ underscored
+
+*all behave similarly (only verbose ones):*
+and then or maybe <./local> fsgfds fsgfds
+and then [local] or maybe [./local] fsgfds [/absolute] fsgfds
+and then (local) or maybe (./local) fsgfds (/absolute) fsgfds
+and then [](local) or maybe [](./local) fsgfds [](/absolute) fsgfds
+and then [.](local) or maybe [.](./local) fsgfds [.](/absolute) fsgfds
+and then [asdf](local) or maybe [asdf](./local) fsgfds [asdf](/absolute) fsgfds
+
+*`ng/OK/OK/OK markdown-it`
+`ng/OK/ng/OK marked`
+`ng/OK/OK/OK showdown`
+`OK/OK/OK/OK simplemde`*
+[with spaces](/with spaces) plain, [with spaces](/with%20spaces) %20, [with spaces]() brackets, [with spaces](/with%20spaces) %20
+
+*this fails marked, **OK: markdown-it, simplemde:***
+
+* testing a list with:
+ `some code after a newline`
+
+* testing a list with:
+ just a newline
+
+and here is really just
+a newline toplevel
+
+*this fails showdown/hypermd, **OK: marked/markdown-it/simplemde:***
+
+* testing a list with
+
+ code here
+ and a newline
+ this should have two leading spaces
+
+ * second list level
+
+ more code here
+ and a newline
+ this should have two leading spaces
+
+.
+
+* testing a list with
+
+ code here
+ and a newline
+ this should have two leading spaces
+
+ * second list level
+
+ more code here
+ and a newline
+ this should have two leading spaces
+
+*this fails stackedit, **OK: showdown/marked/markdown-it/simplemde:***
+
+|||
+|--|--|
+| a table | with no header |
+| second row | foo bar |
+
+*this fails showdown/stackedit, **OK: marked/markdown-it/simplemde:***
+
+|||
+|--|--:|
+| a table | on the right |
+| second row | foo bar |
+
+* list entry
+* [x] yes
+* [ ] no
+* another entry
+
+# s1
+## ep1
+## ep2
+# s2
+## ep1
+## ep2
+# s3
+## ep1
+## ep2
+
+
+#######################################################################
+
+
+
+marked:
+ works in last ff/chrome for xp
+ bug: config{breaks:true} does nothing in 1.0
+ use whitespace, no tabs
+
+showdown:
+ ie6 and ie8 broken, works in last ff/chrome for xp
+
+markdown-it:
+ works in last ff/chrome for xp
+ use whitespace, no tabs
+ no header anchors
+
+tui wysiwyg:
+ requires links to be or [title](location)
+
+
+
+links:
+ http://demo.showdownjs.com/
+ https://marked.js.org/demo/
+ https://markdown-it.github.io/
+ https://simplemde.com/
+
+
+
+all-pass:
+
+https://github.com/joemccann/dillinger
+ https://dillinger.io/
+ uses markdown-it
+
+https://github.com/markdown-it/markdown-it
+ https://markdown-it.github.io/
+
+
+
+almost-all-pass:
+
+https://simplemde.com/
+
+https://github.com/nhn/tui.editor
+ https://nhn.github.io/tui.editor/latest/tutorial-example01-editor-basic
+ ie10 and up
+
+
+
+unrelated neat stuff:
+ https://github.com/gnab/remark
+