Category Archives: Roam to WP

First spike for Roam to WP plugin

Status update on getting the first “spike/prototype” of code working.

Scaffolding initial plugin code

I’ve looked into a number of scaffolding solutions:

I’ve initially tried to use WordPress Plugin Boilerplate Powered but it generated so much boilerplate code that it was just too much work to actually write my own code.

So I ended up using wp scaffold that produced a very nice skeleton that I could start writing code into.

Settings Screen

I first needed to build a screen where I could upload my exported data. Final result looks like this for now:

What was really useful in this research was article by Delicious Brains – 5 Ways to Create a WordPress Plugin Settings Page. I ended up using Carbon Fields as I didn’t want to invest too much time into scaffolding my own HTML Form code just to process a few fields.

Processing the data export

For now the general idea is that I only need to look at the first level of blocks inside Roam. So I’ll have structure like this:

{
  "create-time": 1621153344466,
  "title": "!Resources/BASB/Reviews",
..
"children": [
  {
  "string": "Wordpress:: #publish",
  ..
  ":create/user": {
    ":user/uid": "D9GErGIgWMcqL1SJ9egralKACQ62"
  },
  ..
},
..
]
}

and I need to find all blocks that have somewhere in the first level of children a string that matches "Wordpress:: #publish". The problem is that tree searching libraries like jsonq only return the matching child node but not the parent structure.

No problem we just need to write a single node depth search:

<?php 
function extract_nodes_to_publish( array $data ): array {
	$rules = \preg_split( "/\r\n|\n|\r/", carbon_get_theme_option( 'rtw_import_rules' ) );
	if ( ! $rules ) {
		return [];
	}

	$matching_pages = [];
	foreach ( array_splice( $data, 0 ) as $page ) {
		if ( isset( $page['children'] ) ) {
			foreach ( $page['children'] as $block ) {
				if ( in_array( $block['string'], $rules ) ) {
					ray( $block['string'] );
					$matching_pages[] = $page;
				}
			}
		}
	}

	return $matching_pages;
}

Rendering whole content tree

After we have a list of pages that match we need to render whole Roam content page that is in Markdown strings into HTML. Since I’ve been inspired by “Roam Blocks” plugin I’ve decided to reuse their function that does that: https://github.com/artpi/wp-roam-block/blob/main/endpoints.php#L82

First rendered page

With a bit more glue code I can already see how this plugin creates a new Note custom post types and inserts a page that I tagged with #publish:

Next steps

  • Figure out how to grab Title:: attribute if present so I don’t need to expose internal Roam page titles
  • Figure out what should be a bullet and what should be just a paragraph.
  • Publish to Github so maybe some other brave soul could give it a try

Initial thinking on Roam to WordPress Plugin

What I’m trying to do with this plugin is to create a digital garden that I can connect from Roam Research to WordPress.

Most of the existing solutions are using static web site generators. I’d like to avoid that because I want to have only one CMS on my site and to also maintain only one theme. There are also additional potential power features that would allow me toI can connect exported Roam pages to WordPress Taxonomies.

My current thinking is as follows:

When looking at the overall architecture design I realised that:

  • Roam’s JSON API is something that I don’t understand, it’s undocumented and hard to develop. So I’ll pass it for now.
  • Exported MD files are nice but they’re messy to automate.
  • Uploading one exported JSON file allows me keep all application logic in WordPress plugin and removes the need for any additional tools.

I’ve also found some nice prior art. There’s WP Roam Blocks that does something similar within Gutenberg.

I also appreciate blogging from David Bieber where he describes how he’s currently using Python to blog with Roam:

Next steps

  1. REST API to allow uploading of Roam’s JSON export
  2. Initial React backend upload dialog
  3. Basic jsonq parsing of uploaded JSON file