Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2017-04-11 20:50:21 +0300
committerPhil Hughes <me@iamphill.com>2017-04-11 20:50:21 +0300
commit4755f6066bff2617988704e18c8c141056f1b4bd (patch)
tree7e1c58dfdc96134f7d2594bf63d2be226a98b4d7
parent3082a1195cc0939e0aa0b48a9f59dd84152ebdad (diff)
Moved NotebookLab assets into repo
Moved all the notebooklab assets into the GitLab repo
-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.vue49
-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
-rw-r--r--package.json2
-rw-r--r--spec/javascripts/notebook/cells/code_spec.js51
-rw-r--r--spec/javascripts/notebook/cells/markdown_spec.js37
-rw-r--r--spec/javascripts/notebook/cells/output/index_spec.js122
-rw-r--r--spec/javascripts/notebook/cells/prompt_spec.js56
-rw-r--r--spec/javascripts/notebook/index_spec.js93
-rw-r--r--spec/javascripts/notebook/lib/highlight_spec.js15
-rw-r--r--yarn.lock36
19 files changed, 841 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..edbba94811e
--- /dev/null
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -0,0 +1,49 @@
+<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';
+
+ export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ cell: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ markdown() {
+ const regex = new RegExp('^\\$\\$(.*)\\$\\$$', 'g');
+
+ const source = this.cell.source.map((line) => {
+ const matches = regex.exec(line.trim());
+
+ // Only render use the Katex library if it is actually loaded
+ if (matches && matches.length > 0 && typeof katex !== 'undefined') {
+ return katex.renderToString(matches[1]);
+ }
+
+ return line;
+ });
+
+ return marked(source.join(''));
+ },
+ },
+ };
+</script>
+
+<style>
+.markdown .katex {
+ display: block;
+ text-align: center;
+}
+</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;
diff --git a/package.json b/package.json
index a17399ddb8f..3d845aea21f 100644
--- a/package.json
+++ b/package.json
@@ -32,8 +32,10 @@
"js-cookie": "^2.1.3",
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
+ "marked": "^0.3.6",
"mousetrap": "^1.4.6",
"pikaday": "^1.5.1",
+ "prismjs": "^1.6.0",
"raphael": "^2.2.7",
"raw-loader": "^0.5.1",
"react-dev-utils": "^0.5.2",
diff --git a/spec/javascripts/notebook/cells/code_spec.js b/spec/javascripts/notebook/cells/code_spec.js
new file mode 100644
index 00000000000..e1df98b70ba
--- /dev/null
+++ b/spec/javascripts/notebook/cells/code_spec.js
@@ -0,0 +1,51 @@
+import Vue from 'vue';
+import CodeComponent from '~/notebook/cells/code.vue';
+import json from '../../fixtures/notebook/file.json';
+
+const Component = Vue.extend(CodeComponent);
+
+describe('Code component', () => {
+ let vm;
+
+ describe('without output', () => {
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[0],
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('does not render output prompt', () => {
+ expect(vm.$el.querySelectorAll('.prompt').length).toBe(1);
+ });
+ });
+
+ describe('with output', () => {
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ cell: json.cells[2],
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('does not render output prompt', () => {
+ expect(vm.$el.querySelectorAll('.prompt').length).toBe(2);
+ });
+
+ it('renders output cell', () => {
+ expect(vm.$el.querySelector('.output')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/javascripts/notebook/cells/markdown_spec.js b/spec/javascripts/notebook/cells/markdown_spec.js
new file mode 100644
index 00000000000..5c11272e7e0
--- /dev/null
+++ b/spec/javascripts/notebook/cells/markdown_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import MarkdownComponent from '~/notebook/cells/markdown.vue';
+import json from '../../fixtures/notebook/file.json';
+
+const cell = json.cells[1];
+const Component = Vue.extend(MarkdownComponent);
+
+describe('Markdown component', () => {
+ let vm;
+
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ cell,
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('does not render promot', () => {
+ expect(vm.$el.querySelector('.prompt span')).toBeNull();
+ });
+
+ it('does not render the markdown text', () => {
+ expect(
+ vm.$el.querySelector('.markdown').innerHTML.trim(),
+ ).not.toEqual(cell.source.join(''));
+ });
+
+ it('renders the markdown HTML', () => {
+ expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
+ });
+});
diff --git a/spec/javascripts/notebook/cells/output/index_spec.js b/spec/javascripts/notebook/cells/output/index_spec.js
new file mode 100644
index 00000000000..7fe8d227f05
--- /dev/null
+++ b/spec/javascripts/notebook/cells/output/index_spec.js
@@ -0,0 +1,122 @@
+import Vue from 'vue';
+import CodeComponent from '~/notebook/cells/output/index.vue';
+import json from '../../../fixtures/notebook/file.json';
+
+const Component = Vue.extend(CodeComponent);
+
+describe('Output component', () => {
+ let vm;
+
+ const createComponent = (output) => {
+ vm = new Component({
+ propsData: {
+ output,
+ count: 1,
+ },
+ });
+ vm.$mount();
+ };
+
+ describe('text output', () => {
+ beforeEach((done) => {
+ createComponent(json.cells[2].outputs[0]);
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders as plain text', () => {
+ expect(vm.$el.querySelector('pre')).not.toBeNull();
+ });
+
+ it('renders promot', () => {
+ expect(vm.$el.querySelector('.prompt span')).not.toBeNull();
+ });
+ });
+
+ describe('image output', () => {
+ beforeEach((done) => {
+ createComponent(json.cells[3].outputs[0]);
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders as an image', () => {
+ expect(vm.$el.querySelector('img')).not.toBeNull();
+ });
+
+ it('does not render the prompt', () => {
+ expect(vm.$el.querySelector('.prompt span')).toBeNull();
+ });
+ });
+
+ describe('html output', () => {
+ beforeEach((done) => {
+ createComponent(json.cells[4].outputs[0]);
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders raw HTML', () => {
+ expect(vm.$el.querySelector('p')).not.toBeNull();
+ expect(vm.$el.textContent.trim()).toBe('test');
+ });
+
+ it('does not render the prompt', () => {
+ expect(vm.$el.querySelector('.prompt span')).toBeNull();
+ });
+ });
+
+ describe('svg output', () => {
+ beforeEach((done) => {
+ createComponent(json.cells[5].outputs[0]);
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders as an svg', () => {
+ expect(vm.$el.querySelector('svg')).not.toBeNull();
+ });
+
+ it('does not render the prompt', () => {
+ expect(vm.$el.querySelector('.prompt span')).toBeNull();
+ });
+ });
+
+ describe('default to plain text', () => {
+ beforeEach((done) => {
+ createComponent(json.cells[6].outputs[0]);
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders as plain text', () => {
+ expect(vm.$el.querySelector('pre')).not.toBeNull();
+ expect(vm.$el.textContent.trim()).toContain('testing');
+ });
+
+ it('renders promot', () => {
+ expect(vm.$el.querySelector('.prompt span')).not.toBeNull();
+ });
+
+ it('renders as plain text when doesn\'t recognise other types', (done) => {
+ createComponent(json.cells[7].outputs[0]);
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('pre')).not.toBeNull();
+ expect(vm.$el.textContent.trim()).toContain('testing');
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/notebook/cells/prompt_spec.js b/spec/javascripts/notebook/cells/prompt_spec.js
new file mode 100644
index 00000000000..207fa433a59
--- /dev/null
+++ b/spec/javascripts/notebook/cells/prompt_spec.js
@@ -0,0 +1,56 @@
+import Vue from 'vue';
+import PromptComponent from '~/notebook/cells/prompt.vue';
+
+const Component = Vue.extend(PromptComponent);
+
+describe('Prompt component', () => {
+ let vm;
+
+ describe('input', () => {
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ type: 'In',
+ count: 1,
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders in label', () => {
+ expect(vm.$el.textContent.trim()).toContain('In');
+ });
+
+ it('renders count', () => {
+ expect(vm.$el.textContent.trim()).toContain('1');
+ });
+ });
+
+ describe('output', () => {
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ type: 'Out',
+ count: 1,
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders in label', () => {
+ expect(vm.$el.textContent.trim()).toContain('Out');
+ });
+
+ it('renders count', () => {
+ expect(vm.$el.textContent.trim()).toContain('1');
+ });
+ });
+});
diff --git a/spec/javascripts/notebook/index_spec.js b/spec/javascripts/notebook/index_spec.js
new file mode 100644
index 00000000000..87f18b98342
--- /dev/null
+++ b/spec/javascripts/notebook/index_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import Notebook from '~/notebook/index.vue';
+import json from '../fixtures/notebook/file.json';
+import jsonWithWorksheet from '../fixtures/notebook/worksheets.json';
+
+const Component = Vue.extend(Notebook);
+
+describe('Notebook component', () => {
+ let vm;
+
+ describe('without JSON', () => {
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ notebook: {},
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('does not render', () => {
+ expect(vm.$el.tagName).toBeUndefined();
+ });
+ });
+
+ describe('with JSON', () => {
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ notebook: json,
+ codeCssClass: 'js-code-class',
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders cells', () => {
+ expect(vm.$el.querySelectorAll('.cell').length).toBe(json.cells.length);
+ });
+
+ it('renders markdown cell', () => {
+ expect(vm.$el.querySelector('.markdown')).not.toBeNull();
+ });
+
+ it('renders code cell', () => {
+ expect(vm.$el.querySelector('pre')).not.toBeNull();
+ });
+
+ it('add code class to code blocks', () => {
+ expect(vm.$el.querySelector('.js-code-class')).not.toBeNull();
+ });
+ });
+
+ describe('with worksheets', () => {
+ beforeEach((done) => {
+ vm = new Component({
+ propsData: {
+ notebook: jsonWithWorksheet,
+ codeCssClass: 'js-code-class',
+ },
+ });
+ vm.$mount();
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('renders cells', () => {
+ expect(vm.$el.querySelectorAll('.cell').length).toBe(jsonWithWorksheet.worksheets[0].cells.length);
+ });
+
+ it('renders markdown cell', () => {
+ expect(vm.$el.querySelector('.markdown')).not.toBeNull();
+ });
+
+ it('renders code cell', () => {
+ expect(vm.$el.querySelector('pre')).not.toBeNull();
+ });
+
+ it('add code class to code blocks', () => {
+ expect(vm.$el.querySelector('.js-code-class')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/notebook/lib/highlight_spec.js b/spec/javascripts/notebook/lib/highlight_spec.js
new file mode 100644
index 00000000000..d71c5718858
--- /dev/null
+++ b/spec/javascripts/notebook/lib/highlight_spec.js
@@ -0,0 +1,15 @@
+import Prism from '~/notebook/lib/highlight';
+
+describe('Highlight library', () => {
+ it('imports python language', () => {
+ expect(Prism.languages.python).toBeDefined();
+ });
+
+ it('uses custom CSS classes', () => {
+ const el = document.createElement('div');
+ el.innerHTML = Prism.highlight('console.log("a");', Prism.languages.javascript);
+
+ expect(el.querySelector('.s')).not.toBeNull();
+ expect(el.querySelector('.nf')).not.toBeNull();
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index e16cd9c3673..e7ec3ac7f48 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1119,6 +1119,14 @@ cli-width@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
+clipboard@^1.5.5:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.6.1.tgz#65c5b654812466b0faab82dc6ba0f1d2f8e4be53"
+ dependencies:
+ good-listener "^1.2.0"
+ select "^1.1.2"
+ tiny-emitter "^1.0.0"
+
cliui@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
@@ -1596,6 +1604,10 @@ delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+delegate@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.1.2.tgz#1e1bc6f5cadda6cb6cbf7e6d05d0bcdd5712aebe"
+
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -2493,6 +2505,12 @@ globby@^5.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+good-listener@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
+ dependencies:
+ delegate "^3.1.2"
+
got@^3.2.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca"
@@ -3550,6 +3568,10 @@ map-stream@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
+marked@^0.3.6:
+ version "0.3.6"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7"
+
math-expression-evaluator@^1.2.14:
version "1.2.16"
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9"
@@ -4411,6 +4433,12 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+prismjs@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.6.0.tgz#118d95fb7a66dba2272e343b345f5236659db365"
+ optionalDependencies:
+ clipboard "^1.5.5"
+
private@^0.1.6:
version "0.1.7"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
@@ -4869,6 +4897,10 @@ select2@3.5.2-browserify:
version "3.5.2-browserify"
resolved "https://registry.yarnpkg.com/select2/-/select2-3.5.2-browserify.tgz#dc4dafda38d67a734e8a97a46f0d3529ae05391d"
+select@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
+
semver-diff@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -5364,6 +5396,10 @@ timers-browserify@^2.0.2:
dependencies:
setimmediate "^1.0.4"
+tiny-emitter@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-1.1.0.tgz#ab405a21ffed814a76c19739648093d70654fecb"
+
tmp@0.0.28, tmp@0.0.x:
version "0.0.28"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120"