parent
d5bdb9a7af
commit
98b9ef6589
|
@ -2,47 +2,18 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
// Map multiple JavaScript environments to a single common API,
|
||||
// preferring web standards over Node.js API.
|
||||
//
|
||||
// Environments considered:
|
||||
// - Browsers
|
||||
// - Node.js
|
||||
// - Electron
|
||||
// - Parcel
|
||||
// - Webpack
|
||||
|
||||
if (typeof global !== "undefined") {
|
||||
// global already exists
|
||||
} else if (typeof window !== "undefined") {
|
||||
window.global = window;
|
||||
} else if (typeof self !== "undefined") {
|
||||
self.global = self;
|
||||
} else {
|
||||
throw new Error("cannot export Go (neither global, window nor self is defined)");
|
||||
}
|
||||
|
||||
if (!global.require && typeof require !== "undefined") {
|
||||
global.require = require;
|
||||
}
|
||||
|
||||
if (!global.fs && global.require) {
|
||||
const fs = require("fs");
|
||||
if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) {
|
||||
global.fs = fs;
|
||||
}
|
||||
}
|
||||
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!global.fs) {
|
||||
if (!globalThis.fs) {
|
||||
let outputBuf = "";
|
||||
global.fs = {
|
||||
globalThis.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
|
@ -87,8 +58,8 @@
|
|||
};
|
||||
}
|
||||
|
||||
if (!global.process) {
|
||||
global.process = {
|
||||
if (!globalThis.process) {
|
||||
globalThis.process = {
|
||||
getuid() { return -1; },
|
||||
getgid() { return -1; },
|
||||
geteuid() { return -1; },
|
||||
|
@ -102,47 +73,26 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (!global.crypto && global.require) {
|
||||
const nodeCrypto = require("crypto");
|
||||
global.crypto = {
|
||||
getRandomValues(b) {
|
||||
nodeCrypto.randomFillSync(b);
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!global.crypto) {
|
||||
throw new Error("global.crypto is not available, polyfill required (getRandomValues only)");
|
||||
if (!globalThis.crypto) {
|
||||
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
||||
}
|
||||
|
||||
if (!global.performance) {
|
||||
global.performance = {
|
||||
now() {
|
||||
const [sec, nsec] = process.hrtime();
|
||||
return sec * 1000 + nsec / 1000000;
|
||||
},
|
||||
};
|
||||
if (!globalThis.performance) {
|
||||
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
||||
}
|
||||
|
||||
if (!global.TextEncoder && global.require) {
|
||||
global.TextEncoder = require("util").TextEncoder;
|
||||
}
|
||||
if (!global.TextEncoder) {
|
||||
throw new Error("global.TextEncoder is not available, polyfill required");
|
||||
if (!globalThis.TextEncoder) {
|
||||
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
if (!global.TextDecoder && global.require) {
|
||||
global.TextDecoder = require("util").TextDecoder;
|
||||
if (!globalThis.TextDecoder) {
|
||||
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
if (!global.TextDecoder) {
|
||||
throw new Error("global.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
// End of polyfills for common API.
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
global.Go = class {
|
||||
globalThis.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
|
@ -296,8 +246,8 @@
|
|||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime1() (sec int64, nsec int32)
|
||||
"runtime.walltime1": (sp) => {
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
"runtime.walltime": (sp) => {
|
||||
sp >>>= 0;
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
|
@ -401,6 +351,7 @@
|
|||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
|
@ -417,6 +368,7 @@
|
|||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
|
@ -433,6 +385,7 @@
|
|||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
|
@ -514,7 +467,7 @@
|
|||
null,
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
globalThis,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
|
@ -523,7 +476,7 @@
|
|||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[global, 5],
|
||||
[globalThis, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
|
@ -564,6 +517,13 @@
|
|||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error("total length of command line and environment variables exceeds limit");
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
|
@ -591,36 +551,4 @@
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof module !== "undefined" &&
|
||||
global.require &&
|
||||
global.require.main === module &&
|
||||
global.process &&
|
||||
global.process.versions &&
|
||||
!global.process.versions.electron
|
||||
) {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
go.argv = process.argv.slice(2);
|
||||
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
|
||||
go.exit = process.exit;
|
||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
||||
process.on("exit", (code) => { // Node.js exits if no event handler is pending
|
||||
if (code === 0 && !go.exited) {
|
||||
// deadlock, make Go print error and stack traces
|
||||
go._pendingEvent = { id: 0 };
|
||||
go._resume();
|
||||
}
|
||||
});
|
||||
return go.run(result.instance);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
Binary file not shown.
|
@ -28,87 +28,45 @@ var reTag = regexp.MustCompile(`\[\s*(?P<opentagname>[a-zA-Z0-9]+)|\[\s*\/\s*(?P
|
|||
|
||||
var previewBBCodeCompiler = bbcode.NewCompiler(false, false)
|
||||
var realBBCodeCompiler = bbcode.NewCompiler(false, false)
|
||||
var eduPreviewBBCodeCompiler = bbcode.NewCompiler(false, false)
|
||||
var eduRealBBCodeCompiler = bbcode.NewCompiler(false, false)
|
||||
|
||||
var REYoutubeVidOnly = regexp.MustCompile(`^[a-zA-Z0-9_-]{11}$`)
|
||||
|
||||
func init() {
|
||||
type attr struct {
|
||||
Name, Value string
|
||||
}
|
||||
all := []bbcode.Compiler{previewBBCodeCompiler, realBBCodeCompiler, eduPreviewBBCodeCompiler, eduRealBBCodeCompiler}
|
||||
real := []bbcode.Compiler{realBBCodeCompiler, eduRealBBCodeCompiler}
|
||||
preview := []bbcode.Compiler{previewBBCodeCompiler, eduPreviewBBCodeCompiler}
|
||||
education := []bbcode.Compiler{eduPreviewBBCodeCompiler, eduRealBBCodeCompiler}
|
||||
|
||||
addSimpleTag := func(name, tag string, notext bool, attrs ...attr) {
|
||||
var tagFunc bbcode.TagCompilerFunc = func(bn *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
|
||||
if notext {
|
||||
var newChildren []*bbcode.BBCodeNode
|
||||
for _, child := range bn.Children {
|
||||
if child.ID != bbcode.TEXT {
|
||||
newChildren = append(newChildren, child)
|
||||
}
|
||||
}
|
||||
bn.Children = newChildren
|
||||
}
|
||||
addSimpleTag(all, "h1", "h1", false, nil)
|
||||
addSimpleTag(all, "h2", "h3", false, nil)
|
||||
addSimpleTag(all, "h3", "h3", false, nil)
|
||||
addSimpleTag(all, "m", "span", false, attrs{"class": "monospace"})
|
||||
addSimpleTag(all, "ol", "ol", true, nil)
|
||||
addSimpleTag(all, "ul", "ul", true, nil)
|
||||
addSimpleTag(all, "li", "li", false, nil)
|
||||
addSimpleTag(all, "spoiler", "span", false, attrs{"class": "spoiler"})
|
||||
addSimpleTag(all, "table", "table", true, nil)
|
||||
addSimpleTag(all, "tr", "tr", true, nil)
|
||||
addSimpleTag(all, "th", "th", false, nil)
|
||||
addSimpleTag(all, "td", "td", false, nil)
|
||||
|
||||
out := bbcode.NewHTMLTag("")
|
||||
out.Name = tag
|
||||
for _, a := range attrs {
|
||||
out.Attrs[a.Name] = a.Value
|
||||
}
|
||||
return out, true
|
||||
}
|
||||
previewBBCodeCompiler.SetTag(name, tagFunc)
|
||||
realBBCodeCompiler.SetTag(name, tagFunc)
|
||||
}
|
||||
addTag := func(name string, f bbcode.TagCompilerFunc) {
|
||||
previewBBCodeCompiler.SetTag(name, f)
|
||||
realBBCodeCompiler.SetTag(name, f)
|
||||
}
|
||||
|
||||
previewBBCodeCompiler.SetTag("youtube", makeYoutubeBBCodeFunc(true))
|
||||
realBBCodeCompiler.SetTag("youtube", makeYoutubeBBCodeFunc(false))
|
||||
|
||||
addSimpleTag("h1", "h1", false)
|
||||
addSimpleTag("h2", "h3", false)
|
||||
addSimpleTag("h3", "h3", false)
|
||||
addSimpleTag("m", "span", false, attr{"class", "monospace"})
|
||||
addSimpleTag("ol", "ol", true)
|
||||
addSimpleTag("ul", "ul", true)
|
||||
addSimpleTag("li", "li", false)
|
||||
addSimpleTag("spoiler", "span", false, attr{"class", "spoiler"})
|
||||
addSimpleTag("table", "table", true)
|
||||
addSimpleTag("tr", "tr", true)
|
||||
addSimpleTag("th", "th", false)
|
||||
addSimpleTag("td", "td", false)
|
||||
|
||||
addTag("quote", func(bn *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
|
||||
addTag(all, "quote", func(bn *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
|
||||
cite := bn.GetOpeningTag().Value
|
||||
if cite == "" {
|
||||
out := bbcode.NewHTMLTag("")
|
||||
out.Name = "blockquote"
|
||||
return out, true
|
||||
return htm("blockquote", nil), true
|
||||
} else {
|
||||
out := bbcode.NewHTMLTag("")
|
||||
out.Name = "blockquote"
|
||||
out.Attrs["cite"] = cite
|
||||
|
||||
a := bbcode.NewHTMLTag("")
|
||||
a.Name = "a"
|
||||
a.Attrs = map[string]string{
|
||||
"href": hmnurl.BuildUserProfile(cite),
|
||||
"class": "quotewho",
|
||||
}
|
||||
a.AppendChild(bbcode.NewHTMLTag(cite))
|
||||
|
||||
br := bbcode.NewHTMLTag("")
|
||||
br.Name = "br"
|
||||
|
||||
out.AppendChild(a)
|
||||
out.AppendChild(br)
|
||||
|
||||
return out, true
|
||||
return htm("blockquote", attrs{"cite": cite},
|
||||
htm("a", attrs{"href": hmnurl.BuildUserProfile(cite), "class": "quotewho"},
|
||||
bbcode.NewHTMLTag(cite),
|
||||
),
|
||||
htm("br", nil),
|
||||
), true
|
||||
}
|
||||
})
|
||||
|
||||
addTag("code", func(bn *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
|
||||
addTag(all, "code", func(bn *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
|
||||
lang := ""
|
||||
if tagvalue := bn.GetOpeningTag().Value; tagvalue != "" {
|
||||
lang = tagvalue
|
||||
|
@ -150,6 +108,63 @@ func init() {
|
|||
|
||||
return out, false
|
||||
})
|
||||
|
||||
addTag(preview, "youtube", makeYoutubeBBCodeFunc(true))
|
||||
addTag(real, "youtube", makeYoutubeBBCodeFunc(false))
|
||||
|
||||
addTag(education, "glossary", func(bn *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
|
||||
term := bn.GetOpeningTag().Value
|
||||
return htm("a", attrs{"href": hmnurl.BuildEducationGlossary(term), "class": "glossary-term", "data-term": term}), true
|
||||
})
|
||||
addSimpleTag(education, "note", "div", false, attrs{"class": "education-note"})
|
||||
}
|
||||
|
||||
type attrs map[string]string
|
||||
|
||||
// Generates a "simple" tag, i.e. one that is more or less just HTML.
|
||||
//
|
||||
// name is the bbcode tag name.
|
||||
// tag is the HTML tag to generate.
|
||||
// notext prevents the tag from having any direct text children (useful for e.g. [ol] and [ul])
|
||||
// attrs are any HTML attributes to add to the tag.
|
||||
func addSimpleTag(compilers []bbcode.Compiler, name, tag string, notext bool, attributes attrs) {
|
||||
var tagFunc bbcode.TagCompilerFunc = func(bn *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
|
||||
if notext {
|
||||
var newChildren []*bbcode.BBCodeNode
|
||||
for _, child := range bn.Children {
|
||||
if child.ID != bbcode.TEXT {
|
||||
newChildren = append(newChildren, child)
|
||||
}
|
||||
}
|
||||
bn.Children = newChildren
|
||||
}
|
||||
|
||||
out := bbcode.NewHTMLTag("")
|
||||
out.Name = tag
|
||||
out.Attrs = attributes
|
||||
return out, true
|
||||
}
|
||||
for _, compiler := range compilers {
|
||||
compiler.SetTag(name, tagFunc)
|
||||
}
|
||||
}
|
||||
|
||||
func addTag(compilers []bbcode.Compiler, name string, f bbcode.TagCompilerFunc) {
|
||||
for _, compiler := range compilers {
|
||||
compiler.SetTag(name, f)
|
||||
}
|
||||
}
|
||||
|
||||
// html was taken
|
||||
func htm(tag string, attributes attrs, children ...*bbcode.HTMLTag) *bbcode.HTMLTag {
|
||||
res := bbcode.NewHTMLTag("")
|
||||
res.Name = tag
|
||||
res.Attrs = attributes
|
||||
for _, child := range children {
|
||||
res.AppendChild(child)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func makeYoutubeBBCodeFunc(preview bool) bbcode.TagCompilerFunc {
|
||||
|
@ -233,7 +248,8 @@ func makeYoutubeBBCodeFunc(preview bool) bbcode.TagCompilerFunc {
|
|||
// ----------------------
|
||||
|
||||
type bbcodeParser struct {
|
||||
Preview bool
|
||||
Preview bool
|
||||
Education bool
|
||||
}
|
||||
|
||||
var _ parser.InlineParser = &bbcodeParser{}
|
||||
|
@ -291,9 +307,15 @@ func (s bbcodeParser) Parse(parent gast.Node, block text.Reader, pc parser.Conte
|
|||
unparsedBBCode := restOfSource[:endIndex]
|
||||
block.Advance(len(unparsedBBCode))
|
||||
|
||||
compiler := realBBCodeCompiler
|
||||
if s.Preview {
|
||||
var compiler bbcode.Compiler
|
||||
if s.Preview && s.Education {
|
||||
compiler = eduPreviewBBCodeCompiler
|
||||
} else if s.Preview && !s.Education {
|
||||
compiler = previewBBCodeCompiler
|
||||
} else if !s.Preview && s.Education {
|
||||
compiler = eduRealBBCodeCompiler
|
||||
} else {
|
||||
compiler = realBBCodeCompiler
|
||||
}
|
||||
|
||||
return NewBBCode(compiler.Compile(string(unparsedBBCode)))
|
||||
|
@ -368,12 +390,13 @@ func (r *BBCodeHTMLRenderer) renderBBCode(w util.BufWriter, source []byte, n gas
|
|||
// ----------------------
|
||||
|
||||
type BBCodeExtension struct {
|
||||
Preview bool
|
||||
Preview bool
|
||||
Education bool
|
||||
}
|
||||
|
||||
func (e BBCodeExtension) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||
util.Prioritized(bbcodeParser{Preview: e.Preview}, BBCodePriority),
|
||||
util.Prioritized(bbcodeParser{Preview: e.Preview, Education: e.Education}, BBCodePriority),
|
||||
))
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewBBCodeHTMLRenderer(), BBCodePriority),
|
||||
|
|
|
@ -46,6 +46,24 @@ var DiscordMarkdown = makeGoldmark(
|
|||
goldmark.WithRendererOptions(html.WithHardWraps()),
|
||||
)
|
||||
|
||||
// Used for rendering real-time previews of post content.
|
||||
var EducationPreviewMarkdown = makeGoldmark(
|
||||
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
|
||||
Previews: true,
|
||||
Embeds: true,
|
||||
Education: true,
|
||||
})...),
|
||||
)
|
||||
|
||||
// Used for generating the final HTML for a post.
|
||||
var EducationRealMarkdown = makeGoldmark(
|
||||
goldmark.WithExtensions(makeGoldmarkExtensions(MarkdownOptions{
|
||||
Previews: false,
|
||||
Embeds: true,
|
||||
Education: true,
|
||||
})...),
|
||||
)
|
||||
|
||||
func ParseMarkdown(source string, md goldmark.Markdown) string {
|
||||
var buf bytes.Buffer
|
||||
if err := md.Convert([]byte(source), &buf); err != nil {
|
||||
|
@ -56,8 +74,9 @@ func ParseMarkdown(source string, md goldmark.Markdown) string {
|
|||
}
|
||||
|
||||
type MarkdownOptions struct {
|
||||
Previews bool
|
||||
Embeds bool
|
||||
Previews bool
|
||||
Embeds bool
|
||||
Education bool
|
||||
}
|
||||
|
||||
func makeGoldmark(opts ...goldmark.Option) goldmark.Markdown {
|
||||
|
@ -114,7 +133,8 @@ func makeGoldmarkExtensions(opts MarkdownOptions) []goldmark.Extender {
|
|||
extenders = append(extenders,
|
||||
MathjaxExtension{},
|
||||
BBCodeExtension{
|
||||
Preview: opts.Previews,
|
||||
Preview: opts.Previews,
|
||||
Education: opts.Education,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -61,6 +61,22 @@ func main() {
|
|||
assert.Contains(t, html, "Hello, world!")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("education", func(t *testing.T) {
|
||||
t.Run("[glossary]", func(t *testing.T) {
|
||||
html := ParseMarkdown("[glossary=foo]Foo Protocol[/glossary]", EducationRealMarkdown)
|
||||
t.Log(html)
|
||||
assert.Equal(t, 1, strings.Count(html, "<a"))
|
||||
assert.Contains(t, html, `class="glossary-term"`)
|
||||
assert.Contains(t, html, `data-term="foo"`)
|
||||
})
|
||||
t.Run("[note]", func(t *testing.T) {
|
||||
html := ParseMarkdown("[note]This should only appear to editors![/note]", EducationRealMarkdown)
|
||||
t.Log(html)
|
||||
assert.Equal(t, 1, strings.Count(html, "<div"))
|
||||
assert.Contains(t, html, `class="education-note"`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSharlock(t *testing.T) {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
//go:build !js
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"git.handmade.network/hmn/hmn/src/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
const publicDir = "../../../public"
|
||||
compile := exec.Command("go", "build", "-o", filepath.Join(publicDir, "parsing.wasm"))
|
||||
compile.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm")
|
||||
run(compile)
|
||||
|
||||
utils.Must(copy(
|
||||
fmt.Sprintf("%s/misc/wasm/wasm_exec.js", build.Default.GOROOT),
|
||||
filepath.Join(publicDir, "go_wasm_exec.js"),
|
||||
))
|
||||
}
|
||||
|
||||
func run(cmd *exec.Cmd) {
|
||||
output, err := cmd.CombinedOutput()
|
||||
fmt.Print(string(output))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if exit, ok := err.(*exec.ExitError); ok {
|
||||
os.Exit(exit.ExitCode())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func copy(src string, dst string) error {
|
||||
s, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
d, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(d, s); err != nil {
|
||||
d.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return d.Close()
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
PUBLIC_DIR=../../../public
|
||||
|
||||
GOOS=js GOARCH=wasm go build -o $PUBLIC_DIR/parsing.wasm
|
||||
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" $PUBLIC_DIR/go_wasm_exec.js
|
|
@ -1,4 +1,4 @@
|
|||
// +build js
|
||||
//go:build js
|
||||
|
||||
package main
|
||||
|
||||
|
@ -12,6 +12,9 @@ func main() {
|
|||
js.Global().Set("parseMarkdown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return parsing.ParseMarkdown(args[0].String(), parsing.ForumPreviewMarkdown)
|
||||
}))
|
||||
js.Global().Set("parseMarkdownEdu", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
return parsing.ParseMarkdown(args[0].String(), parsing.EducationPreviewMarkdown)
|
||||
}))
|
||||
|
||||
var done chan bool
|
||||
<-done // block forever
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
previewWorker.postMessage({
|
||||
elementID: inputEl.id,
|
||||
markdown: inputEl.value,
|
||||
parserName: '{{ or .ParserName "parseMarkdown" }}',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
importScripts('/public/go_wasm_exec.js');
|
||||
|
||||
// wowee good javascript yeah
|
||||
const global = Function('return this')();
|
||||
|
||||
/*
|
||||
NOTE(ben): The structure here is a little funny but allows for some debouncing. Any postMessages
|
||||
that got queued up can run all at once, then it can process the latest one.
|
||||
|
@ -9,8 +12,8 @@ let wasmLoaded = false;
|
|||
let jobs = {};
|
||||
|
||||
onmessage = ({ data }) => {
|
||||
const { elementID, markdown } = data;
|
||||
jobs[elementID] = markdown;
|
||||
const { elementID, markdown, parserName } = data;
|
||||
jobs[elementID] = { markdown, parserName };
|
||||
setTimeout(doPreview, 0);
|
||||
}
|
||||
|
||||
|
@ -27,8 +30,8 @@ const doPreview = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
for (const [elementID, markdown] of Object.entries(jobs)) {
|
||||
const html = parseMarkdown(markdown);
|
||||
for (const [elementID, { markdown, parserName }] of Object.entries(jobs)) {
|
||||
const html = global[parserName](markdown);
|
||||
postMessage({
|
||||
elementID: elementID,
|
||||
html: html,
|
||||
|
|
|
@ -311,6 +311,8 @@ func getEditorDataForEduArticle(
|
|||
MaxFileSize: AssetMaxSize(currentUser),
|
||||
UploadUrl: urlContext.BuildAssetUpload(),
|
||||
ShowEduOptions: true,
|
||||
|
||||
ParserName: "parseMarkdownEdu",
|
||||
}
|
||||
|
||||
if article != nil {
|
||||
|
|
|
@ -50,6 +50,7 @@ type editorData struct {
|
|||
PostReplyingTo *templates.Post
|
||||
ShowEduOptions bool
|
||||
|
||||
ParserName string
|
||||
MaxFileSize int
|
||||
UploadUrl string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue