tags: home main index start author: admin protected: admin
-=index=-
The current time is: -=time=-
-=uptime=-
tags: markdown cheatsheet help author: admin protected: admin
{:.no_toc}
#### This is an h4 tag
###### This is an h6 tag
*This text will be italic*
_This will also be italic_
**This text will be bold**
__This will also be bold__
*You __can__ combine them*
This text will be italic This will also be italic This text will be bold This will also be bold You can combine them
[Reddit](http://reddit.com)

* Item 1
* Item 2
1. Item 2a
2. Item 2b
3. Item 2c
start with 4 spaces
void main(){
return -1;
}
void main(){
return -1;
}
> We're living the future so
> the present is our past.
We're living the future so the present is our past.
[Reddit](http://reddit.com){: #myid .btn .btn-primary}
Reddit{: #myid .btn .btn-primary}
* footnotes [^foot]
[^foot]: I really was missing those.
Col1 | Very very long head | Very very long head |
-----|:-------------------:|-------------------:|
cell1 | center-align | right-align |
cell2 | one more | one more |
{: .table .table-bordered .table-hover}
Col1 | Very very long head | Very very long head |
---|---|---|
cell1 | center-align | right-align |
cell2 | one more | one more |
{: .table .table-bordered .table-hover} |
The first lines before a double linebreak are treated as header lines.
Here you can use these special headers among other normal markdown headers:
private: foo # page is only accessible by the user foo
private: foo bar # page is only accessible by the users foo and bar
protected: fritz # page is only editable by the user fritz
protected: hans foo # page is only editable by the users hans and foo
tags: blah stuff crap # feel free to use this to enhance searchability
This is a specialty of this Wiki and has nothing to do with markdown.
Use -=func=-
or -=func arg=-
where func should be defined in the replacers method.
The following youtube video is embedded using embed
as func and http://www.youtube.com/embed/SDnYMbYB-nU
as arg
-=embed http://www.youtube.com/embed/SDnYMbYB-nU=-
I really was missing those. ↩
#!/usr/bin/env ruby | |
DATADIR = (i=ARGV.index('--datadir')) ? ARGV.slice!(i,i+1)[1] : 'data' # hack to cope with Sinatras ARGV greediness | |
require 'sinatra' | |
require 'haml' | |
require 'maruku' | |
require 'yaml/store' | |
STDERR.reopen(File.new('access.log','a')).sync = true | |
def clean(name, fallback=nil, re=/\w+/) # clean a name with a regexp | |
name.tr(' ','_').match(re)[0] rescue fallback | |
end | |
def pages(name=nil, version=nil) # find matching files/versions | |
Dir.glob("#{DATADIR}/#{'.'if version}#{clean(name,'*')}.#{clean(version,'*')}.*") | |
end | |
def markdown_parts(s) # read markdown headers and body and convert headers to a hash | |
lines = s.lines.to_a | |
[{},''].tap{|a| a[0][$1.to_sym]=$2 while (l=lines.shift) =~ /^(\w[\w\s\-]+): +(.*?)$/; a[1] = ([l]+lines).join} | |
end | |
def protect!(levels, headers=@headers, user=@user) # keep out the unauthorized | |
levels.each{|l| throw(:halt, [401, "Not authorized - #{l}\n"]) if headers[l] && !headers[l].split.map(&:strip).include?(user)} | |
end | |
def replacers(s) # special replacers to add more dynamic to the wiki | |
s.gsub(/-=index=-/){ @files = pages('*').map{|e| File.basename(e).split('.')[0]}.sort; haml(:list, layout: false) } | |
.gsub(/-=versions (.*?)=-/){ @files = pages($1,'*').map{|e| File.basename(e).split('.')[1,2]}; haml(:list, layout: false) } | |
.gsub(/-=partial (.*?)=-/){ h,c=markdown_parts(File.read(pages($1)[0]));protect!([:private],h); Maruku.new(c).to_html } | |
.gsub(/-=embed (.*?)=-/){ %(<iframe src="#{URI.parse($1).to_s}" frameborder="0"> </iframe>) } | |
.gsub(/-=diff=-/){ %x{diff -Bu #{pages(@page,@version).first} #{pages(@page).first}} } | |
.gsub(/-=time=-/, Time.now.to_s) # you can simply add custom stuff like this | |
.gsub(/-=uptime=-/, %x{uptime}.strip) # remember to keep it safe ;) | |
end | |
configure{ set :sessions => true } | |
before{ @user = session[:user]; content_type 'text/html', :charset => 'utf-8' } # before every request | |
get '/' do redirect '/page/home' end | |
get '/page/?:page?/?' do | |
@page, @version = clean(params[:page], params.has_key?('edit') ? nil : 'home'), clean(params[:version]) | |
@raw_content = (@page ? (File.read(pages(@page,@version).first) rescue '') : '').gsub(/</, '<') #xss protection?! | |
@headers, @content = markdown_parts(replacers(@raw_content)) # replacer magic and header retrieval | |
protect!([:private]) # if the page has a private field in the header, honor it | |
haml (@content=='' || params.has_key?('edit')) ? :edit : :show | |
end | |
post '/page/?:page?/?' do # create or update a page .. yea not RESTful | |
throw(:halt, [401, "Not logged in\n"]) unless @user # only users can play here | |
FileUtils.mkdir_p(DATADIR) unless File.exist?(DATADIR) # create data dir | |
@page, time = clean(params[:page]), Time.now.strftime('%y%m%d%H%I%S') # pagename and version | |
file = pages(@page).first || nil # edit or create? | |
throw(:halt, [405, "Pagename invalid. (only a-zA-Z0-9_)\n"]) unless @page | |
protect!([:protected, :private], markdown_parts(File.read(file))[0]) if file # check rights | |
@headers, @content = markdown_parts(params[:content]) | |
FileUtils.copy(file, "#{DATADIR}/.#{@page}.#{time}.md") if file # backup current to a version | |
@headers = @headers.merge({author: @user}).map{|h| h.join(': ')}.join("\r\n") # overwrite author header | |
File.write(file || "#{DATADIR}/#{@page}.#{time}.md", "#{@headers}\r\n\r\n#{@content}") | |
redirect "/page/#{@page}" | |
end | |
post '/?' do # user stuff and search | |
if params[:q] # the search happens here. could be a GET but who cares? | |
@files = (pages('*') + pages('*','*')).map do |f| # searching in latest and versions | |
bnc = File.basename(f).split('.') # "basename components" | |
name, version = bnc[0]=='' ? bnc[1,2] : [bnc[0], nil] | |
hits = File.read(f).scan(/#{params[:q]}/i).size # magic search operation | |
[name, version, "#{hits} hits"] if hits > 0 | |
end.compact.sort{|a,b| b.last.to_i<=>a.last.to_i} # throw out non hits and sort by hits | |
@page = 'Search Results'; return haml :list | |
end | |
db = YAML::Store.new('users.yml') # User stuff happens here! login/logout/register | |
(@user = session[:user] = nil; return redirect(back)) if params[:logout] # that's the logout | |
throw(:halt, [401, "Username invalid. (only a-zA-Z0-9_)\n"]) unless (clean_user = clean(params[:user])) # clean names only | |
db.transaction{db[clean_user] = Digest::SHA2.base64digest(params[:pass]) unless db[clean_user]} if params[:register] # register | |
db.transaction{db[clean_user] == Digest::SHA2.base64digest(params[:pass]).to_s} ? | |
(session[:user] = clean_user; redirect(back)) : # successfully logged in | |
throw(:halt, [401, "Login invalid\n"]) # login failed | |
end | |
__END__ | |
@@ layout | |
!!! 5 | |
%html | |
%head | |
%title= @page ? "niki - #{@page}" : 'niki' | |
%meta{'http-equiv' => 'Content-Type', content: 'text/html', charset: 'utf-8'} | |
%meta{name: 'keywords', content: (@headers[:tags].split(/\W/).uniq.join(', ') rescue '')} | |
%link{rel: 'shortcut icon', href: 'about:blank'} | |
%link{rel: 'stylesheet', type: 'text/css', href: '//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css'} | |
:css | |
body { padding: 0; } | |
.container-narrow { margin: 40px auto; max-width: 820px; } | |
textarea{ width: 99%; } | |
iframe{ width: 99%; height:360px; } | |
%body | |
.container-narrow | |
%form.form-inline.pull-right{action: '/', method: 'post'} | |
%input.input-small{type: 'text', name: 'q', placeholder: 'search', required:''} | |
%button.btn{type:'submit'} go | |
%ul.nav.nav-pills.pull-right | |
%li | |
%a{href: '/page/home'} | |
%i.icon-home | |
home | |
%li | |
%a{href: "/page?edit"} | |
%i.icon-plus | |
new | |
%li | |
%a{href: "/page/#{@page}?edit#{"&version=#{@version}" if @version}"} | |
%i.icon-pencil | |
edit | |
%h3.muted= @page || 'New Page' | |
%hr | |
= yield | |
%hr | |
%footer.muted | |
© The Open Source Community | |
%form.form-inline.pull-right{action: '/', method: 'post'} | |
- if @user | |
%button.btn.btn-link{type:'submit', name: 'logout', value: '1'}&= "logout #{@user}" | |
- else | |
%input.input-small{type: 'text', name: 'user', placeholder: 'username', required:''} | |
%input.input-small{type: 'password', name: 'pass', placeholder: 'password', required:''} | |
%button.btn{type:'submit'} login | |
%button.btn{type:'submit', name: 'register', value: '1'} register | |
@@ list | |
%ul.nav.nav-list | |
- @files.each do |name, version, extra| | |
%li | |
%a{href: "/page/#{name}#{"?version=#{version}" if version}"}= [name, version, extra].compact.join(' – ') | |
@@ show | |
%div{:ondblclick => "location.href='/page/#{@page}?edit'"} | |
:markdown | |
#{@content} | |
@@ edit | |
%form{action: "/page/#{@page}", method: 'post'} | |
- unless @page | |
%input{type: 'text', name: 'page', required: '', placeholder: 'page name', autofocus: '', required:''} | |
%textarea{name: 'content', rows: '20', placeholder: 'page content in markdown', required:''}= @raw_content | |
%a.btn{href: "#{@page ? "/page/#{@page}" : '/page'}"} back | |
%input.btn.btn-primary{type: 'submit', value: 'save'} | |
- if @page | |
- if @version | |
%h4 Diff with latest version | |
%pre= replacers("-=diff=-") | |
%h4 Older versions of this page: | |
= replacers("-=versions #{@page}=-") |