├── .gitignore ├── LICENSE.md ├── README.md ├── example.gif ├── example_notebooks ├── big_example.js └── markdown.js ├── keymaps └── atom-observable.json ├── lib ├── atom-observable-view.js ├── atom-observable.js └── viewer │ ├── bundle.js │ ├── index.html │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── index.js │ └── render.js │ └── yarn.lock ├── menus └── atom-observable.json ├── package.json ├── spec ├── atom-observable-spec.js └── atom-observable-view-spec.js ├── styles └── atom-observable.less └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atom-observable package 2 | 3 | Render full Observable notebooks in Atom! 4 | 5 | ![](./example.gif) 6 | 7 | ## Installing 8 | 9 | [Package here](https://atom.io/packages/atom-observable). You can `apm install atom-observable` or just look up `atom-observable` in the Atom package installer. 10 | 11 | ## Usage 12 | 13 | When you have a "notebook" file open, just press `Alt+Ctrl+O` (or `Packages` -> `atom-observable` -> `Toggle`), and a preview will popup, with the rendered notebook. 14 | 15 | Once the preview is open, whenever you save the file, the entire preview will reload. It's not as cool of a dev experience as observablehq.com is, but it's something! 16 | 17 | All [`stdlib`](https://github.com/observablehq/stdlib) should work - `DOM`, `require`, `html`, `md`, all that. `import` cells will resolve from observablehq.com by default. 18 | 19 | ### What is a "notebook"? 20 | 21 | It's basically just a regular notebook you would write on observablehq.com, but in a file. It can be made of several top-level cell definitions - with import support! 22 | 23 | For example: 24 | 25 | ```javascript 26 | 27 | a = 1 28 | 29 | b = 2 30 | 31 | c = a + b 32 | 33 | viewof name = DOM.input() 34 | 35 | md`Hello ${name}!` 36 | 37 | import {chart} from "@d3/bar-chart" 38 | ``` 39 | 40 | Keep in mind - not all javascript files are valid Observable syntax. [`example_notebooks`](./example_notebooks) has a few examples of what could work. 41 | 42 | ## How it works 43 | 44 | Most of the magic happens with [`@alex.garcia/unofficial-observablehq-compiler`](https://github.com/asg017/unofficial-observablehq-compiler) - an unofficial compiler for Observable notebook syntax. All this package does is basically send the file contents to an iframe, and a script in the iframe uses the compiler to compile it to an element. 45 | 46 | ## Contributing 47 | 48 | Please do! There's a ton of potential here - access to node.js, custom libraries, better local development. Take a look at these [issues](https://github.com/asg017/atom-observable/issues) to find something to work on. Just please follow the [Contributor Covenant](https://www.contributor-covenant.org/) in all your interactions 😄 49 | 50 | ## Acknowledgement 51 | 52 | This was built with libraries like [@observablehq/runtime](https://github.com/observablehq/runtime) and [@observablehq/parser](https://github.com/observablehq/parser) which are licensed under ISC. 53 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asg017/atom-observable/59a663e6235afe78b1296bb7050e68622d15cc92/example.gif -------------------------------------------------------------------------------- /example_notebooks/big_example.js: -------------------------------------------------------------------------------- 1 | md`# Hello, unoffical-atom-observable!` 2 | 3 | md`This is an example Observable notebook that can 4 | be ran inside of Atom!` 5 | 6 | md`\`unofficial-atom-observable\` is an Atom package 7 | that gives a preview to any local Observable notebook 8 | that you have.` 9 | 10 | 11 | md`Take this file - it's a typical Javascript file 12 | (with [Observable syntax](https://observablehq.com/@observablehq/observables-not-javascript)). 13 | It's saved to my computer as \`example.notebook.js\`. 14 | Just like any other file, I can open it in Atom - 15 | and with \`unofficial-atom-observable\`, I can press 16 | \`Ctrl+Alt+O\` and a new preview window will pop up - 17 | with a fully fledged rendered notebook!` 18 | 19 | md`Nearly all Observable syntax will work - including 20 | normal javascript. For example:` 21 | 22 | md`3 + 4 = ${3 + 4}` 23 | 24 | md`"xyz" in base64 = ${btoa("xyz")}` 25 | 26 | 27 | { 28 | let i = 1; 29 | while(i) { 30 | yield Promises.tick(100, ++i); 31 | } 32 | } 33 | 34 | md`Just like on the Observable site, \`require\` can be used to 35 | import thousands of javascript packages available on npm:` 36 | 37 | d3 = require('d3') 38 | 39 | data = d3.csv("https://gist.githubusercontent.com/mbostock/4063570/raw/11847750012dfe5351ee1eb290d2a254a67051d0/flare.csv") 40 | 41 | simpleStatistics = require("simple-statistics") 42 | 43 | simpleStatistics.standardDeviation([1, 2, 3]) 44 | 45 | md`Not only that - you can also import any public 46 | or shared notebook that's on observablehq.com! ` 47 | 48 | import {textarea} from '@jashkenas/inputs' 49 | 50 | viewof x = textarea("Woah, this using Jeremy's inputs library!") 51 | 52 | 53 | import {ramp} from "@mbostock/color-ramp" 54 | 55 | ramp(t => `hsl(${t * 360}, 100%, 50%)`) 56 | 57 | import {map} from "@d3/interrupted-sinu-mollweide" 58 | 59 | map 60 | 61 | md`You can also inject values into imported notebooks - ` 62 | 63 | import {slider} from '@jashkenas/inputs' 64 | 65 | viewof height = slider({min:100, max:600, value:200}) 66 | 67 | import {canvas} with {height} from "@mbostock/connected-particles-iii" 68 | 69 | html`${canvas}` 70 | 71 | md`Playing with Observable stdlib works well, too!` 72 | 73 | html`
yay` 74 | 75 | html`` 76 | 77 | 78 | md`## Caveats 79 | - Code for the notebook isn't shown in rendered output - just the output cells. 80 | - The entire notebook refreshing when you save the source file - so development 81 | is a little cumbersome and slow. 82 | - observablehq.com specific features don't exist (Secrets, DBClient, suggestions, 83 | teams, etc.) 84 | - CSS inside the rendered notebook is a little messy still 85 | - Syntax highlighting in markdown components don't work 86 | - 87 | 88 | \`unofficial-atom-observable\` isn't complete yet. 89 | Here's some stuff that needs more development: 90 | 91 | ☐ mutable probably doesn't work 92 | 93 | ☐ importing viewof probably doesn't work 94 | 95 | Here are some cool features to work on in the future: 96 | 97 | - Access your computer's environment variables with a new stdlib builtin (e.g. \`env.get('API_TOKEN')\`) 98 | - Access your computer's files (e.g. \`d3.csv("file://path/to/file")\`) 99 | - Importing other local javascript notebooks as modules 100 | - Atom syntax highlighting for Observable syntax (viewof, mutable, cell blocks, etc.) 101 | - Linter/prettier for Observable syntax 102 | 103 | ` 104 | -------------------------------------------------------------------------------- /example_notebooks/markdown.js: -------------------------------------------------------------------------------- 1 | md` 2 | # Untitled 3 | 4 | Somebody once told me the world was gonna roll me 5 | 6 | I: 7 | 8 | - aint the sharpest 9 | - **tool in** the \`shed\` 10 | 11 | ~~~python 12 | 13 | import fake as f 14 | 15 | def scrape(a,b,c): 16 | d = f.get(a,b,c) 17 | return d 18 | ~~~ 19 | `; 20 | 21 | md` 22 | | header1 | header2 | 23 | | ------- | ------- | 24 | | asdf | fdassdf | 25 | `; 26 | 27 | md` 28 | | Tables | Are | Cool | 29 | | ------------- | :-----------: | -----: | 30 | | col 3 is | right-aligned | \$1600 | 31 | | col 2 is | centered | \$12 | 32 | | zebra stripes | are neat | \$1 | 33 | `; 34 | 35 | md` 36 | > Blockquotes are very handy in email to emulate reply text. 37 | > This line is part of the same quote. 38 | `; 39 | -------------------------------------------------------------------------------- /keymaps/atom-observable.json: -------------------------------------------------------------------------------- 1 | { 2 | "atom-workspace": { 3 | "ctrl-alt-o": "atom-observable:toggle" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /lib/atom-observable-view.js: -------------------------------------------------------------------------------- 1 | "use babel"; 2 | const { Emitter, Disposable, CompositeDisposable, File } = require("atom"); 3 | const { join } = require("path"); 4 | 5 | const viewerSrc = `file://${join(__dirname, "viewer", "index.html")}`; 6 | 7 | export default class AtomObservableView { 8 | constructor({ editorId, filePath }) { 9 | // Create root element 10 | this.editorId = editorId; 11 | this.filePath = filePath; 12 | 13 | this.element = document.createElement("div"); 14 | this.element.classList.add("atom-observable"); 15 | this.element.tabIndex = -1; 16 | 17 | this.output = document.createElement("div"); 18 | this.output.classList.add("output"); 19 | 20 | this.element.appendChild(this.output); 21 | 22 | this.iframe = document.createElement("iframe"); 23 | this.iframe.style = 24 | "width: 100%; height: 100%; background-color: white; overflow: hidden;"; 25 | this.iframe.src = viewerSrc; 26 | this.element.appendChild(this.iframe); 27 | 28 | this.emitter = new Emitter(); 29 | this.disposables = new CompositeDisposable(); 30 | 31 | if (this.editorId == null) { 32 | console.error("what how, editorId is null"); 33 | } 34 | this.editor = this.editorForId(editorId); 35 | this.disposables.add( 36 | this.editor.onDidSave(() => { 37 | this.reloadNotebookViewer(this.filePath); 38 | }) 39 | ); 40 | this.reloadNotebookViewer(this.filePath); 41 | } 42 | 43 | editorForId(editorId) { 44 | for (const editor of atom.workspace.getTextEditors()) { 45 | if (editor.id != null && editor.id.toString() === editorId.toString()) { 46 | return editor; 47 | } 48 | } 49 | return null; 50 | } 51 | 52 | // Returns an object that can be retrieved when package is activated 53 | serialize() { 54 | return { 55 | deserializer: "AtomObservableView", 56 | filePath: this.getPath() != null ? this.getPath() : this.filePath, 57 | editorId: this.editorId 58 | }; 59 | } 60 | 61 | // Tear down any state and detach 62 | destroy() { 63 | this.element.remove(); 64 | } 65 | 66 | getElement() { 67 | return this.element; 68 | } 69 | getTitle() { 70 | if (this.file != null && this.getPath() != null) { 71 | return `${path.basename(this.getPath())} Preview`; 72 | } else if (this.editor != null) { 73 | return `${this.editor.getTitle()} Preview`; 74 | } else { 75 | return "Atom Observable"; 76 | } 77 | } 78 | 79 | reloadNotebookViewer(filePath) { 80 | this.file = new File(filePath); 81 | return this.getFileSource().then(text => { 82 | this.iframe.src = ""; 83 | this.iframe.src = viewerSrc; 84 | new Promise((res, rej) => { 85 | this.iframe.onload = e => res(); 86 | this.iframe.onerror = e => rej(e); 87 | }).then(() => { 88 | const iframeWindow = this.iframe.contentWindow; 89 | iframeWindow.postMessage(text, "file://"); 90 | }); 91 | }); 92 | } 93 | getFileSource() { 94 | if (this.file && this.file.getPath()) { 95 | return this.file 96 | .read() 97 | .then(source => { 98 | if (source === null) { 99 | return Promise.reject( 100 | new Error(`${this.file.getBaseName()} could not be found`) 101 | ); 102 | } else { 103 | return Promise.resolve(source); 104 | } 105 | }) 106 | .catch(reason => Promise.reject(reason)); 107 | } else if (this.editor != null) { 108 | return Promise.resolve(this.editor.getText()); 109 | } else { 110 | return Promise.reject(new Error("No editor found")); 111 | } 112 | } 113 | getURI() { 114 | if (this.file != null) { 115 | return `atom-observable://${this.getPath()}`; 116 | } else { 117 | return `atom-observable://editor/${this.editorId}`; 118 | } 119 | } 120 | getPath() { 121 | if (this.file != null) { 122 | return this.file.getPath(); 123 | } else if (this.editor != null) { 124 | return this.editor.getPath(); 125 | } 126 | } 127 | showError(result) { 128 | this.output.textContent = ""; 129 | const h2 = document.createElement("h2"); 130 | h2.textContent = "Previewing Markdown Failed"; 131 | this.output.appendChild(h2); 132 | if (result) { 133 | const h3 = document.createElement("h3"); 134 | h3.textContent = result.message; 135 | this.output.appendChild(h3); 136 | } 137 | } 138 | 139 | showLoading() { 140 | this.output.textContent = ""; 141 | const div = document.createElement("div"); 142 | div.classList.add("markdown-spinner"); 143 | div.textContent = "Loading Markdown\u2026"; 144 | this.output.appendChild(div); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/atom-observable.js: -------------------------------------------------------------------------------- 1 | "use babel"; 2 | 3 | import AtomObservableView from "./atom-observable-view"; 4 | import { CompositeDisposable } from "atom"; 5 | 6 | const isAtomObservableView = function(object) { 7 | return object instanceof AtomObservableView; 8 | }; 9 | 10 | export default { 11 | subscriptions: null, 12 | 13 | activate(state) { 14 | this.subscriptions = new CompositeDisposable(); 15 | 16 | this.subscriptions.add( 17 | atom.commands.add("atom-workspace", { 18 | "atom-observable:toggle": () => this.toggle() 19 | }) 20 | ); 21 | this.subscriptions.add( 22 | atom.workspace.addOpener(uriToOpen => { 23 | let [protocol, path] = uriToOpen.split("://"); 24 | if (protocol !== "atom-observable") { 25 | return; 26 | } 27 | 28 | try { 29 | path = decodeURI(path); 30 | } catch (error) { 31 | return; 32 | } 33 | 34 | if (path.startsWith("editor/")) { 35 | return this.createAtomObservableView({ editorId: path.substring(7) }); 36 | } else { 37 | return this.createAtomObservableView({ filePath: path }); 38 | } 39 | }) 40 | ); 41 | }, 42 | 43 | deactivate() { 44 | this.modalPanel.destroy(); 45 | this.subscriptions.dispose(); 46 | }, 47 | 48 | createAtomObservableView(state) { 49 | if (state.editorId || fs.isFileSync(state.filePath)) { 50 | return new AtomObservableView(state); 51 | } 52 | }, 53 | toggle() { 54 | const editor = atom.workspace.getActiveTextEditor(); 55 | if (editor == null) { 56 | return; 57 | } 58 | 59 | if (!this.removePreviewForEditor(editor)) { 60 | return this.addPreviewForEditor(editor); 61 | } 62 | }, 63 | uriForEditor(editor) { 64 | return `atom-observable://editor/${editor.id}`; 65 | }, 66 | 67 | removePreviewForEditor(editor) { 68 | const uri = this.uriForEditor(editor); 69 | const previewPane = atom.workspace.paneForURI(uri); 70 | if (previewPane != null) { 71 | previewPane.destroyItem(previewPane.itemForURI(uri)); 72 | return true; 73 | } else { 74 | return false; 75 | } 76 | }, 77 | 78 | addPreviewForEditor(editor) { 79 | const uri = this.uriForEditor(editor); 80 | const previousActivePane = atom.workspace.getActivePane(); 81 | const options = { searchAllPanes: true }; 82 | return atom.workspace.open(uri, options).then(function(AtomObservableView) { 83 | if (isAtomObservableView(AtomObservableView)) { 84 | previousActivePane.activate(); 85 | } 86 | }); 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /lib/viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 16 | 31 | 32 | 33 |
34 |
35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /lib/viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-observable-viewer", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@alex.garcia/unofficial-observablehq-compiler": "^0.0.4", 8 | "@observablehq/parser": "^1.2.1", 9 | "@observablehq/runtime": "^4.4.3" 10 | }, 11 | "devDependencies": { 12 | "rollup": "^1.21.2", 13 | "rollup-plugin-commonjs": "^10.1.0", 14 | "rollup-plugin-node-resolve": "^5.2.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/viewer/rollup.config.js: -------------------------------------------------------------------------------- 1 | import node from "rollup-plugin-node-resolve"; 2 | import commonjs from "rollup-plugin-commonjs"; 3 | 4 | export default { 5 | input: "src/index.js", 6 | output: [ 7 | { 8 | file: "bundle.js", 9 | format: "es" 10 | } 11 | ], 12 | plugins: [node(), commonjs()] 13 | }; 14 | -------------------------------------------------------------------------------- /lib/viewer/src/index.js: -------------------------------------------------------------------------------- 1 | import { renderNotebook } from "./render"; 2 | const main = document.querySelector("#main"); 3 | 4 | window.addEventListener( 5 | "message", 6 | e => { 7 | if (e.origin !== "file://") { 8 | return; 9 | } 10 | const nbElement = renderNotebook(e.data); 11 | main.appendChild(nbElement); 12 | }, 13 | false 14 | ); 15 | -------------------------------------------------------------------------------- /lib/viewer/src/render.js: -------------------------------------------------------------------------------- 1 | import { Runtime, Inspector } from "@observablehq/runtime"; 2 | import compiler from "@alex.garcia/unofficial-observablehq-compiler"; 3 | 4 | const render = function(text) { 5 | const root = document.createElement("div"); 6 | 7 | const compile = new compiler.Compiler(); 8 | const define = compile.module(text); 9 | const rt = new Runtime(); 10 | rt.module(define, Inspector.into(root)); 11 | return root; 12 | }; 13 | 14 | export function renderNotebook(text) { 15 | const domFragment = render(text); 16 | return domFragment; 17 | } 18 | -------------------------------------------------------------------------------- /lib/viewer/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@alex.garcia/unofficial-observablehq-compiler@^0.0.4": 6 | version "0.0.4" 7 | resolved "https://registry.yarnpkg.com/@alex.garcia/unofficial-observablehq-compiler/-/unofficial-observablehq-compiler-0.0.4.tgz#8c009200f1751780ff6e23615daae42bd9422664" 8 | integrity sha512-GPHMVIjHqatYyPNVMJPsQDvYkC6BjvdUt8ATQoWQSsPbaKf2WDRccOZou0PYilKmkUiu1LzaAGyJqiX+enZBMQ== 9 | dependencies: 10 | "@observablehq/parser" "^1.2.1" 11 | "@observablehq/runtime" "^4.4.4" 12 | 13 | "@observablehq/inspector@^3.1.0": 14 | version "3.1.0" 15 | resolved "https://registry.yarnpkg.com/@observablehq/inspector/-/inspector-3.1.0.tgz#4726f9aabc58aa410a4ba27adf89b180fa9a302b" 16 | integrity sha512-DOx40q05QZdQnYpUsKch+8raWGmKSBh5DIAinQRoK+ij7Eq/PKpHJvNrrhThF4+2b/qViUhZGOwMLPhlC2qMzg== 17 | dependencies: 18 | esm "^3.0.84" 19 | 20 | "@observablehq/parser@^1.2.1": 21 | version "1.2.1" 22 | resolved "https://registry.yarnpkg.com/@observablehq/parser/-/parser-1.2.1.tgz#e55460d6de0a419235e918f3b9f35659297eeae3" 23 | integrity sha512-5RJhh4nsET6l+Ky6ZT0Qa+ts4KZ6L0cbpLGZxxnwICc6eIDA2/UftIhlp7cL3ZYUQuaP8BgCNV/3c8Z9+WaOnQ== 24 | dependencies: 25 | acorn "^6.0.4" 26 | acorn-bigint "^0.3.1" 27 | acorn-walk "^6.1.1" 28 | 29 | "@observablehq/runtime@^4.4.3": 30 | version "4.4.3" 31 | resolved "https://registry.yarnpkg.com/@observablehq/runtime/-/runtime-4.4.3.tgz#c0aca6d376fafd8e5ad5168efb84f5a1f0ffe05c" 32 | integrity sha512-sjG9i3js5Ayd5bLVjxejADxb3Apw1V5lnw6JX8Nz5fKC76Nk/TTte2ItWCZOyqAom5A8HKdEzIDIJ5TNy3w/eQ== 33 | dependencies: 34 | "@observablehq/inspector" "^3.1.0" 35 | "@observablehq/stdlib" "^3.0.0" 36 | esm "^3.0.84" 37 | 38 | "@observablehq/runtime@^4.4.4": 39 | version "4.4.4" 40 | resolved "https://registry.yarnpkg.com/@observablehq/runtime/-/runtime-4.4.4.tgz#e799204fe04ae0eaf40fe7a65dde47f31e10c578" 41 | integrity sha512-lHfXGlNfbd/kYV8weO4r4O7jTlmEWFmYgYEGmktiMkcWlvOHdGRmXUf6NxmdbJbZFbGVwxvcV2cQgpUwz/3xBg== 42 | dependencies: 43 | "@observablehq/inspector" "^3.1.0" 44 | "@observablehq/stdlib" "^3.0.0" 45 | esm "^3.0.84" 46 | 47 | "@observablehq/stdlib@^3.0.0": 48 | version "3.1.1" 49 | resolved "https://registry.yarnpkg.com/@observablehq/stdlib/-/stdlib-3.1.1.tgz#65e4368cba5920aa649c8c2a0c9d8f23bb51150f" 50 | integrity sha512-lwt+ThlV5QIFg7WCIKn7IvCnGpEsGw8BkASPd5yyNZ7hU8gxubE7jOChVte8Lf+yMQmZsju55bCsk813+u4GxQ== 51 | dependencies: 52 | d3-require "^1.2.0" 53 | esm "^3.0.84" 54 | marked "https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f" 55 | 56 | "@types/estree@0.0.39": 57 | version "0.0.39" 58 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 59 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 60 | 61 | "@types/node@*", "@types/node@^12.7.4": 62 | version "12.7.4" 63 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.4.tgz#64db61e0359eb5a8d99b55e05c729f130a678b04" 64 | integrity sha512-W0+n1Y+gK/8G2P/piTkBBN38Qc5Q1ZSO6B5H3QmPCUewaiXOo2GCAWZ4ElZCcNhjJuBSUSLGFUJnmlCn5+nxOQ== 65 | 66 | "@types/resolve@0.0.8": 67 | version "0.0.8" 68 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" 69 | integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== 70 | dependencies: 71 | "@types/node" "*" 72 | 73 | acorn-bigint@^0.3.1: 74 | version "0.3.1" 75 | resolved "https://registry.yarnpkg.com/acorn-bigint/-/acorn-bigint-0.3.1.tgz#edb40a414dcaf5a09c2933db6bed79454b3ff46a" 76 | integrity sha512-WT9LheDC4/d/sD/jgC6L5UMq4U9X3KNMy0JrXp/MdJL83ZqcuPQuMkj50beOX0dMub8IoZUYycfN7bIVZuU5zg== 77 | 78 | acorn-walk@^6.1.1: 79 | version "6.2.0" 80 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" 81 | integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== 82 | 83 | acorn@^6.0.4: 84 | version "6.3.0" 85 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" 86 | integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== 87 | 88 | acorn@^7.0.0: 89 | version "7.0.0" 90 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a" 91 | integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ== 92 | 93 | builtin-modules@^3.1.0: 94 | version "3.1.0" 95 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" 96 | integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== 97 | 98 | d3-require@^1.2.0: 99 | version "1.2.3" 100 | resolved "https://registry.yarnpkg.com/d3-require/-/d3-require-1.2.3.tgz#65a1c2137e69c5c442f1ca2a04f72a3965124c16" 101 | integrity sha512-zfat3WTZRZmh7jCrTIhg4zZvivA0DZvez8lZq0JwYG9aW8LcSUFO4eiPnL5F2MolHcLE8CLfEt06sPP8N7y3AQ== 102 | 103 | esm@^3.0.84: 104 | version "3.2.25" 105 | resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" 106 | integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== 107 | 108 | estree-walker@^0.6.1: 109 | version "0.6.1" 110 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" 111 | integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== 112 | 113 | is-module@^1.0.0: 114 | version "1.0.0" 115 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" 116 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= 117 | 118 | is-reference@^1.1.2: 119 | version "1.1.3" 120 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.3.tgz#e99059204b66fdbe09305cfca715a29caa5c8a51" 121 | integrity sha512-W1iHHv/oyBb2pPxkBxtaewxa1BC58Pn5J0hogyCdefwUIvb6R+TGbAcIa4qPNYLqLhb3EnOgUf2MQkkF76BcKw== 122 | dependencies: 123 | "@types/estree" "0.0.39" 124 | 125 | magic-string@^0.25.2: 126 | version "0.25.3" 127 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" 128 | integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA== 129 | dependencies: 130 | sourcemap-codec "^1.4.4" 131 | 132 | "marked@https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f": 133 | version "0.3.12" 134 | resolved "https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f" 135 | 136 | path-parse@^1.0.6: 137 | version "1.0.6" 138 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 139 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 140 | 141 | resolve@^1.11.0, resolve@^1.11.1: 142 | version "1.12.0" 143 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" 144 | integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== 145 | dependencies: 146 | path-parse "^1.0.6" 147 | 148 | rollup-plugin-commonjs@^10.1.0: 149 | version "10.1.0" 150 | resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb" 151 | integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q== 152 | dependencies: 153 | estree-walker "^0.6.1" 154 | is-reference "^1.1.2" 155 | magic-string "^0.25.2" 156 | resolve "^1.11.0" 157 | rollup-pluginutils "^2.8.1" 158 | 159 | rollup-plugin-node-resolve@^5.2.0: 160 | version "5.2.0" 161 | resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz#730f93d10ed202473b1fb54a5997a7db8c6d8523" 162 | integrity sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw== 163 | dependencies: 164 | "@types/resolve" "0.0.8" 165 | builtin-modules "^3.1.0" 166 | is-module "^1.0.0" 167 | resolve "^1.11.1" 168 | rollup-pluginutils "^2.8.1" 169 | 170 | rollup-pluginutils@^2.8.1: 171 | version "2.8.1" 172 | resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97" 173 | integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg== 174 | dependencies: 175 | estree-walker "^0.6.1" 176 | 177 | rollup@^1.21.2: 178 | version "1.21.2" 179 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.21.2.tgz#eaabd07d0bd309587ad8bebf731fca6fcb96f4d0" 180 | integrity sha512-sCAHlcQ/PExU5t/kRwkEWHdhGmQrZ2IgdQzbjPVNfhWbKHMMZGYqkASVTpQqRPLtQKg15xzEscc+BnIK/TE7/Q== 181 | dependencies: 182 | "@types/estree" "0.0.39" 183 | "@types/node" "^12.7.4" 184 | acorn "^7.0.0" 185 | 186 | sourcemap-codec@^1.4.4: 187 | version "1.4.6" 188 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9" 189 | integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg== 190 | -------------------------------------------------------------------------------- /menus/atom-observable.json: -------------------------------------------------------------------------------- 1 | { 2 | "context-menu": { 3 | "atom-text-editor": [ 4 | { 5 | "label": "Toggle atom-observable", 6 | "command": "atom-observable:toggle" 7 | } 8 | ] 9 | }, 10 | "menu": [ 11 | { 12 | "label": "Packages", 13 | "submenu": [ 14 | { 15 | "label": "atom-observable", 16 | "submenu": [ 17 | { 18 | "label": "Toggle", 19 | "command": "atom-observable:toggle" 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-observable", 3 | "main": "./lib/atom-observable", 4 | "version": "0.4.1", 5 | "description": "Render Observable notebooks directly in Atom!", 6 | "keywords": [], 7 | "activationCommands": { 8 | "atom-workspace": "atom-observable:toggle" 9 | }, 10 | "repository": "https://github.com/asg017/atom-observable", 11 | "license": "MIT", 12 | "engines": { 13 | "atom": ">=1.0.0 <2.0.0" 14 | }, 15 | "dependencies": { 16 | "@observablehq/parser": "^1.2.1", 17 | "@observablehq/runtime": "^4.3.3", 18 | "fs-extra": "^8.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/atom-observable-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import AtomObservable from '../lib/atom-observable'; 4 | 5 | // Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs. 6 | // 7 | // To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit` 8 | // or `fdescribe`). Remove the `f` to unfocus the block. 9 | 10 | describe('AtomObservable', () => { 11 | let workspaceElement, activationPromise; 12 | 13 | beforeEach(() => { 14 | workspaceElement = atom.views.getView(atom.workspace); 15 | activationPromise = atom.packages.activatePackage('atom-observable'); 16 | }); 17 | 18 | describe('when the atom-observable:toggle event is triggered', () => { 19 | it('hides and shows the modal panel', () => { 20 | // Before the activation event the view is not on the DOM, and no panel 21 | // has been created 22 | expect(workspaceElement.querySelector('.atom-observable')).not.toExist(); 23 | 24 | // This is an activation event, triggering it will cause the package to be 25 | // activated. 26 | atom.commands.dispatch(workspaceElement, 'atom-observable:toggle'); 27 | 28 | waitsForPromise(() => { 29 | return activationPromise; 30 | }); 31 | 32 | runs(() => { 33 | expect(workspaceElement.querySelector('.atom-observable')).toExist(); 34 | 35 | let atomObservableElement = workspaceElement.querySelector('.atom-observable'); 36 | expect(atomObservableElement).toExist(); 37 | 38 | let atomObservablePanel = atom.workspace.panelForItem(atomObservableElement); 39 | expect(atomObservablePanel.isVisible()).toBe(true); 40 | atom.commands.dispatch(workspaceElement, 'atom-observable:toggle'); 41 | expect(atomObservablePanel.isVisible()).toBe(false); 42 | }); 43 | }); 44 | 45 | it('hides and shows the view', () => { 46 | // This test shows you an integration test testing at the view level. 47 | 48 | // Attaching the workspaceElement to the DOM is required to allow the 49 | // `toBeVisible()` matchers to work. Anything testing visibility or focus 50 | // requires that the workspaceElement is on the DOM. Tests that attach the 51 | // workspaceElement to the DOM are generally slower than those off DOM. 52 | jasmine.attachToDOM(workspaceElement); 53 | 54 | expect(workspaceElement.querySelector('.atom-observable')).not.toExist(); 55 | 56 | // This is an activation event, triggering it causes the package to be 57 | // activated. 58 | atom.commands.dispatch(workspaceElement, 'atom-observable:toggle'); 59 | 60 | waitsForPromise(() => { 61 | return activationPromise; 62 | }); 63 | 64 | runs(() => { 65 | // Now we can test for view visibility 66 | let atomObservableElement = workspaceElement.querySelector('.atom-observable'); 67 | expect(atomObservableElement).toBeVisible(); 68 | atom.commands.dispatch(workspaceElement, 'atom-observable:toggle'); 69 | expect(atomObservableElement).not.toBeVisible(); 70 | }); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /spec/atom-observable-view-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import AtomObservableView from '../lib/atom-observable-view'; 4 | 5 | describe('AtomObservableView', () => { 6 | it('has one valid test', () => { 7 | expect('life').toBe('easy'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /styles/atom-observable.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .atom-observable { 8 | width: 100%; 9 | height: 100%; 10 | overflow-y: scroll; 11 | } 12 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@observablehq/inspector@^3.1.0": 6 | version "3.1.0" 7 | resolved "https://registry.yarnpkg.com/@observablehq/inspector/-/inspector-3.1.0.tgz#4726f9aabc58aa410a4ba27adf89b180fa9a302b" 8 | integrity sha512-DOx40q05QZdQnYpUsKch+8raWGmKSBh5DIAinQRoK+ij7Eq/PKpHJvNrrhThF4+2b/qViUhZGOwMLPhlC2qMzg== 9 | dependencies: 10 | esm "^3.0.84" 11 | 12 | "@observablehq/parser@^1.2.1": 13 | version "1.2.1" 14 | resolved "https://registry.yarnpkg.com/@observablehq/parser/-/parser-1.2.1.tgz#e55460d6de0a419235e918f3b9f35659297eeae3" 15 | integrity sha512-5RJhh4nsET6l+Ky6ZT0Qa+ts4KZ6L0cbpLGZxxnwICc6eIDA2/UftIhlp7cL3ZYUQuaP8BgCNV/3c8Z9+WaOnQ== 16 | dependencies: 17 | acorn "^6.0.4" 18 | acorn-bigint "^0.3.1" 19 | acorn-walk "^6.1.1" 20 | 21 | "@observablehq/runtime@^4.3.3": 22 | version "4.3.3" 23 | resolved "https://registry.yarnpkg.com/@observablehq/runtime/-/runtime-4.3.3.tgz#5970c79b26adb2f6806e0c3a322e3af009b86844" 24 | integrity sha512-g8z6bGNJLgnhiN3sgDcG5/+MkyDtncLuoCyXR9DqIPIb3cOQq/5fD3KGAZ2/Glu4uCsYykBdBuXL2RF6rJLRuA== 25 | dependencies: 26 | "@observablehq/inspector" "^3.1.0" 27 | "@observablehq/stdlib" "^3.0.0" 28 | esm "^3.0.84" 29 | 30 | "@observablehq/stdlib@^3.0.0": 31 | version "3.0.4" 32 | resolved "https://registry.yarnpkg.com/@observablehq/stdlib/-/stdlib-3.0.4.tgz#12be3fa9ffc611608e85f39d04f920ed1d119acd" 33 | integrity sha512-5550y5NFIZMnXoq1s9hdE8rEN7cn55TrZoGABCxmL/N9sHwHlmw/CPDWTb60NzBcQpNfL1lYlCIAZryal8rfrg== 34 | dependencies: 35 | d3-require "^1.2.0" 36 | esm "^3.0.84" 37 | marked "https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f" 38 | 39 | acorn-bigint@^0.3.1: 40 | version "0.3.1" 41 | resolved "https://registry.yarnpkg.com/acorn-bigint/-/acorn-bigint-0.3.1.tgz#edb40a414dcaf5a09c2933db6bed79454b3ff46a" 42 | integrity sha512-WT9LheDC4/d/sD/jgC6L5UMq4U9X3KNMy0JrXp/MdJL83ZqcuPQuMkj50beOX0dMub8IoZUYycfN7bIVZuU5zg== 43 | 44 | acorn-walk@^6.1.1: 45 | version "6.2.0" 46 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" 47 | integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== 48 | 49 | acorn@^6.0.4: 50 | version "6.3.0" 51 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" 52 | integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== 53 | 54 | d3-require@^1.2.0: 55 | version "1.2.3" 56 | resolved "https://registry.yarnpkg.com/d3-require/-/d3-require-1.2.3.tgz#65a1c2137e69c5c442f1ca2a04f72a3965124c16" 57 | integrity sha512-zfat3WTZRZmh7jCrTIhg4zZvivA0DZvez8lZq0JwYG9aW8LcSUFO4eiPnL5F2MolHcLE8CLfEt06sPP8N7y3AQ== 58 | 59 | esm@^3.0.84: 60 | version "3.2.25" 61 | resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" 62 | integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== 63 | 64 | fs-extra@^8.1.0: 65 | version "8.1.0" 66 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 67 | integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== 68 | dependencies: 69 | graceful-fs "^4.2.0" 70 | jsonfile "^4.0.0" 71 | universalify "^0.1.0" 72 | 73 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 74 | version "4.2.2" 75 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" 76 | integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== 77 | 78 | jsonfile@^4.0.0: 79 | version "4.0.0" 80 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 81 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 82 | optionalDependencies: 83 | graceful-fs "^4.1.6" 84 | 85 | "marked@https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f": 86 | version "0.3.12" 87 | resolved "https://github.com/observablehq/marked.git#94c6b946f462fd25db4465d71a6859183f86c57f" 88 | 89 | universalify@^0.1.0: 90 | version "0.1.2" 91 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 92 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 93 | --------------------------------------------------------------------------------