├── .gitignore ├── docs ├── assets │ └── pyroam.gif └── pyroam.mdx ├── src ├── index.ts ├── keyboard.ts ├── junkyard.txt ├── pyodide.ts ├── notebook.ts ├── api.ts ├── helpers.js ├── observable.ts ├── helpers.ts └── test │ └── helpers.test.ts ├── babel.config.js ├── tsconfig.json ├── README.md ├── package.json └── dist ├── pyroam.js └── pyroam.js.map /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vscode 3 | yarn.lock 4 | coverage 5 | node_modules 6 | .idea 7 | dist 8 | -------------------------------------------------------------------------------- /docs/assets/pyroam.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stvad/pyroam/master/docs/assets/pyroam.gif -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { handleKeyPress } from "./keyboard"; 2 | 3 | console.log("Loading pyroam."); 4 | 5 | document.addEventListener("keydown", handleKeyPress); 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // babel.config.js 2 | module.exports = { 3 | presets: [['@babel/preset-env', { targets: { node: 'current' } }], 4 | '@babel/preset-typescript'], 5 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./out", 4 | "rootDir": "./src", 5 | "sourceMap": true, 6 | "moduleResolution": "node", 7 | "target": "es5" 8 | } 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyroam - Python notebooks in Roam Research 2 | 3 | Documentation: http://adamkrivka.com/roam-plugins/pyroam 4 | 5 | ## Contributing 6 | 7 | You can create issues or pull requests in this repository or DM me on Twitter (https://twitter.com/adam_krivka) 8 | 9 | If you want to develop this plugin, you need to clone this repository, run `npm install` or `yarn install` and then `npm run dev` `yarn run dev`. The final script will be hosted at `localhost:1234/pyroam.js` (or something else, the bundler will tell you), so you should replace the `src` of the script in Roam with that (it is `adamkrivka.com/roam-plugins/pyroam/pyroam.js` by default). Unfortunately, you need to refresh Roam each time you make a change (there are keyboard listeners which get added on each other). -------------------------------------------------------------------------------- /src/keyboard.ts: -------------------------------------------------------------------------------- 1 | import {runActiveBlockAndWriteToNext, runActiveNotebook} from "./notebook" 2 | import {getActiveBlockUid} from "./helpers" 3 | import {createNextPythonBlock} from "./api" 4 | 5 | export const handleKeyPress = async (e: KeyboardEvent) => { 6 | if (e.code === "Enter" && e.altKey === true && !e.shiftKey) { 7 | await runActiveBlockAndWriteToNext() 8 | } else if (e.code === "Enter" && e.metaKey === true ) { 9 | await runActiveBlockAndWriteToNext() 10 | await createNextPythonBlock(getActiveBlockUid()) 11 | e.stopPropagation() 12 | } else if (e.code === "Enter" && e.altKey === true && e.shiftKey === true) { 13 | runActiveNotebook(); 14 | } else if (e.key === "-" && e.ctrlKey === true && e.metaKey === true) { 15 | console.log("pyroam: Removing key listener") 16 | document.removeEventListener("keydown", handleKeyPress) 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pyroam", 3 | "version": "0.0.0", 4 | "description": "Python notebooks in Roam", 5 | "main": "src/index.ts", 6 | "repository": "https://github.com/aidam38/pyroam.git", 7 | "author": "Adam Krivka ", 8 | "license": "MIT", 9 | "scripts": { 10 | "dev": "parcel src/index.ts -o build/pyroam.js", 11 | "build": "parcel build src/index.ts -o build/pyroam.js", 12 | "test": "jest --coverage" 13 | }, 14 | "dependencies": { 15 | "@alex.garcia/unofficial-observablehq-compiler": "^0.6.0-alpha.9", 16 | "@observablehq/runtime": "^4.12.0", 17 | "roam-client": "^1.76.3" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.13.8", 21 | "@babel/preset-env": "^7.13.9", 22 | "@babel/preset-typescript": "^7.13.0", 23 | "@types/jest": "^26.0.20", 24 | "babel-jest": "^26.6.3", 25 | "jest": "^26.6.3", 26 | "parcel-bundler": "^1.12.4", 27 | "typescript": "^4.2.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/junkyard.txt: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | const writeToNextBlock = async (uid, string) => { 3 | if (!string) return; 4 | const query = ` 5 | [:find 6 | (pull ?block 7 | [:block/order 8 | {:block/_children 9 | [:block/uid 10 | {:block/children [:block/uid :block/order]}]}]) 11 | :where 12 | [?block :block/uid "${uid}"]]`; 13 | 14 | const result = await q(query); 15 | if (!result) console.log("Couldn't find the block."); 16 | 17 | var block = result[0][0]; 18 | var parent = block._children[0]; 19 | var siblings = parent.children; 20 | 21 | if (!siblings.some((sibling) => sibling.order > block.order)) { 22 | const newUid = createUid(); 23 | createBlock(parent.uid, block.order + 1, newUid, string); 24 | } else { 25 | const nextSibling = siblings.filter( 26 | (sibling) => sibling.order === block.order + 1 27 | )[0]; 28 | updateBlock(nextSibling.uid, string); 29 | } 30 | } -------------------------------------------------------------------------------- /src/pyodide.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import { sleep, addScriptToPage } from "./helpers"; 3 | 4 | export const loadPyodide = async () => { 5 | addScriptToPage( 6 | "pyodide", 7 | "https://cdn.jsdelivr.net/pyodide/v0.16.1/full/pyodide.js" 8 | ); 9 | 10 | await sleep(2000); 11 | 12 | await languagePluginLoader; 13 | 14 | console.log("pyodide successfully loaded."); 15 | 16 | // setup pyodide environment to run code blocks as needed 17 | const setup_code = ` 18 | import sys, io, traceback 19 | namespace = {} # use separate namespace to hide run_code, modules, etc. 20 | def run_code(code): 21 | """run specified code and return stdout and stderr""" 22 | out = io.StringIO() 23 | oldout = sys.stdout 24 | olderr = sys.stderr 25 | sys.stdout = sys.stderr = out 26 | try: 27 | # change next line to exec(code, {}) if you want to clear vars each time 28 | exec(code, namespace) 29 | except: 30 | traceback.print_exc() 31 | 32 | sys.stdout = oldout 33 | sys.stderr = olderr 34 | return out.getvalue() 35 | `; 36 | pyodide.runPython(setup_code); 37 | 38 | await pyodide.loadPackage([ 39 | "numpy", 40 | "matplotlib", 41 | "scipy", 42 | ]); 43 | console.log("Loaded packages: "); 44 | console.log(pyodide.loadedPackages); 45 | }; 46 | 47 | export const runPython = async (code) => { 48 | if (!code) return; 49 | pyodide.globals.code_to_run = code; 50 | var out = await pyodide.runPythonAsync("run_code(code_to_run)"); 51 | return out; 52 | }; 53 | -------------------------------------------------------------------------------- /src/notebook.ts: -------------------------------------------------------------------------------- 1 | import {getActiveBlockUid, getActiveCodeBlockContent as getActiveCodeBlockContent} from "./helpers" 2 | import {getAllCodeBlocksNestedUnder, getUidOfClosestBlockReferencing, writeToNestedBlock} from "./api" 3 | import {runPython} from "./pyodide" 4 | import {CellRunner} from "./observable" 5 | 6 | 7 | const runAllBlocksBelowUidAndWrite = async (notebookUid) => { 8 | const cells = await getAllCodeBlocksNestedUnder(notebookUid) 9 | const activeUid = getActiveBlockUid() 10 | 11 | for (const cell of cells) { 12 | if (cell.uid === activeUid) cell.string = getActiveCodeBlockContent() 13 | 14 | const out = await runPython(cell.string) 15 | await writeToNestedBlock(cell.uid, out) 16 | } 17 | } 18 | 19 | const cellRunner = new CellRunner() 20 | /** 21 | * Runs only the active cell 22 | */ 23 | export const runActiveBlockAndWriteToNext = async () => { 24 | const activeUid = getActiveBlockUid() 25 | const code = getActiveCodeBlockContent() 26 | const out = await cellRunner.run(code, activeUid, (out: string) => { 27 | console.log("trying to write a child of ", activeUid, out) 28 | return writeToNestedBlock(activeUid, out) 29 | }) 30 | // await writeToNestedBlock(activeUid, out) 31 | } 32 | 33 | /** 34 | * Runs the whole notebook 35 | */ 36 | export const runActiveNotebook = async () => { 37 | const uid = getActiveBlockUid() 38 | const notebookUid = await getUidOfClosestBlockReferencing(uid, "pyroam/notebook") 39 | console.log("Notebook Block uid:" + notebookUid) 40 | runAllBlocksBelowUidAndWrite(notebookUid) 41 | } 42 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import {processCodeBlocks} from "./helpers" 2 | import {createBlock, getOrderByBlockUid, getParentUidByBlockUid} from "roam-client" 3 | 4 | /* === WRAPPERS === */ 5 | export const q = async (query) => { 6 | const result = await window.roamAlphaAPI.q(query); 7 | if (!result || result.length === 0) { 8 | return null; 9 | } else return result; 10 | }; 11 | 12 | export const updateBlock = async (uid, string) => { 13 | await window.roamAlphaAPI.updateBlock({ 14 | block: { 15 | uid: uid, 16 | string: string, 17 | }, 18 | }); 19 | }; 20 | 21 | /* === COMPOSITE FUNCTIONS === */ 22 | export const getBlockStringByUid = async (uid) => { 23 | const query = `[:find (pull ?block [:block/string]) 24 | :where [?block :block/uid "${uid}"]]`; 25 | const result = await q(query); 26 | 27 | return result[0][0].string; 28 | } 29 | export const writeToNestedBlock = async (parentUid, string) => { 30 | if (!string) return; 31 | 32 | const query = ` 33 | [:find 34 | (pull ?nestedBlock 35 | [:block/uid]) 36 | :where 37 | [?parentBlock :block/uid "${parentUid}"] 38 | [?parentBlock :block/children ?nestedBlock]]`; 39 | 40 | const result = await q(query); 41 | if (!result) { 42 | createBlock({ 43 | node: {text: string}, 44 | parentUid, 45 | order: 0 46 | }) 47 | } else { 48 | const nestedUid = result[0][0].uid; 49 | return updateBlock(nestedUid, string); 50 | } 51 | }; 52 | 53 | export const getUidOfClosestBlockReferencing = async (uid: string, page: string) => { 54 | //@ts-ignore 55 | const results = await q(`[:find 56 | (pull ?notebookBlock [:block/uid]) 57 | :where [?notebookBlock :block/refs ?pyroamNotebook] 58 | [?pyroamNotebook :node/title "${page}"] 59 | [?activeBlock :block/parents ?notebookBlock] 60 | [?activeBlock :block/uid "${uid}"]]`); 61 | //@ts-ignore 62 | if (results) return results[0][0].uid; 63 | else { 64 | const page = await q(`[:find 65 | (pull ?page [:block/uid]) 66 | :where [?page :node/title] 67 | [?activeBlock :block/uid "${uid}"] 68 | [?activeBlock :block/parents ?page]]`) 69 | 70 | return page[0][0].uid; 71 | } 72 | }; 73 | 74 | export const getAllCodeBlocksNestedUnder = async (topUid) => { 75 | const results = await window.roamAlphaAPI.q('[:find\ 76 | (pull ?cell [:block/string :block/order :block/uid {:block/_children ...}])\ 77 | :where [?notebookBlock :block/uid "' + topUid + '"]\ 78 | [?cell :block/parents ?notebookBlock]\ 79 | [?cell :block/string ?string]\ 80 | [(clojure.string/starts-with? ?string "```python")]]') 81 | return processCodeBlocks(results, topUid); 82 | }; 83 | 84 | export async function createNextPythonBlock(activeUid: string) { 85 | // todo generate hiccup for displaying complex things? 86 | // todo interactive output 87 | // todo proper click on the new code block 88 | 89 | const order = getOrderByBlockUid(activeUid) + 1 90 | const newUuid = createBlock({ 91 | node: { 92 | text: "```python\n```", 93 | }, 94 | parentUid: getParentUidByBlockUid(activeUid), 95 | order, 96 | }) 97 | 98 | console.log("Created new python block:", newUuid) 99 | } 100 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | exports.invertTree = exports.processRawHtml = exports.getCodeBlockContent = exports.getActiveBlockUid = exports.createUid = exports.addScriptToPage = exports.addElementToPage = exports.sleep = void 0; 4 | var sleep = function (m) { 5 | var t = m ? m : 10; 6 | return new Promise(function (r) { return setTimeout(r, t); }); 7 | }; 8 | exports.sleep = sleep; 9 | var addElementToPage = function (element, tagId, typeT) { 10 | try { 11 | document.getElementById(tagId).remove(); 12 | } 13 | catch (e) { } 14 | Object.assign(element, { type: typeT, async: false, tagId: tagId }); 15 | document.getElementsByTagName("head")[0].appendChild(element); 16 | }; 17 | exports.addElementToPage = addElementToPage; 18 | var addScriptToPage = function (tagId, script) { 19 | exports.addElementToPage(Object.assign(document.createElement("script"), { src: script }), tagId, "text/javascript"); 20 | }; 21 | exports.addScriptToPage = addScriptToPage; 22 | var createUid = function () { 23 | // From roam42 based on https://github.com/ai/nanoid#js version 3.1.2 24 | var nanoid = function (t) { 25 | if (t === void 0) { t = 21; } 26 | var e = "", r = crypto.getRandomValues(new Uint8Array(t)); 27 | for (; t--;) { 28 | var n = 63 & r[t]; 29 | e += 30 | n < 36 31 | ? n.toString(36) 32 | : n < 62 33 | ? (n - 26).toString(36).toUpperCase() 34 | : n < 63 35 | ? "_" 36 | : "-"; 37 | } 38 | return e; 39 | }; 40 | return nanoid(9); 41 | }; 42 | exports.createUid = createUid; 43 | var getActiveBlockUid = function () { 44 | var el = document.activeElement; 45 | var uid = el.closest(".rm-block__input").id.slice(-9); 46 | return uid; 47 | }; 48 | exports.getActiveBlockUid = getActiveBlockUid; 49 | var getCodeBlockContent = function (el) { 50 | var rawHtml = el.parentElement.parentElement.querySelector(".CodeMirror-code").outerHTML; 51 | return exports.processRawHtml(rawHtml); 52 | }; 53 | exports.getCodeBlockContent = getCodeBlockContent; 54 | var processRawHtml = function (rawHtml) { 55 | var withNewLines = rawHtml 56 | .replace(/
() 27 | readonly observers = new Map() 28 | 29 | constructor() { 30 | } 31 | 32 | async run(code: string, id: string, writeResult: (out: string) => Promise) { 33 | this.clearVars(id) 34 | // const parent = document.querySelector(".roam-article > div:first-of-type") 35 | // root = root || createRoot(parent) 36 | 37 | // root = root || createRoot(parent) 38 | 39 | this.observers.set(id, this.observers.get(id) || this.getObserver(id, writeResult)) 40 | const observer = this.observers.get(id) 41 | //todo save observer? 42 | const interpret = new Interpreter({module: this.mainModule, observer}) 43 | 44 | this.varsForCells.set(id, await interpret.module(code)) 45 | // return await interpret.cell(code) 46 | 47 | } 48 | 49 | private clearVars(id: string) { 50 | console.log("Cleaning up vars for block ", id) 51 | //otherwise we get "x is redefined errors" 52 | //from https://observablehq.com/d/63585ffc01d6a1af 53 | this.varsForCells.get(id)?.forEach(vars => { 54 | for (const v of vars) { 55 | v.delete() 56 | if (v._observer._node) { 57 | v._observer._node.remove() 58 | } 59 | } 60 | }) 61 | //this seems to break the interactive viewof html element though 🤔 62 | } 63 | 64 | getObserver(id: string, writeResult: (out: string) => Promise) { 65 | const parent = document.activeElement 66 | .parentElement.parentElement.parentElement.parentElement.parentElement.parentElement 67 | //todo search up for parent instead 68 | // const parent = document.activeElement.parentElement 69 | 70 | const displayElement = createRoot(parent) 71 | const delegateGenerator = Inspector.into(displayElement) 72 | return (genInp) => { 73 | console.log("creating observer for", genInp) 74 | const delegate = delegateGenerator() 75 | return ({ 76 | pending() { 77 | console.log("pending") 78 | delegate.pending() 79 | }, 80 | rejected(e) { 81 | console.log("rejected", e) 82 | delegate.rejected(e) 83 | }, 84 | fulfilled(value: any, name: any) { 85 | console.log(value, name) 86 | delegate.fulfilled(value, name) 87 | 88 | //todo run html -> hiccup when relevant 89 | writeResult(JSON.stringify(value)) 90 | }, 91 | }) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | /* ====== BASIC ======= */ 2 | 3 | export const sleep = (m) => { 4 | var t = m ? m : 10; 5 | return new Promise((r) => setTimeout(r, t)); 6 | }; 7 | 8 | export const addElementToPage = (element, tagId, typeT) => { 9 | try { 10 | document.getElementById(tagId).remove(); 11 | } catch (e) { } 12 | Object.assign(element, { type: typeT, async: false, tagId: tagId }); 13 | document.getElementsByTagName("head")[0].appendChild(element); 14 | }; 15 | 16 | export const addScriptToPage = (tagId, script) => { 17 | addElementToPage( 18 | Object.assign(document.createElement("script"), { src: script }), 19 | tagId, 20 | "text/javascript" 21 | ); 22 | }; 23 | 24 | export function getActiveRoamInputElement() { 25 | const el = document.activeElement 26 | return el.closest(".rm-block__input") 27 | } 28 | 29 | export const getActiveBlockUid = () => { 30 | const roamInput = getActiveRoamInputElement() 31 | return roamInput.id.slice(-9) 32 | } 33 | 34 | export const removeBackticks = (str: string) => { 35 | var ttt = "``" + "`"; 36 | return str.replace(ttt + "python", "").replace(ttt, ""); 37 | } 38 | 39 | /* ======== CODEBLOCK PROCESSING ======= */ 40 | export const processRawHtml = (rawHtml: string) => { 41 | const withNewLines = rawHtml 42 | .replace(/
{ 49 | const el = document.activeElement; 50 | const rawHtml = el.parentElement.parentElement.querySelector(".CodeMirror-code").outerHTML; 51 | return processRawHtml(rawHtml); 52 | } 53 | 54 | /* ====== TREE PARSERS ====== */ 55 | const invertTree = (block, topUid) => { 56 | var attachParent = (oldTopBlock) => { 57 | var newTopBlock = oldTopBlock._children[0]; 58 | newTopBlock.child = oldTopBlock; 59 | if (newTopBlock.uid === topUid) return newTopBlock; 60 | return attachParent(newTopBlock) 61 | } 62 | var invertedTree = attachParent(block); 63 | 64 | return invertedTree; 65 | } 66 | 67 | const mergeTreesByUid = (topBlocks) => { 68 | var latchOnto = (tree, topBlock) => { 69 | var order = parseInt(topBlock.order) || 0; 70 | if (!tree[order]) { 71 | tree[order] = topBlock; 72 | if (topBlock.child) { 73 | tree[order].children = [] 74 | tree[order].children[topBlock.child.order] = topBlock.child 75 | } 76 | } if (topBlock.child && topBlock.uid === tree[order].uid) { 77 | tree[order].children = latchOnto(tree[order].children || [], topBlock.child) 78 | } 79 | 80 | return tree; 81 | } 82 | 83 | var finalTree = []; 84 | topBlocks.forEach(topBlock => { 85 | finalTree = latchOnto(finalTree, topBlock) 86 | }); 87 | return finalTree; 88 | } 89 | 90 | const flatten = (tree) => { 91 | var finalArray = [] 92 | var stepIn = (node) => { 93 | finalArray.push(node) 94 | if (node.children && node.children.length !== 0) 95 | node.children.forEach(child => { 96 | stepIn(child); 97 | }); 98 | } 99 | stepIn(tree); 100 | return finalArray; 101 | } 102 | 103 | export const processCodeBlocks = (codeblocks, uid) => { 104 | var trees = codeblocks.map(codeblock => invertTree(codeblock[0], uid)); 105 | var tree = mergeTreesByUid(trees); 106 | console.log(tree) 107 | var cells = flatten(tree.filter(tr => tr)[0]); 108 | cells = cells.filter(cell => cell.string && cell.string.startsWith("```python")) 109 | .map(cell => { 110 | return { 111 | uid: cell.uid, 112 | string: removeBackticks(cell.string) 113 | } 114 | }) 115 | return cells; 116 | } 117 | -------------------------------------------------------------------------------- /docs/pyroam.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Pyroam - Python notebooks in Roam Research" 3 | --- 4 | 5 | ![pyroam gif](/roam-plugins/assets/pyroam.gif) 6 | 7 | ## Installation 8 | 9 | To install this plugin, **click** on the "*Copy extension*." button below, **paste** somewhere in your graph and **click** the "*Yes, I know what I'm doing.*" button. 10 | 11 |
12 | 13 |
14 | 15 | 16 | (*Warning*: The above method might not work in not Chrome-based browsers, in which case you should follow the manual installation below.) 17 | 18 | ### Manual installation 19 | 20 | **Copy** the following snippet and **paste** somewhere in your graph. 21 | 22 | ```js 23 | - {{[[roam/js]]}} 24 | - `````` 25 | ``` 26 | 27 | It should create a code block. Into the code block, paste the following code: 28 | 29 | ```js 30 | /** pyroam - Python notebooks in Roam 31 | * Author: Adam Krivka 32 | * Docs: https://adamkrivka.com/roam-plugins/pyroam 33 | */ 34 | 35 | window.pyroamSettings = { 36 | /** There might some important settings here in the future, 37 | * make sure to check the docs once in a while. 38 | */ 39 | } 40 | 41 | var pyroamID = "pyroam-script"; 42 | var oldPyroam = document.getElementById(pyroamID); 43 | if (oldPyroam) oldPyroam.remove(); 44 | var pyroam = document.createElement('script'); 45 | pyroam.type = "text/javascript"; 46 | pyroam.id = pyroamID; 47 | pyroam.src = "https://adamkrivka.com/roam-plugins/pyroam/pyroam.js"; 48 | pyroam.async = true; 49 | document.getElementsByTagName('head')[0].appendChild(pyroam); 50 | ``` 51 | 52 | ## Usage 53 | 54 | The main thing you need to know are the two following keybindings: 55 | 56 | | Keybinding | Action | 57 | | ------------------ | ---------------- | 58 | | | Run current cell and write | 59 | | | Run all cells in *active notebook* and write | 60 | 61 | When a codeblock is run, the output is written to the block nested one below it. 62 | 63 | The fastest way to add a Python codeblock in Roam is by typing `/python` and pressing Enter. 64 | 65 | ### Active notebooks 66 | 67 | By default, the current page is the *active notebook*, i.e., when you press , all blocks on the current page get run and their outputs are written. 68 | 69 | You can designate a smaller portion of the page to be the *active notebook* by referencing the page `[[pyroam/notebook]]` in the parent block, i.e. all blocks nested under this blocks will get run when pressing , but no other. You can also nest notebooks - the plugin will always run the "smallest" one. 70 | 71 | All variables are shared, it's like if the code blocks were one script. 72 | 73 | ### Packages 74 | 75 | Currently, `numpy`, `matplotlib` and `scipy` get loaded, though `numpy` tends to be buggy and plot generation with `matplotlib` doesn't work yet. 76 | 77 | In the future, the plugin will load all imported packages dynamically (as long as `pyodide`, the underlying Python browser compiler, allows it). 78 | 79 | 80 | ## Bug reports & Contributing 81 | You can create issues or pull requests in the [repository](https://github.com/aidam38/pyroam) or DM me on Twitter (https://twitter.com/adam_krivka) 82 | -------------------------------------------------------------------------------- /dist/pyroam.js: -------------------------------------------------------------------------------- 1 | parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c0&&r[r.length-1])&&(6===s[0]||2===s[0])){a=0;continue}if(3===s[0]&&(!r||s[1]>r[0]&&s[1]0&&r[r.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!r||i[1]>r[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){c=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){u=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1] { 4 | // Raw Input 5 | //#region 6 | const testRawHtml = `` 7 | //#endregion 8 | 9 | // Output 10 | //#region 11 | const testOutput = `import math 12 | import random as rnd 13 | 14 | n = 100 15 | counter = 0 16 | 17 | for i in range(n): 18 | # Sample the length of the fragment between 1 and 10 19 | d = rnd.random()*9+1 20 | 21 | # Pick a random declination 22 | alpha = rnd.random()*math.pi/2 23 | 24 | # Calculate the effective radius of the fragment 25 | r_eff = d*math.sin(alpha)/2 26 | 27 | # Pick a random right ascension 28 | beta = rnd.random()*2*math.pi 29 | 30 | # Pick a random center of the fragment within the rectangular chain link 2x2 cm 31 | P = (rnd.random()*2, rnd.random()*2) 32 | 33 | # Calculate the position of the farEnd of the fragment 34 | farEnd = (P[0]+r_eff*math.cos(beta), P[1]+r_eff*math.sin(beta)) 35 | 36 | # If the far end is withn the chain link, add 1 to a counter 37 | if farEnd[0] < 2 and farEnd[0] > 0 and farEnd[1] < 2 and farEnd[1] > 0: 38 | counter += 1 39 | 40 | # Print the final number of fragments that go through 41 | print(counter)`.replace(/\u200B/g, "") 42 | 43 | //#endregion 44 | expect(processRawHtml(testRawHtml)).toMatch(testOutput) 45 | }) 46 | 47 | /* test("proper tree parsing", () => { 48 | var testTree1NotInverted = { 49 | uid: "C", 50 | order: 0, 51 | _children: [{ 52 | uid: "B", 53 | order: 0, 54 | _children: [{ 55 | uid: "A", 56 | order: 3, 57 | _children: ["uglamugla"] 58 | }] 59 | }] 60 | }; 61 | 62 | var testTree1Inverted = { 63 | uid: "A", 64 | order: 3, 65 | _children: ["uglamugla"], 66 | children: [{ 67 | uid: "B", 68 | order: 0, 69 | _children: [{ 70 | uid: "A", 71 | order: 3, 72 | _children: ["uglamugla"] 73 | }], 74 | children: [{ 75 | uid: "C", 76 | order: 0, 77 | _children: [{ 78 | uid: "B", 79 | order: 0, 80 | _children: [{ 81 | uid: "A", 82 | order: 3, 83 | _children: ["uglamugla"] 84 | }] 85 | }] 86 | }] 87 | }] 88 | } 89 | console.log(invertTree(testTree1NotInverted, "A")) 90 | expect(invertTree(testTree1NotInverted, "A")).toEqual(testTree1Inverted) 91 | 92 | var testTree2 = { 93 | uid: "A", 94 | order: 3, 95 | children: [{ 96 | uid: "D", 97 | order: 1 98 | }] 99 | } 100 | 101 | var testTree = { 102 | uid: "A", 103 | order: 3, 104 | _children: ["uglamugla"], 105 | children: [{ 106 | uid: "B", 107 | order: 0, 108 | _children: [{ 109 | uid: "A", 110 | order: 3, 111 | _children: ["uglamugla"] 112 | }], 113 | children: [{ 114 | uid: "C", 115 | order: 0, 116 | _children: [{ 117 | uid: "B", 118 | order: 0, 119 | _children: [{ 120 | uid: "A", 121 | order: 3, 122 | _children: ["uglamugla"] 123 | }] 124 | }] 125 | }] 126 | }, 127 | { 128 | uid: "D", 129 | order: 1 130 | }] 131 | } 132 | expect(mergeTreesByUid([testTree1Inverted, testTree2])).toEqual(testTree) 133 | }); */ -------------------------------------------------------------------------------- /dist/pyroam.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["helpers.ts","pyodide.ts","api.ts","notebook.ts","keyboard.ts","index.ts"],"names":[],"mappings":"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","file":"pyroam.js","sourceRoot":"../src","sourcesContent":["/* ====== BASIC ======= */\nexport const sleep = (m) => {\n var t = m ? m : 10;\n return new Promise((r) => setTimeout(r, t));\n};\n\nexport const addElementToPage = (element, tagId, typeT) => {\n try {\n document.getElementById(tagId).remove();\n } catch (e) { }\n Object.assign(element, { type: typeT, async: false, tagId: tagId });\n document.getElementsByTagName(\"head\")[0].appendChild(element);\n};\n\nexport const addScriptToPage = (tagId, script) => {\n addElementToPage(\n Object.assign(document.createElement(\"script\"), { src: script }),\n tagId,\n \"text/javascript\"\n );\n};\n\nexport const createUid = () => {\n // From roam42 based on https://github.com/ai/nanoid#js version 3.1.2\n let nanoid = (t = 21) => {\n let e = \"\",\n r = crypto.getRandomValues(new Uint8Array(t));\n for (; t--;) {\n let n = 63 & r[t];\n e +=\n n < 36\n ? n.toString(36)\n : n < 62\n ? (n - 26).toString(36).toUpperCase()\n : n < 63\n ? \"_\"\n : \"-\";\n }\n return e;\n };\n return nanoid(9);\n};\n\nexport const getActiveBlockUid = () => {\n const el = document.activeElement;\n const uid = el.closest(\".rm-block__input\").id.slice(-9);\n return uid;\n};\n\nexport const removeBackticks = (str: string) => {\n var ttt = \"``\" + \"`\";\n return str.replace(ttt + \"python\", \"\").replace(ttt, \"\");\n}\n\n/* ======== CODEBLOCK PROCESSING ======= */\nexport const processRawHtml = (rawHtml: string) => {\n const withNewLines = rawHtml\n .replace(/
/gm, \"\\n\")\n\n const output = new DOMParser().parseFromString(withNewLines, \"text/html\").documentElement.textContent.replace(/\\u200B/g, \"\");\n return output.slice(1);\n}\n\nexport const getActiveCodeBlockContent = () => {\n const el = document.activeElement;\n const rawHtml = el.parentElement.parentElement.querySelector(\".CodeMirror-code\").outerHTML;\n return processRawHtml(rawHtml);\n}\n\n/* ====== TREE PARSERS ====== */\nconst invertTree = (block, topUid) => {\n var attachParent = (oldTopBlock) => {\n var newTopBlock = oldTopBlock._children[0];\n newTopBlock.child = oldTopBlock;\n if (newTopBlock.uid === topUid) return newTopBlock;\n return attachParent(newTopBlock)\n }\n var invertedTree = attachParent(block);\n\n return invertedTree;\n}\n\nconst mergeTreesByUid = (topBlocks) => {\n var latchOnto = (tree, topBlock) => {\n var order = parseInt(topBlock.order) || 0;\n if (!tree[order]) {\n tree[order] = topBlock;\n if (topBlock.child) {\n tree[order].children = []\n tree[order].children[topBlock.child.order] = topBlock.child\n }\n } if (topBlock.child && topBlock.uid === tree[order].uid) {\n tree[order].children = latchOnto(tree[order].children || [], topBlock.child)\n }\n\n return tree;\n }\n\n var finalTree = [];\n topBlocks.forEach(topBlock => {\n finalTree = latchOnto(finalTree, topBlock)\n });\n return finalTree;\n}\n\nconst flatten = (tree) => {\n var finalArray = []\n var stepIn = (node) => {\n finalArray.push(node)\n if (node.children && node.children.length !== 0)\n node.children.forEach(child => {\n stepIn(child);\n });\n }\n stepIn(tree);\n return finalArray;\n}\n\nexport const processCodeBlocks = (codeblocks, uid) => {\n var trees = codeblocks.map(codeblock => invertTree(codeblock[0], uid));\n var tree = mergeTreesByUid(trees);\n console.log(tree)\n var cells = flatten(tree.filter(tr => tr)[0]);\n cells = cells.filter(cell => cell.string && cell.string.startsWith(\"```python\"))\n .map(cell => {\n return {\n uid: cell.uid,\n string: removeBackticks(cell.string)\n }\n })\n return cells;\n}","//@ts-nocheck\nimport { sleep, addScriptToPage } from \"./helpers\";\n\nexport const loadPyodide = async () => {\n addScriptToPage(\n \"pyodide\",\n \"https://cdn.jsdelivr.net/pyodide/v0.16.1/full/pyodide.js\"\n );\n\n await sleep(2000);\n\n await languagePluginLoader;\n\n console.log(\"pyodide successfully loaded.\");\n\n // setup pyodide environment to run code blocks as needed\n const setup_code = `\nimport sys, io, traceback\nnamespace = {} # use separate namespace to hide run_code, modules, etc.\ndef run_code(code):\n \"\"\"run specified code and return stdout and stderr\"\"\"\n out = io.StringIO()\n oldout = sys.stdout\n olderr = sys.stderr\n sys.stdout = sys.stderr = out\n try:\n # change next line to exec(code, {}) if you want to clear vars each time\n exec(code, namespace)\n except:\n traceback.print_exc()\n\n sys.stdout = oldout\n sys.stderr = olderr\n return out.getvalue()\n`;\n pyodide.runPython(setup_code);\n\n await pyodide.loadPackage([\n \"numpy\",\n \"matplotlib\",\n \"scipy\",\n ]);\n console.log(\"Loaded packages: \");\n console.log(pyodide.loadedPackages);\n};\n\nexport const runPython = async (code) => {\n if (!code) return;\n pyodide.globals.code_to_run = code;\n var out = await pyodide.runPythonAsync(\"run_code(code_to_run)\");\n return out;\n};\n","import { createUid, processCodeBlocks } from \"./helpers\";\n\n/* === WRAPPERS === */\nexport const q = async (query) => {\n //@ts-ignore\n const result = await window.roamAlphaAPI.q(query);\n if (!result || result.length === 0) {\n return null;\n } else return result;\n};\n\nexport const updateBlock = async (uid, string) => {\n //@ts-ignore\n await window.roamAlphaAPI.updateBlock({\n block: {\n uid: uid,\n string: string,\n },\n });\n};\n\nexport const createBlock = async (parentUid, order, uid, string) => {\n //@ts-ignore\n await window.roamAlphaAPI.createBlock({\n location: {\n \"parent-uid\": parentUid,\n order: order,\n },\n block: {\n uid: uid,\n string: string,\n },\n });\n};\n\n/* === COMPOSITE FUNCTIONS === */\nexport const getBlockStringByUid = async (uid) => {\n const query = `[:find (pull ?block [:block/string])\n :where [?block :block/uid \"${uid}\"]]`;\n const result = await q(query);\n\n return result[0][0].string;\n}\nexport const writeToNestedBlock = async (parentUid, string) => {\n if (!string) return;\n\n const query = `\n [:find \n (pull ?nestedBlock \n [:block/uid])\n :where\n [?parentBlock :block/uid \"${parentUid}\"]\n [?parentBlock :block/children ?nestedBlock]]`;\n\n const result = await q(query);\n if (!result) {\n const nestedUid = createUid();\n createBlock(parentUid, 0, nestedUid, string);\n } else {\n const nestedUid = result[0][0].uid;\n updateBlock(nestedUid, string);\n }\n};\n\nexport const getUidOfClosestBlockReferencing = async (uid: string, page: string) => {\n //@ts-ignore\n const results = await q(`[:find \n (pull ?notebookBlock [:block/uid]) \n :where [?notebookBlock :block/refs ?pyroamNotebook]\n [?pyroamNotebook :node/title \"${page}\"]\n [?activeBlock :block/parents ?notebookBlock]\n [?activeBlock :block/uid \"${uid}\"]]`);\n //@ts-ignore\n if (results) return results[0][0].uid;\n else {\n const page = await q(`[:find \n (pull ?page [:block/uid])\n :where [?page :node/title]\n [?activeBlock :block/uid \"${uid}\"] \n [?activeBlock :block/parents ?page]]`)\n\n return page[0][0].uid;\n }\n};\n\nexport const getAllCodeBlocksNestedUnder = async (topUid) => {\n //@ts-ignore\n var results = await window.roamAlphaAPI.q('[:find\\\n (pull ?cell [:block/string :block/order :block/uid {:block/_children ...}])\\\n :where [?notebookBlock :block/uid \"' + topUid + '\"]\\\n [?cell :block/parents ?notebookBlock]\\\n [?cell :block/string ?string]\\\n [(clojure.string/starts-with? ?string \"```python\")]]');\n const cells = processCodeBlocks(results, topUid);\n return cells;\n};","import { getActiveBlockUid, getActiveCodeBlockContent as getActiveCodeBlockContent, removeBackticks } from \"./helpers\";\nimport { q, writeToNestedBlock, getUidOfClosestBlockReferencing, getAllCodeBlocksNestedUnder, getBlockStringByUid } from \"./api\";\nimport { runPython } from \"./pyodide\";\n\n\nconst runAllBlocksBelowUidAndWrite = async (notebookUid) => {\n const cells = await getAllCodeBlocksNestedUnder(notebookUid);\n const activeUid = getActiveBlockUid();\n\n for (const cell of cells) {\n if (cell.uid === activeUid) cell.string = getActiveCodeBlockContent()\n\n const out = await runPython(cell.string);\n await writeToNestedBlock(cell.uid, out)\n }\n}\n\n/**\n * Runs only the active cell\n */\nexport const runActiveBlockAndWriteToNext = async () => {\n const activeUid = getActiveBlockUid();\n const code = getActiveCodeBlockContent()\n const out = await runPython(code)\n await writeToNestedBlock(activeUid, out)\n};\n\n/**\n * Runs the whole notebook\n */\nexport const runActiveNotebook = async () => {\n const uid = getActiveBlockUid();\n const notebookUid = await getUidOfClosestBlockReferencing(uid, \"pyroam/notebook\");\n console.log(\"Notebook Block uid:\" + notebookUid)\n runAllBlocksBelowUidAndWrite(notebookUid);\n}","\nimport { runActiveBlockAndWriteToNext, runActiveNotebook } from \"./notebook\";\n\nexport const handleKeyPress = async (e) => {\n if (e.code === \"Enter\" && e.altKey === true && !e.shiftKey) {\n runActiveBlockAndWriteToNext();\n } else if (e.code === \"Enter\" && e.altKey === true && e.shiftKey === true) {\n runActiveNotebook();\n }\n};\n","import { loadPyodide } from \"./pyodide\";\nimport { handleKeyPress } from \"./keyboard\";\n\nconsole.log(\"Loading pyroam.\");\n\nloadPyodide();\n\ndocument.addEventListener(\"keydown\", handleKeyPress);\n"]} --------------------------------------------------------------------------------