Recall Revisions

We recreate prior revisions of a page by replaying the Actions in its Journal. We encode the desired revision into an integer, the count of Actions to replay, and include it as a suffix to page slugs interpreted by the client javascript.

Action clicks are handled in a legacy delegate. We infer the desired revision number by noticing the click target's position within the rendered Journal.

rev = $(this).parent().children() .not('.separator') .index($action)

The rev gets tacked on to the slug here, appended as a new page in the dom, and then rendered by refresh.

link.createPage("#{slug}_rev#{rev}", ...) .appendTo($('.main')) .each refresh.cycle

Try recalling revisions of this page by adding _rev24 to its slug in the browser's location bar.

An alternate path is followed into link.createPage when link.showResult is called. Why add ghost here? github

Refresh separates the slug and rev it finds in the page's id and forwards both to the page handler for retrieval.

[slug, rev] = $page.attr('id').split('_rev')

As part of a successful get, the pageHandler will reconstruct a prior revision of the page if one is called for.

success: (page) -> page = revision.create rev, page if rev

Similar logic applies when the page is retrieved from browser local storage. github

Starting with an empty story, the revision module applies one Action after another until the revision of interest has been reconstructed.

revPage = {title: data.title, story: []} for action in revJournal apply revPage, action||{}

Refresh (again) resumes rendering page elements including the timestamp that is contingent on having requested a specific revision.

emitTimestamp = ($header, $page, pageObject) -> if $page.attr('id').match /_rev/ ...

# Fork

We can revert a page to a previous revision by recalling and forking the desired version from history.

Legacy (again) handles the fork button. One can't normally fork ghost pages so we erase this property before continuing with the fork.

if $page.data('rev')? $page.removeClass('ghost') $page.find('.revision').remove()

PageHandler isolates the rev number but doesn't seem to use it. Fix this? github

PageHandler (again) recognizes that a whole page, as reconstructed, should be included in the action.

if action.type == 'fork' bundle.item = deepCopy pageObject.getRawPage()

The wiki-server can accept fork actions with page content pushed as action.item which is then erased before action is saved. github

if action.item # push itemCopy = JSON.parse(JSON.stringify(action.item)) delete action.item

Note that both the client and the server deep copy the page to preserve functional semantics in the presence of incremental updates elsewhere.

Try adding an image to the welcome page of a site you own. Recall the version before the add and fork it. Compare the action sent to the server with that stored in the revised journal.

# Reflection

Actions implement the command design pattern. Nick Hallahan designed the version browsing logic and pair-programmed it with me.

We once delegated fetching remote content when forked to the server (pull) but found that crossed firewalls the wrong way. The action.item payload (push) is our client-privileged replacement which also enables fork from history.