summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorJacob Schatz <jschatz@gitlab.com>2017-04-24 18:17:49 +0000
committerJacob Schatz <jschatz@gitlab.com>2017-04-24 18:17:49 +0000
commit640e8093d4e9a96e3c6b787d6b5bd5f679594724 (patch)
treeecd3ed4b799957e74b2ffe3663f4fb11a8c41088 /app
parent3ac4ef221b532e3425d890f044ef7d39ac81e163 (diff)
parent1d99c7755ce337a715847344f4f20df23b29bfc7 (diff)
downloadgitlab-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.js6
-rw-r--r--app/assets/javascripts/notebook/cells/code.vue58
-rw-r--r--app/assets/javascripts/notebook/cells/code/index.vue57
-rw-r--r--app/assets/javascripts/notebook/cells/index.js2
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue98
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue22
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue27
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue83
-rw-r--r--app/assets/javascripts/notebook/cells/prompt.vue30
-rw-r--r--app/assets/javascripts/notebook/index.vue75
-rw-r--r--app/assets/javascripts/notebook/lib/highlight.js22
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;