Skip to content

Commit

Permalink
Implements brackets tags and log messages. Version bump to 0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
claudioc committed Feb 16, 2013
1 parent cc527d0 commit 9775706
Show file tree
Hide file tree
Showing 15 changed files with 158 additions and 42 deletions.
23 changes: 22 additions & 1 deletion README.md
Expand Up @@ -18,7 +18,7 @@ Features
- Show differences between document revisions - Show differences between document revisions
- Search through the content and the page names - Search through the content and the page names
- Layout accepts custom sidebar and footer - Layout accepts custom sidebar and footer
- Can use custom css and JavaScript scripts - Can use custom CSS and JavaScript scripts
- White list for authorization on page reading and writing - White list for authorization on page reading and writing
- Detects unwritten pages (will appear in red) - Detects unwritten pages (will appear in red)
- Automatically push to a remote - Automatically push to a remote
Expand Down Expand Up @@ -72,3 +72,24 @@ You can customize jingo in four different ways:
All those files are cached (thus, not re-read for every page load, but kept in memory). This means that for every modification in _style.css and _script.js you need to restart the server (sorry, working on that). This is not true for the footer and the sidebar but ONLY IF you edit those pages from jingo (which in that case will clear the cache by itself). All those files are cached (thus, not re-read for every page load, but kept in memory). This means that for every modification in _style.css and _script.js you need to restart the server (sorry, working on that). This is not true for the footer and the sidebar but ONLY IF you edit those pages from jingo (which in that case will clear the cache by itself).


jingo uses twitter Bootstrap and jQuery as its front-end components. jingo uses twitter Bootstrap and jQuery as its front-end components.

Editing
-------

To link to another Jingo wiki page, use the Jingo Page Link Tag.

[[Jingo Works]]

The above tag will create a link to the corresponding page file named
`jingo-works.md`. The conversion is as follows:

1. Replace any spaces (U+0020) with dashes (U+002D)
2. Replace any slashes (U+002F) with dashes (U+002D)

If you'd like the link text to be something that doesn't map directly to the
page name, you can specify the actual page name after a pipe:

[[How Jingo works|Jingo Works]]

The above tag will link to `jingo-works.md` using "How Jingo Works" as the link text.

2 changes: 1 addition & 1 deletion jingo
Expand Up @@ -14,7 +14,7 @@ var express = require('express')
, yaml = require("yaml") , yaml = require("yaml")
, program = require('commander'); , program = require('commander');


program.version('0.1.5') program.version('0.2.0')
.option('-c, --config <path>', 'Specify the config file') .option('-c, --config <path>', 'Specify the config file')
.option('-s, --sample-config', 'Dumps a config file template and exits') .option('-s, --sample-config', 'Dumps a config file template and exits')
.parse(process.argv); .parse(process.argv);
Expand Down
2 changes: 1 addition & 1 deletion lib/gitmech.js
Expand Up @@ -128,7 +128,7 @@ module.exports = Gitmech = (function() {


gitExec(["log", "-" + howMany, "--reverse", "--no-notes", "--pretty=format:%h%n%H%n%an%n%ae%n%aD%n%ar%n%at%n%s", version, "--", path], function(err, data) { gitExec(["log", "-" + howMany, "--reverse", "--no-notes", "--pretty=format:%h%n%H%n%an%n%ae%n%aD%n%ar%n%at%n%s", version, "--", path], function(err, data) {


var logdata = data.toString().split("\n") var logdata = data ? data.toString().split("\n") : []
, group , group
, metadata = []; , metadata = [];


Expand Down
1 change: 1 addition & 0 deletions lib/namer.js
Expand Up @@ -11,6 +11,7 @@ var normalize = function(str) {
str = iconv.convert(str) str = iconv.convert(str)
.toString() .toString()
.replace(/\s/g, '-') .replace(/\s/g, '-')
.replace(/\//g, '-')
.replace(/[^a-zA-Z0-9\- _]/g, "") .replace(/[^a-zA-Z0-9\- _]/g, "")
.toLowerCase(); .toLowerCase();


Expand Down
72 changes: 52 additions & 20 deletions lib/renderer.js
@@ -1,5 +1,7 @@


var Marked = require("marked"); var Marked = require("marked");
var Crypto = require('crypto');
var Namer = require("../lib/namer");


Marked.setOptions({ Marked.setOptions({
gfm: true, gfm: true,
Expand All @@ -10,37 +12,67 @@ Marked.setOptions({
} }
}); });


var tagmap = {};


var Renderer = { // Yields the content with the rendered [[bracket tags]]

// The rules are the same for Gollum https://github.com/github/gollum
tagmap: {}, function extractTags(text) {


render: function(content) { tagmap = {};


var text = Marked(content); var matches = text.match(/(.?)\[\[(.+?)\]\]([^\[]?)/g)

, tag
return Marked(content); , id;
},

// Yields the content with the rendered [[bracket tags]]
// The rules are the same for Gollum https://github.com/github/gollum
compileMarkup: function(content) {

var text = content.match(/(.?)\[\[(.+?)\]\]([^\[]?)/g);
console.log(text);
return text;


}, // TODO text the ' at the beginning
if (matches) {
matches.forEach(function(match) {
tag = /\[\[(.+?)\]\]/.exec(match)[1];
id = Crypto.createHash('sha1').update(tag).digest("hex")
tagmap[id] = tag;
text = text.replace(tag, id);
});


extractTags: function(content) { }
return text;
}

function evalTags(text) {
var parts
, name
, pageName
, re;

for (var k in tagmap) {
parts = tagmap[k].split("|");
name = pageName = parts[0];
if (parts[1]) {
pageName = parts[1];
}
pageName = Namer.normalize(pageName);

tagmap[k] = "<a class=\"internal\" href=\"/wiki/" + pageName + "\">" + name + "</a>";
}


return for (k in tagmap) {
re = new RegExp("\\[\\[" + k + "\\]\\]", "g");
text = text.replace(re, tagmap[k]);
} }
return text.replace(/\n/g, "");
}

var Renderer = {


render: function(content) {


var text = Marked(content);


text = extractTags(text);


text = evalTags(text);


return text;
}


}; };


Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{ {
"name": "jingo", "name": "jingo",
"version": "0.1.5", "version": "0.2.0",
"description": "A nodejs based wiki engine", "description": "A nodejs based wiki engine",
"author": "Claudio Cicali <claudio.cicali@gmail.com>", "author": "Claudio Cicali <claudio.cicali@gmail.com>",
"main": "jingo", "main": "jingo",
Expand Down
15 changes: 15 additions & 0 deletions public/css/style.css
Expand Up @@ -148,3 +148,18 @@ input#pageTitle {
a.absent { a.absent {
color: red; color: red;
} }

form.edit {
border-radius: 4px;
background-color: #eee;
padding: 2%;
padding-bottom: 1%;
width: 96%;
}

form.edit input[type=text] {
width: 98%;
}
form.edit textarea {
width: 103%;
}
22 changes: 15 additions & 7 deletions public/js/app.js
Expand Up @@ -26,22 +26,30 @@
return false; return false;
}); });


if (window.location.pathname.match(/^\/wiki\//)) { if (/^\/wiki\//.test(window.location.pathname)) {
var pages = $.map($("#content a[href]").filter(function(i, a) { var pages = []
var href = $(a).attr("href"); , match
return !(href[0] == '/' || href.match(/^(f|ht)tps?:/)); , href;
}), function(a) {
return a.getAttribute("href").split("#")[0]; $("#content a.internal").each(function(i, a) {
href = $(a).attr("href");
if (match = /\/wiki\/(.+)/.exec(href)) {
pages.push(match[1]);
}
}); });


$.getJSON("/misc/existence", {data: pages}, function(result) { $.getJSON("/misc/existence", {data: pages}, function(result) {
$.each(result.data, function(href, a) { $.each(result.data, function(href, a) {
$("#content a[href=" + a + "]").addClass("absent"); $("#content a[href=\\/wiki\\/" + a + "]").addClass("absent");
}); });
}); });
} }


function toggleCompareCheckboxes() { function toggleCompareCheckboxes() {
if ($hCol1.find(":checkbox").length == 1) {
$hCol1.find(":checkbox").hide();
return;
}
if ($hCol1.find(":checked").length == 2) { if ($hCol1.find(":checked").length == 2) {
$hCol1.find(":not(:checked)") $hCol1.find(":not(:checked)")
.hide(); .hide();
Expand Down
3 changes: 3 additions & 0 deletions public/vendor/jquery-migrate-1.1.0.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions routes/index.js
Expand Up @@ -239,6 +239,7 @@ exports.pageUpdate = function(req, res) {
var pageName = res.locals.pageName = Namer.normalize(req.params.page) var pageName = res.locals.pageName = Namer.normalize(req.params.page)
, errors , errors
, pageFile , pageFile
, message
, content; , content;


req.check('pageTitle', 'Page title cannot be empty').notEmpty(); req.check('pageTitle', 'Page title cannot be empty').notEmpty();
Expand All @@ -255,12 +256,15 @@ exports.pageUpdate = function(req, res) {


req.sanitize('pageTitle').trim(); req.sanitize('pageTitle').trim();
req.sanitize('content').trim(); req.sanitize('content').trim();
req.sanitize('message').trim();


content = "#" + req.body.pageTitle + "\n" + req.body.content; content = "#" + req.body.pageTitle + "\n" + req.body.content;
pageFile = app.locals.repo + "/" + pageName + ".md"; pageFile = app.locals.repo + "/" + pageName + ".md";


message = (req.body.message == "") ? "Content updated (" + pageName + ")" : req.body.message;

Fs.writeFile(pageFile, content, function() { Fs.writeFile(pageFile, content, function() {
app.locals.Git.add(pageName + ".md", "Content updated (" + pageName + ")", req.user.asGitAuthor, function(err) { app.locals.Git.add(pageName + ".md", message, req.user.asGitAuthor, function(err) {
Locker.unlock(pageName); Locker.unlock(pageName);
if (pageName == '_footer') { if (pageName == '_footer') {
app.locals._footer = null; app.locals._footer = null;
Expand Down Expand Up @@ -418,7 +422,7 @@ exports.miscSyntaxReference = function(req, res) {
res.render('syntax'); res.render('syntax');
} }


// Filters out pages that does not exist in the index // Filters out pages that do not exist in the index
exports.miscExistence = function(req, res) { exports.miscExistence = function(req, res) {


if (!req.query.data) { if (!req.query.data) {
Expand Down
2 changes: 1 addition & 1 deletion test/spec/namerSpec.js
Expand Up @@ -14,7 +14,7 @@ describe ("Namer", function() {
expect(Namer.normalize("CoffeE")).to.equal("coffee"); expect(Namer.normalize("CoffeE")).to.equal("coffee");
expect(Namer.normalize("Caffé")).to.equal("caffe"); expect(Namer.normalize("Caffé")).to.equal("caffe");
expect(Namer.normalize("Caffé corretto!")).to.equal("caffe-corretto"); expect(Namer.normalize("Caffé corretto!")).to.equal("caffe-corretto");
expect(Namer.normalize("Caff<p>e</p> senza schiuma")).to.equal("caffpep-senza-schiuma"); expect(Namer.normalize("Caff<p>e</p> senza schiuma")).to.equal("caffpe-p-senza-schiuma");
expect(Namer.normalize("Per favore: nessun, dico; E un punto...")).to.equal("per-favore-nessun-dico-e-un-punto"); expect(Namer.normalize("Per favore: nessun, dico; E un punto...")).to.equal("per-favore-nessun-dico-e-un-punto");
}); });


Expand Down
32 changes: 30 additions & 2 deletions test/spec/rendererSpec.js
Expand Up @@ -5,11 +5,39 @@ var Renderer = require("../../lib/renderer");


describe ("Renderer", function() { describe ("Renderer", function() {


it ("should render bracket tags", function() { it ("should render bracket tags1", function() {
var text = "a [[Foo]] b";
expect(Renderer.render(text)).to.be.equal("<p>a <a class=\"internal\" href=\"/wiki/foo\">Foo</a> b</p>");
});

it ("should render bracket tags2", function() {
var text = "a [[Foo]][[Foo]][[Foo]] b";
expect(Renderer.render(text)).to.be.equal("<p>a <a class=\"internal\" href=\"/wiki/foo\">Foo</a><a class=\"internal\" href=\"/wiki/foo\">Foo</a><a class=\"internal\" href=\"/wiki/foo\">Foo</a> b</p>");
});

it ("should render bracket tags3", function() {
var text = "a [[Foo Bar]] b";
expect(Renderer.render(text)).to.be.equal("<p>a <a class=\"internal\" href=\"/wiki/foo-bar\">Foo Bar</a> b</p>");
});


it ("should render bracket tags4", function() {
var text = "a [[Foo]][[Bar]] b"; var text = "a [[Foo]][[Bar]] b";
expect(Renderer.render(text)).to.be.equal("<p>a <a class=\"internal\" href=\"/wiki/foo\">Foo</a><a class=\"internal\" href=\"/wiki/bar\">Bar</a> b</p>");
});

it ("should render bracket tags5", function() {
var text = "a [[Foo]] [[Bar]] b";
expect(Renderer.render(text)).to.be.equal("<p>a <a class=\"internal\" href=\"/wiki/foo\">Foo</a> <a class=\"internal\" href=\"/wiki/bar\">Bar</a> b</p>");
});

it ("should render bracket tags6", function() {
var text = "a [[Il marito di Foo|Foobar]] [[Bar]] b";
expect(Renderer.render(text)).to.be.equal("<p>a <a class=\"internal\" href=\"/wiki/foobar\">Il marito di Foo</a> <a class=\"internal\" href=\"/wiki/bar\">Bar</a> b</p>");
});


expect(Renderer.compileMarkup(text)).to.be.a("<p>a <a class=\"internal\" href=\"/Foo\">Foo</a><a class=\"internal\" href=\"/Bar\">Bar</a> b</p>"); it ("should render bracket tags7", function() {
var text = "a [[Foo / Bar]] b";
expect(Renderer.render(text)).to.be.equal("<p>a <a class=\"internal\" href=\"/wiki/foo---bar\">Foo / Bar</a> b</p>");
}); });




Expand Down
6 changes: 3 additions & 3 deletions views/create.jade
Expand Up @@ -10,16 +10,16 @@ block content


mixin errors() mixin errors()


form(action='/pages', method='post') form(action='/pages', method='post', class='edit')


input(type="hidden", name="pageName", value="#{coalesce(pageName, '')}") input(type="hidden", name="pageName", value="#{coalesce(pageName, '')}")


div div
label Page title label Page title
input(type='text', name='pageTitle', value="#{coalesce(formData.pageTitle, '')}")#pageTitle.span8 input(type='text', name='pageTitle', value="#{coalesce(formData.pageTitle, '')}")#pageTitle


div div
textarea(name="content", rows=25)#editor.span8 #{coalesce(formData.content, '')} textarea(name="content", rows=25)#editor #{coalesce(formData.content, '')}


mixin saveAndCancel() mixin saveAndCancel()


Expand Down
9 changes: 6 additions & 3 deletions views/edit.jade
Expand Up @@ -19,15 +19,18 @@ block content
mixin errors() mixin errors()
mixin warning() mixin warning()


form(action='/pages/#{pageName}', method='post') form(action='/pages/#{pageName}', method='post', class='edit')


div div
label Page title label Page title
input(type='text', name='pageTitle', value="#{coalesce(formData.pageTitle, '')}")#pageTitle.span8 input(type='text', name='pageTitle', value="#{coalesce(formData.pageTitle, '')}")#pageTitle
input(type="hidden", name="_method", value="put") input(type="hidden", name="_method", value="put")


div div
textarea(name="content", rows=25)#editor.span8 #{coalesce(formData.content, '')} textarea(name="content", rows=25)#editor #{coalesce(formData.content, '')}

div
input(type='text', name='message', placeholder='Write a small message here explaining this change (optional)')#message.span8


mixin saveAndCancel() mixin saveAndCancel()


Expand Down
1 change: 1 addition & 0 deletions views/mixins/form.jade
Expand Up @@ -19,6 +19,7 @@ mixin markitupStylesheets()
link(rel="stylesheet", type="text/css", href="/vendor/markitup/sets/markdown/style.css") link(rel="stylesheet", type="text/css", href="/vendor/markitup/sets/markdown/style.css")


mixin markitupJavaScripts() mixin markitupJavaScripts()
script(src="/vendor/jquery-migrate-1.1.0.min.js")
script(src="/vendor/markitup/jquery.markitup.js") script(src="/vendor/markitup/jquery.markitup.js")
script(src="/vendor/markitup/sets/markdown/set.js") script(src="/vendor/markitup/sets/markdown/set.js")
script script
Expand Down

0 comments on commit 9775706

Please sign in to comment.