Skip to content

Instantly share code, notes, and snippets.

@kaleguy
Last active March 4, 2017 23:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaleguy/cef095e16e147bc04dd6c5812d732fb2 to your computer and use it in GitHub Desktop.
Save kaleguy/cef095e16e147bc04dd6c5812d732fb2 to your computer and use it in GitHub Desktop.
Leo Viewer
license: MIT
height: 480
border: yes

Leo is, among other things, an open source outlining editor.

This code snippet shows how to a view simple Leo file (one that contains no @file directives, just text nodes).

If the first line in a node has a @language directive (including md or html), the content will be properly displayed.

View on bl.ocks.org

Leo files are XML, the snippet transforms the Leo XML into JSON via XSL and then uses Vue.js create the viewer.

<?xml version="1.0" encoding="utf-8"?>
<!-- Created by Leo: http://leoeditor.com/leo_toc.html -->
<leo_file xmlns:leo="http://leoeditor.com/namespaces/leo-python-editor/1.1" >
<leo_header file_format="2" tnodes="0" max_tnode_index="0" clone_windows="0"/>
<globals body_outline_ratio="0.5" body_secondary_ratio="0.5">
<global_window_position top="50" left="50" height="500" width="700"/>
<global_log_window_position top="0" left="0" height="0" width="0"/>
</globals>
<preferences/>
<find_panel_settings/>
<vnodes>
<v t="josephorr.20170228222411.2" a="E"><vh>Top</vh>
<v t="josephorr.20170304173237.1" a="E"><vh>HTML</vh>
<v t="josephorr.20170304174041.1"><vh>Text and Photo</vh></v>
</v>
<v t="josephorr.20170304174421.1" a="E"><vh>Text</vh>
<v t="josephorr.20170304174436.1"><vh>A Note About Something</vh></v>
</v>
<v t="josephorr.20170304113011.1" a="E"><vh>Code</vh>
<v t="josephorr.20170304113024.1"><vh>Javascript</vh></v>
<v t="josephorr.20170304115429.1"><vh>Coffeescript</vh></v>
</v>
<v t="josephorr.20170304103722.1" a="E"><vh>Markdown</vh>
<v t="josephorr.20170304175209.1"><vh>Markdown In Leo</vh></v>
<v t="josephorr.20170304103726.1"><vh>A Markdown Section</vh></v>
</v>
</v>
</vnodes>
<tnodes>
<t tx="josephorr.20170228222411.2">This is the top.</t>
<t tx="josephorr.20170304103722.1"></t>
<t tx="josephorr.20170304103726.1">@language md
# A topic
This is fascinating.
## Another Topic
This one is less so.
### Fine Print
We would rather you not read this section.</t>
<t tx="josephorr.20170304113011.1"></t>
<t tx="josephorr.20170304113024.1">@language javascript
function getLanguage(text){
var language = '';
var re = /^@language (\w+)/;
var languageTokens = re.exec(text);
if (languageTokens){
language = languageTokens[1];
console.log(language);
}
return language;
}</t>
<t tx="josephorr.20170304115429.1">@language coffeescript
Client = require 'ftp'
async = require 'async'
remote_path ='/public_html/targetfolder/'
local_path = 'data/'
fs = require 'fs'
c = new Client()
c.on 'ready', ()-&gt;
c.list remote_path, (err, list)-&gt;
if (err) then console.error 'list', err
file_funcs = (getFileFunc file.name for file in list)
async.series file_funcs, ()-&gt; c.end()
getFileFunc = (name) -&gt;
(callback)-&gt;
if /csv$/.test name
c.get remote_path + name, (err, stream)-&gt;
console.log 'Getting ', name
if err then console.log 'get', err
stream.once 'close', callback
stream.pipe fs.createWriteStream local_path + name
else
console.log "Skipping", name
callback()
c.connect(
host : 'ftp.targethost.com'
user : 'username@domain.com'
password : 'password'
)</t>
<t tx="josephorr.20170304173237.1">This branch has an HTML leaf in it.</t>
<t tx="josephorr.20170304174041.1">@language html
&lt;h1&gt;Bee And Flower&lt;/h1&gt;
An example of HTML content.
&lt;div&gt;
&lt;p&gt;
&lt;img src='https://upload.wikimedia.org/wikipedia/commons/5/5e/200x133px-Biene_auf_lavendel.png'&gt;
&lt;/p&gt;
&lt;/div&gt; </t>
<t tx="josephorr.20170304174421.1"></t>
<t tx="josephorr.20170304174436.1">This is just some regular text.
And now for some hipster ipsum:
Prism selvage farm-to-table, chillwave hexagon echo park tacos wayfarers shabby chic. Man braid chartreuse offal meggings. Messenger bag fap shabby chic blue bottle, typewriter la croix man braid chicharrones fashion axe mlkshk mustache narwhal. Single-origin coffee banh mi put a bird on it, lomo cred mumblecore schlitz flexitarian freegan retro. Edison bulb keytar single-origin coffee, direct trade plaid pug everyday carry ethical enamel pin. Viral neutra direct trade ramps semiotics intelligentsia, organic celiac food truck vice. Echo park PBR&amp;B iPhone trust fund dreamcatcher.</t>
<t tx="josephorr.20170304175209.1">@language md
## Leo Does Markdown
This node was writen with Markdown.
Use the @language md directive to set the node to markdown.</t>
</tnodes>
</leo_file>
<html>
<head>
<script src="https://unpkg.com/vue"></script>
<style type="text/css">
body {
font-family: Verdana;
}
.item {
cursor: pointer;
}
.bold {
font-weight: bold;
}
ul {
padding-left: 1em;
line-height: 1.5em;
list-style-type: none;
}
li {
white-space: nowrap;
}
.active {
background: #81ff00;
}
li > div {
padding-left:4px;
}
</style>
<script src="https://cdn.rawgit.com/lingtalfi/simpledrag/master/simpledrag.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/styles/vs.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.9.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<style type="text/css">
html, body {
height: 100%;
margin:0;
}
textarea {
margin-top:20px;
border: none;
background-color: transparent;
resize: none;
outline: none;
font-size:16px;
font-family: Verdana;
height:100%;
width:100%;
}
.panes-container {
display: flex;
width: 100%;
overflow: hidden;
}
.left-pane {
width: 250px;
background: #fff;
}
.panes-separator {
width: 11px;
background: #eee;
position: relative;
cursor: col-resize;
background-image: url('vertical.png');
background-repeat: no-repeat;
background-position: 50%;
}
.right-pane {
flex: auto;
background: #fff;
}
.panes-container,
.panes-separator,
.left-pane,
.right-pane {
margin: 0;
padding: 0;
height: 100%;
}
.right-pane {
padding:10px;
padding-top:0px;
}
</style>
</head>
<body>
<script type="text/x-template" id="item-template">
<li>
<div
:class="{bold: isFolder, active: active}"
@click="toggle">
<span v-if="isFolder">{{open ? '▼' : '▶'}}</span>
{{model.name}}
</div>
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="model in model.children"
:model="model">
</item>
</ul>
</li>
</script>
<div class="panes-container">
<div class="left-pane" id="left-pane">
<ul id="demo">
<item
class="item"
:model="treeData">
</item>
</ul>
</div>
<div class="panes-separator" id="panes-separator"></div>
<div class="right-pane" id="right-pane">
</div>
</div>
</body>
<script src="tree.js"></script>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="v">
<xsl:variable name="t" select="@t"/>
{
"name":"<xsl:value-of select="vh"/>",
"text":`<xsl:value-of select="//t[@tx=$t]"/>`,
"children":[<xsl:apply-templates select="v"/>]
}
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
var leftPane = document.getElementById('left-pane');
var rightPane = document.getElementById('right-pane');
var paneSep = document.getElementById('panes-separator');
rightPane.innerHTML = '';
var currentNode = null;
function showText(text){
if (! text){return}
language = getLanguage(text);
// just plain text
if (! language){
rightPane.innerHTML = `<textarea readonly>${text}</textarea>`;
return;
}
// remove directives
text = removeFirstLine(text);
switch(language){
case 'md':
text = marked(text);
rightPane.innerHTML = text;
break;
case 'html':
rightPane.innerHTML = text;
break;
default:
text = `<pre><code class="${language}">${text}</code></pre>`;
rightPane.innerHTML = text;
hljs.highlightBlock(rightPane);
}
}
function getLanguage(text){
var language = '';
var re = /^@language (\w+)/;
var languageTokens = re.exec(text);
if (languageTokens){
language = languageTokens[1];
console.log(language);
}
return language;
}
function removeFirstLine(text){
return text.split(/[\n]/).splice(1).join('\n');
}
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function(){
return {
open: false,
active: false
}
},
computed: {
isFolder: function() {
return this.model.children && this.model.children.length;
}
},
methods: {
toggle: function(){
if (this.isFolder){
this.open = ! this.open;
}
if (currentNode) {
currentNode.active = false;
}
currentNode = this;
currentNode.active = true;
console.log(this);
showText(this.model.text);
}
}
});
function loadXMLDoc(filename, type){
var xhttp = new XMLHttpRequest();
xhttp.open('GET', filename, false); // synchronous
xhttp.send('');
return xhttp['response' + type];
}
function displayResult(){
var xmlString = loadXMLDoc('example.leo', 'Text');
var oParser = new DOMParser();
var xml = oParser.parseFromString(xmlString,'text/xml');
var xsl = loadXMLDoc('leo.xsl', 'XML');
var xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsl);
var resultDocument = xsltProcessor.transformToFragment(xml, document);
var data = resultDocument.textContent;
data = data.replace(/,\s?$/,''); // kludge to get rid of trailing comma
data = 'var data = ' + data;
eval(data); // JSON.Parse doesn't work because of template strings
var demo = new Vue({
el: '#demo',
data: {
treeData: data
}
})
}
// The script below constrains the target to move horizontally between a left and a right virtual boundaries.
// - the left limit is positioned at 10% of the screen width
// - the right limit is positioned at 90% of the screen width
var leftLimit = 0;
var rightLimit = 90;
paneSep.sdrag(function (el, pageX, startX, pageY, startY, fix) {
fix.skipX = true;
if (pageX < window.innerWidth * leftLimit / 100) {
pageX = window.innerWidth * leftLimit / 100;
fix.pageX = pageX;
}
if (pageX > window.innerWidth * rightLimit / 100) {
pageX = window.innerWidth * rightLimit / 100;
fix.pageX = pageX;
}
var cur = pageX / window.innerWidth * 100;
if (cur < 0) {
cur = 0;
}
if (cur > window.innerWidth) {
cur = window.innerWidth;
}
var right = (100-cur-2);
leftPane.style.width = cur + '%';
rightPane.style.width = right + '%';
}, null, 'horizontal');
displayResult();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment