diff options
author | Jacob Schatz <jschatz@gitlab.com> | 2017-04-24 18:17:49 +0000 |
---|---|---|
committer | Jacob Schatz <jschatz@gitlab.com> | 2017-04-24 18:17:49 +0000 |
commit | 640e8093d4e9a96e3c6b787d6b5bd5f679594724 (patch) | |
tree | ecd3ed4b799957e74b2ffe3663f4fb11a8c41088 /app | |
parent | 3ac4ef221b532e3425d890f044ef7d39ac81e163 (diff) | |
parent | 1d99c7755ce337a715847344f4f20df23b29bfc7 (diff) | |
download | gitlab-ce-640e8093d4e9a96e3c6b787d6b5bd5f679594724.tar.gz |
Merge branch 'notebooklab-in-repo' into 'master'
Moved NotebookLab assets into repo
See merge request !10630
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/blob/notebook/index.js | 6 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/code.vue | 58 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/code/index.vue | 57 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/markdown.vue | 98 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/output/html.vue | 22 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/output/image.vue | 27 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/output/index.vue | 83 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/prompt.vue | 30 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/index.vue | 75 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/lib/highlight.js | 22 |
11 files changed, 478 insertions, 2 deletions
diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index 9b8bfbfc8c0..36fe8a7184f 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -1,10 +1,9 @@ /* eslint-disable no-new */ import Vue from 'vue'; import VueResource from 'vue-resource'; -import NotebookLab from 'vendor/notebooklab'; +import notebookLab from '../../notebook/index.vue'; Vue.use(VueResource); -Vue.use(NotebookLab); export default () => { const el = document.getElementById('js-notebook-viewer'); @@ -19,6 +18,9 @@ export default () => { json: {}, }; }, + components: { + notebookLab, + }, template: ` <div class="container-fluid md prepend-top-default append-bottom-default"> <div diff --git a/app/assets/javascripts/notebook/cells/code.vue b/app/assets/javascripts/notebook/cells/code.vue new file mode 100644 index 00000000000..b8a16356576 --- /dev/null +++ b/app/assets/javascripts/notebook/cells/code.vue @@ -0,0 +1,58 @@ +<template> + <div class="cell"> + <code-cell + type="input" + :raw-code="rawInputCode" + :count="cell.execution_count" + :code-css-class="codeCssClass" /> + <output-cell + v-if="hasOutput" + :count="cell.execution_count" + :output="output" + :code-css-class="codeCssClass" /> + </div> +</template> + +<script> +import CodeCell from './code/index.vue'; +import OutputCell from './output/index.vue'; + +export default { + components: { + 'code-cell': CodeCell, + 'output-cell': OutputCell, + }, + props: { + cell: { + type: Object, + required: true, + }, + codeCssClass: { + type: String, + required: false, + default: '', + }, + }, + computed: { + rawInputCode() { + if (this.cell.source) { + return this.cell.source.join(''); + } + + return ''; + }, + hasOutput() { + return this.cell.outputs.length; + }, + output() { + return this.cell.outputs[0]; + }, + }, +}; +</script> + +<style scoped> +.cell { + flex-direction: column; +} +</style> diff --git a/app/assets/javascripts/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue new file mode 100644 index 00000000000..31b30f601e2 --- /dev/null +++ b/app/assets/javascripts/notebook/cells/code/index.vue @@ -0,0 +1,57 @@ +<template> + <div :class="type"> + <prompt + :type="promptType" + :count="count" /> + <pre + class="language-python" + :class="codeCssClass" + ref="code" + v-text="code"> + </pre> + </div> +</template> + +<script> + import Prism from '../../lib/highlight'; + import Prompt from '../prompt.vue'; + + export default { + components: { + prompt: Prompt, + }, + props: { + count: { + type: Number, + required: false, + default: 0, + }, + codeCssClass: { + type: String, + required: false, + default: '', + }, + type: { + type: String, + required: true, + }, + rawCode: { + type: String, + required: true, + }, + }, + computed: { + code() { + return this.rawCode; + }, + promptType() { + const type = this.type.split('put')[0]; + + return type.charAt(0).toUpperCase() + type.slice(1); + }, + }, + mounted() { + Prism.highlightElement(this.$refs.code); + }, + }; +</script> diff --git a/app/assets/javascripts/notebook/cells/index.js b/app/assets/javascripts/notebook/cells/index.js new file mode 100644 index 00000000000..e4c255609fe --- /dev/null +++ b/app/assets/javascripts/notebook/cells/index.js @@ -0,0 +1,2 @@ +export { default as MarkdownCell } from './markdown.vue'; +export { default as CodeCell } from './code.vue'; diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue new file mode 100644 index 00000000000..3e8240d10ec --- /dev/null +++ b/app/assets/javascripts/notebook/cells/markdown.vue @@ -0,0 +1,98 @@ +<template> + <div class="cell text-cell"> + <prompt /> + <div class="markdown" v-html="markdown"></div> + </div> +</template> + +<script> + /* global katex */ + import marked from 'marked'; + import Prompt from './prompt.vue'; + + const renderer = new marked.Renderer(); + + /* + Regex to match KaTex blocks. + + Supports the following: + + \begin{equation}<math>\end{equation} + $$<math>$$ + inline $<math>$ + + The matched text then goes through the KaTex renderer & then outputs the HTML + */ + const katexRegexString = `( + ^\\\\begin{[a-zA-Z]+}\\s + | + ^\\$\\$ + | + \\s\\$(?!\\$) + ) + (.+?) + ( + \\s\\\\end{[a-zA-Z]+}$ + | + \\$\\$$ + | + \\$ + ) + `.replace(/\s/g, '').trim(); + + renderer.paragraph = (t) => { + let text = t; + let inline = false; + + if (typeof katex !== 'undefined') { + const katexString = text.replace(/\\/g, '\\'); + const matches = new RegExp(katexRegexString, 'gi').exec(katexString); + + if (matches && matches.length > 0) { + if (matches[1].trim() === '$' && matches[3].trim() === '$') { + inline = true; + + text = `${katexString.replace(matches[0], '')} ${katex.renderToString(matches[2])}`; + } else { + text = katex.renderToString(matches[2]); + } + } + } + + return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`; + }; + + marked.setOptions({ + sanitize: true, + renderer, + }); + + export default { + components: { + prompt: Prompt, + }, + props: { + cell: { + type: Object, + required: true, + }, + }, + computed: { + markdown() { + return marked(this.cell.source.join('')); + }, + }, + }; +</script> + +<style> +.markdown .katex { + display: block; + text-align: center; +} + +.markdown .inline-katex .katex { + display: inline; + text-align: initial; +} +</style> diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue new file mode 100644 index 00000000000..0f39cd138df --- /dev/null +++ b/app/assets/javascripts/notebook/cells/output/html.vue @@ -0,0 +1,22 @@ +<template> + <div class="output"> + <prompt /> + <div v-html="rawCode"></div> + </div> +</template> + +<script> +import Prompt from '../prompt.vue'; + +export default { + props: { + rawCode: { + type: String, + required: true, + }, + }, + components: { + prompt: Prompt, + }, +}; +</script> diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue new file mode 100644 index 00000000000..f3b873bbc0f --- /dev/null +++ b/app/assets/javascripts/notebook/cells/output/image.vue @@ -0,0 +1,27 @@ +<template> + <div class="output"> + <prompt /> + <img + :src="'data:' + outputType + ';base64,' + rawCode" /> + </div> +</template> + +<script> +import Prompt from '../prompt.vue'; + +export default { + props: { + outputType: { + type: String, + required: true, + }, + rawCode: { + type: String, + required: true, + }, + }, + components: { + prompt: Prompt, + }, +}; +</script> diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue new file mode 100644 index 00000000000..23c9ea78939 --- /dev/null +++ b/app/assets/javascripts/notebook/cells/output/index.vue @@ -0,0 +1,83 @@ +<template> + <component :is="componentName" + type="output" + :outputType="outputType" + :count="count" + :raw-code="rawCode" + :code-css-class="codeCssClass" /> +</template> + +<script> +import CodeCell from '../code/index.vue'; +import Html from './html.vue'; +import Image from './image.vue'; + +export default { + props: { + codeCssClass: { + type: String, + required: false, + default: '', + }, + count: { + type: Number, + required: false, + default: 0, + }, + output: { + type: Object, + requred: true, + }, + }, + components: { + 'code-cell': CodeCell, + 'html-output': Html, + 'image-output': Image, + }, + data() { + return { + outputType: '', + }; + }, + computed: { + componentName() { + if (this.output.text) { + return 'code-cell'; + } else if (this.output.data['image/png']) { + this.outputType = 'image/png'; + + return 'image-output'; + } else if (this.output.data['text/html']) { + this.outputType = 'text/html'; + + return 'html-output'; + } else if (this.output.data['image/svg+xml']) { + this.outputType = 'image/svg+xml'; + + return 'html-output'; + } + + this.outputType = 'text/plain'; + return 'code-cell'; + }, + rawCode() { + if (this.output.text) { + return this.output.text.join(''); + } + + return this.dataForType(this.outputType); + }, + }, + methods: { + dataForType(type) { + let data = this.output.data[type]; + + if (typeof data === 'object') { + data = data.join(''); + } + + return data; + }, + }, +}; +</script> diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue new file mode 100644 index 00000000000..4540e4248d8 --- /dev/null +++ b/app/assets/javascripts/notebook/cells/prompt.vue @@ -0,0 +1,30 @@ +<template> + <div class="prompt"> + <span v-if="type && count"> + {{ type }} [{{ count }}]: + </span> + </div> +</template> + +<script> + export default { + props: { + type: { + type: String, + required: false, + }, + count: { + type: Number, + required: false, + }, + }, + }; +</script> + +<style scoped> +.prompt { + padding: 0 10px; + min-width: 7em; + font-family: monospace; +} +</style> diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue new file mode 100644 index 00000000000..fd62c1231ef --- /dev/null +++ b/app/assets/javascripts/notebook/index.vue @@ -0,0 +1,75 @@ +<template> + <div v-if="hasNotebook"> + <component + v-for="(cell, index) in cells" + :is="cellType(cell.cell_type)" + :cell="cell" + :key="index" + :code-css-class="codeCssClass" /> + </div> +</template> + +<script> + import { + MarkdownCell, + CodeCell, + } from './cells'; + + export default { + components: { + 'code-cell': CodeCell, + 'markdown-cell': MarkdownCell, + }, + props: { + notebook: { + type: Object, + required: true, + }, + codeCssClass: { + type: String, + required: false, + default: '', + }, + }, + methods: { + cellType(type) { + return `${type}-cell`; + }, + }, + computed: { + cells() { + if (this.notebook.worksheets) { + const data = { + cells: [], + }; + + return this.notebook.worksheets.reduce((cellData, sheet) => { + const cellDataCopy = cellData; + cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells); + return cellDataCopy; + }, data).cells; + } + + return this.notebook.cells; + }, + hasNotebook() { + return Object.keys(this.notebook).length; + }, + }, + }; +</script> + +<style> +.cell, +.input, +.output { + display: flex; + width: 100%; + margin-bottom: 10px; +} + +.cell pre { + margin: 0; + width: 100%; +} +</style> diff --git a/app/assets/javascripts/notebook/lib/highlight.js b/app/assets/javascripts/notebook/lib/highlight.js new file mode 100644 index 00000000000..74ade6d2edf --- /dev/null +++ b/app/assets/javascripts/notebook/lib/highlight.js @@ -0,0 +1,22 @@ +import Prism from 'prismjs'; +import 'prismjs/components/prism-python'; +import 'prismjs/plugins/custom-class/prism-custom-class'; + +Prism.plugins.customClass.map({ + comment: 'c', + error: 'err', + operator: 'o', + constant: 'kc', + namespace: 'kn', + keyword: 'k', + string: 's', + number: 'm', + 'attr-name': 'na', + builtin: 'nb', + entity: 'ni', + function: 'nf', + tag: 'nt', + variable: 'nv', +}); + +export default Prism; |