From 2079ae00216a35fbde092121fb55207ea33128da Mon Sep 17 00:00:00 2001 From: ychao_1983 Date: Tue, 26 Jul 2022 17:47:56 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3notebook=E4=B8=8D=E8=83=BD?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E5=9B=BE=E7=89=87=EF=BC=8C=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=92=8C=E6=96=87=E5=AD=97=E5=8F=AF=E8=83=BD=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E4=B8=8D=E5=85=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/self/css/notebook/notebook.css | 12 +- public/self/js/notebook/notebook.js | 365 ++++++++++++++++++++++++++ routers/repo/view.go | 1 + templates/repo/header.tmpl | 1 - templates/repo/view_file.tmpl | 14 +- 5 files changed, 386 insertions(+), 7 deletions(-) create mode 100644 public/self/js/notebook/notebook.js diff --git a/public/self/css/notebook/notebook.css b/public/self/css/notebook/notebook.css index c75c33865..0e4979660 100644 --- a/public/self/css/notebook/notebook.css +++ b/public/self/css/notebook/notebook.css @@ -1,6 +1,7 @@ .nb-notebook { line-height: 1.5; - margin-left: 7em; + margin-left: 6em; + } .nb-stdout, .nb-stderr { @@ -15,6 +16,7 @@ .nb-cell + .nb-cell { margin-top: 0.5em; + max-width: 100%; } .nb-output table { @@ -40,6 +42,11 @@ padding-left: 1em; } +.nb-notebook img { + max-width: 80%; + padding: 3px; +} + .nb-cell { position: relative; } @@ -60,7 +67,8 @@ } .nb-output img { - max-width: 100%; + max-width: 80%; + padding: 3px; } .nb-output:before, .nb-input:before { diff --git a/public/self/js/notebook/notebook.js b/public/self/js/notebook/notebook.js new file mode 100644 index 000000000..20c8c9b66 --- /dev/null +++ b/public/self/js/notebook/notebook.js @@ -0,0 +1,365 @@ +// http://github.com/jsvine/notebookjs +// notebook.js may be freely distributed under the MIT license. +(function () { + var VERSION = "0.6.7"; + var root = this; + var isBrowser = root.window !== undefined; + var doc; + + // Get browser or JSDOM document + if (isBrowser) { + doc = root.document; + } else { + var jsdom = require("jsdom"); + var dom = new jsdom.JSDOM(); + doc = dom.window.document; + } + + // Helper functions + var ident = function (x) { return x; }; + + var makeElement = function (tag, classNames) { + var el = doc.createElement(tag); + el.className = (classNames || []).map(function (cn) { + return nb.prefix + cn; + }).join(" "); + return el; + }; + + var escapeHTML = function (raw) { + var replaced = raw + .replace(//g, ">"); + return replaced; + }; + + var joinText = function (text) { + if (text.join) { + return text.map(joinText).join(""); + } else { + return text; + } + }; + + // Get supporting libraries + var getMarkdown = function () { + return root.marked || (typeof require === "function" && require("marked")); + }; + + var getAnsi = function () { + var lib = root.ansi_up || (typeof require === "function" && require("ansi_up")); + return lib && lib.ansi_to_html; + }; + + var getSanitizer = function () { + var lib = root.DOMPurify || (typeof require === "function" && require("dompurify")); + if (isBrowser) { + return lib && lib.sanitize; + } else { + return lib(dom.window).sanitize; + } + }; + + // Set up `nb` namespace + var nb = { + prefix: "nb-", + markdown: getMarkdown() || ident, + ansi: getAnsi() || ident, + sanitizer: getSanitizer() || ident, + highlighter: ident, + VERSION: VERSION + }; + + // Inputs + nb.Input = function (raw, cell) { + this.raw = raw; + this.cell = cell; + }; + + nb.Input.prototype.render = function () { + if (!this.raw.length) { return makeElement("div"); } + var holder = makeElement("div", [ "input" ]); + var cell = this.cell; + if (typeof cell.number === "number") { + holder.setAttribute("data-prompt-number", this.cell.number); + } + var pre_el = makeElement("pre"); + var code_el = makeElement("code"); + var notebook = cell.worksheet.notebook; + var m = notebook.metadata; + var lang = this.cell.raw.language || m.language || (m.kernelspec && m.kernelspec.language) || (m.language_info && m.language_info.name); + code_el.setAttribute("data-language", lang); + code_el.className = "lang-" + lang; + code_el.innerHTML = nb.highlighter(escapeHTML(joinText(this.raw)), pre_el, code_el, lang); + pre_el.appendChild(code_el); + holder.appendChild(pre_el); + this.el = holder; + return holder; + }; + + // Outputs and output-renderers + var imageCreator = function (format) { + return function (data) { + var el = makeElement("img", [ "image-output" ]); + el.src = "data:image/" + format + ";base64," + joinText(data).replace(/\n/g, ""); + return el; + }; + }; + + nb.display = {}; + nb.display.text = function (text) { + var el = makeElement("pre", [ "text-output" ]); + el.innerHTML = nb.highlighter(nb.ansi(joinText(text)), el); + return el; + }; + nb.display["text/plain"] = nb.display.text; + + nb.display.html = function (html) { + var el = makeElement("div", [ "html-output" ]); + el.innerHTML = nb.sanitizer(joinText(html)); + return el; + }; + nb.display["text/html"] = nb.display.html; + + nb.display.marked = function(md) { + return nb.display.html(nb.markdown(joinText(md))); + }; + nb.display["text/markdown"] = nb.display.marked; + + nb.display.svg = function (svg) { + var el = makeElement("div", [ "svg-output" ]); + el.innerHTML = joinText(svg); + return el; + }; + nb.display["text/svg+xml"] = nb.display.svg; + nb.display["image/svg+xml"] = nb.display.svg; + + nb.display.latex = function (latex) { + var el = makeElement("div", [ "latex-output" ]); + el.innerHTML = joinText(latex); + return el; + }; + nb.display["text/latex"] = nb.display.latex; + + nb.display.javascript = function (js) { + var el = makeElement("script"); + el.innerHTML = joinText(js); + return el; + }; + nb.display["application/javascript"] = nb.display.javascript; + + nb.display.png = imageCreator("png"); + nb.display["image/png"] = nb.display.png; + nb.display.jpeg = imageCreator("jpeg"); + nb.display["image/jpeg"] = nb.display.jpeg; + + nb.display_priority = [ + "png", "image/png", "jpeg", "image/jpeg", + "svg", "image/svg+xml", "text/svg+xml", "html", "text/html", + "text/markdown", "latex", "text/latex", + "javascript", "application/javascript", + "text", "text/plain" + ]; + + var render_display_data = function () { + var o = this; + var formats = nb.display_priority.filter(function (d) { + return o.raw.data ? o.raw.data[d] : o.raw[d]; + }); + var format = formats[0]; + if (format) { + if (nb.display[format]) { + return nb.display[format](o.raw[format] || o.raw.data[format]); + } + } + return makeElement("div", [ "empty-output" ]); + }; + + var render_error = function () { + var el = makeElement("pre", [ "pyerr" ]); + var raw = this.raw.traceback.join("\n"); + el.innerHTML = nb.highlighter(nb.ansi(escapeHTML(raw)), el); + return el; + }; + + nb.Output = function (raw, cell) { + this.raw = raw; + this.cell = cell; + this.type = raw.output_type; + }; + + nb.Output.prototype.renderers = { + "display_data": render_display_data, + "execute_result": render_display_data, + "pyout": render_display_data, + "pyerr": render_error, + "error": render_error, + "stream": function () { + var el = makeElement("pre", [ (this.raw.stream || this.raw.name) ]); + var raw = joinText(this.raw.text); + el.innerHTML = nb.highlighter(nb.ansi(escapeHTML(raw)), el); + return el; + } + }; + + nb.Output.prototype.render = function () { + var outer = makeElement("div", [ "output" ]); + if (typeof this.cell.number === "number") { + outer.setAttribute("data-prompt-number", this.cell.number); + } + var inner = this.renderers[this.type].call(this); + outer.appendChild(inner); + this.el = outer; + return outer; + }; + + // Post-processing + nb.coalesceStreams = function (outputs) { + if (!outputs.length) { return outputs; } + var last = outputs[0]; + var new_outputs = [ last ]; + outputs.slice(1).forEach(function (o) { + if (o.raw.output_type === "stream" && + last.raw.output_type === "stream" && + o.raw.stream === last.raw.stream && + o.raw.name === last.raw.name) { + last.raw.text = last.raw.text.concat(o.raw.text); + } else { + new_outputs.push(o); + last = o; + } + }); + return new_outputs; + }; + + // Cells + nb.Cell = function (raw, worksheet) { + var cell = this; + cell.raw = raw; + cell.worksheet = worksheet; + cell.type = raw.cell_type; + if (cell.type === "code") { + cell.number = raw.prompt_number > -1 ? raw.prompt_number : raw.execution_count; + var source = raw.input || [ raw.source ]; + cell.input = new nb.Input(source, cell); + var raw_outputs = (cell.raw.outputs || []).map(function (o) { + return new nb.Output(o, cell); + }); + cell.outputs = nb.coalesceStreams(raw_outputs); + } + }; + + var math_delimiters = [ + {left: "$$", right: "$$", display: true}, + {left: "\\[", right: "\\]", display: true}, + {left: "\\(", right: "\\)", display: false}, + {left: "$", right: "$", display: false} + ]; + + nb.Cell.prototype.renderers = { + markdown: function () { + var el = makeElement("div", [ "cell", "markdown-cell" ]); + + var joined = joinText(this.raw.source); + + // Pre-render math via KaTeX's auto-render extension, if available + if (root.renderMathInElement != null) { + el.innerHTML = nb.sanitizer(joined); + root.renderMathInElement(el, { delimiters: math_delimiters }); + el.innerHTML = nb.sanitizer(nb.markdown( + el.innerHTML + .replace(/>/g, ">") // Necessary to enable blockquote syntax + )); + } else { + el.innerHTML = nb.sanitizer(nb.markdown(joined)); + } + + return el; + }, + heading: function () { + var el = makeElement("h" + this.raw.level, [ "cell", "heading-cell" ]); + el.innerHTML = nb.sanitizer(joinText(this.raw.source)); + return el; + }, + raw: function () { + var el = makeElement("div", [ "cell", "raw-cell" ]); + el.innerHTML = escapeHTML(joinText(this.raw.source)); + return el; + }, + code: function () { + var cell_el = makeElement("div", [ "cell", "code-cell" ]); + cell_el.appendChild(this.input.render()); + var output_els = this.outputs.forEach(function (o) { + cell_el.appendChild(o.render()); + }); + return cell_el; + } + }; + + nb.Cell.prototype.render = function () { + var el = this.renderers[this.type].call(this); + this.el = el; + return el; + }; + + // Worksheets + nb.Worksheet = function (raw, notebook) { + var worksheet = this; + this.raw = raw; + this.notebook = notebook; + this.cells = raw.cells.map(function (c) { + return new nb.Cell(c, worksheet); + }); + this.render = function () { + var worksheet_el = makeElement("div", [ "worksheet" ]); + worksheet.cells.forEach(function (c) { + worksheet_el.appendChild(c.render()); + }); + this.el = worksheet_el; + return worksheet_el; + }; + }; + + // Notebooks + nb.Notebook = function (raw, config) { + var notebook = this; + this.raw = raw; + this.config = config; + var meta = this.metadata = raw.metadata || {}; + this.title = meta.title || meta.name; + var _worksheets = raw.worksheets || [ { cells: raw.cells } ]; + this.worksheets = _worksheets.map(function (ws) { + return new nb.Worksheet(ws, notebook); + }); + this.sheet = this.worksheets[0]; + }; + + nb.Notebook.prototype.render = function () { + var notebook_el = makeElement("div", [ "notebook" ]); + this.worksheets.forEach(function (w) { + notebook_el.appendChild(w.render()); + }); + this.el = notebook_el; + return notebook_el; + }; + + nb.parse = function (nbjson, config) { + return new nb.Notebook(nbjson, config); + }; + + // Exports + if (typeof define === 'function' && define.amd) { + define(function() { + return nb; + }); + } + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = nb; + } + exports.nb = nb; + } else { + root.nb = nb; + } + +}).call(this); diff --git a/routers/repo/view.go b/routers/repo/view.go index 02004fa06..3a18e4ddf 100755 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -484,6 +484,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st ) } else if isNoteBook { ctx.Data["FileContent"] = string(buf) + ctx.Data["FileParentURL"] = path.Dir(rawLink+"/"+ctx.Repo.TreePath) + "/" } else { // Building code view blocks with line number on server side. var fileContent string diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index e5c2619e2..b36e89d4d 100755 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -190,7 +190,6 @@ - diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 61d04570f..91ec9b0bb 100755 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -103,20 +103,23 @@ {{end}} {{else if .FileSize}} - + {{if .IsNoteBook}} +
+ + {{else}} +
{{if .IsFileTooLarge}} - {{else if .IsNoteBook}} - {{else}} {{end}} -
{{.i18n.Tr "repo.file_too_large"}}{{.LineNums}}
    {{.FileContent}}
+ + {{end}} {{end}} @@ -134,6 +137,9 @@ function showNoteBook(){ var isNoteBook = {{.IsNoteBook}} if (isNoteBook) { var jsonStr = "{{.FileContent}}" + nb.markdown.setOptions({ + baseUrl: {{.FileParentURL}} + }); var notebook = nb.parse(JSON.parse(jsonStr)); var rendered = notebook.render(); $("#notebook").append(rendered);