"""
open/dulcinea/lib/ui/wiki.qpy
"""
from dulcinea.common import format_date_time, format_user
from dulcinea.note import get_note_mapping, Note
from dulcinea.ui.attachment import format_attachments, AttachmentUI
from dulcinea.ui.table import Table
from dulcinea.ui.user.util import ensure_signed_in
from dulcinea.util import format_text
from qp.fill.directory import Directory
from qp.fill.form import Form
from qp.fill.html import nl2br, href
from qp.pub.common import header, footer, redirect, page
from qpy import xml, xml_quote, stringify
import re
def wiki_link:xml(title, verbose=False):
note = get_note_mapping().get_note(title)
if note:
first_text = note.get_text()[:60]
if verbose:
href('/wiki/%s/' % title, title)
' - '
first_text
else:
href('/wiki/%s/' % title, title, first_text)
class WikiDirectory (Directory):
def get_exports(self):
ensure_signed_in()
yield ('', 'index', 'Wiki', 'Admin wiki Pages')
yield ('new', 'new', 'New', 'New wiki page')
def __init__(self, title='Wiki Pages'):
self.title = title
def _q_lookup(self, component):
if component.strip():
note = get_note_mapping().get_note(component)
if note:
return self.get_wiki_page_ui()(note)
def get_wiki_page_ui(self):
return WikiPageUI
def new(self):
return edit_wiki_page(Note())
def index:xml(self):
header(self.title)
form = Form(use_tokens=False, method="get")
form.add_string('keywords')
form.add_submit('search', value="Search")
form.render()
'
'
keyword = form.get('keywords')
if keyword:
keyword = keyword.lower()
matches = [(name, note)
for name, note in get_note_mapping().iteritems()
if keyword in name.lower() or
keyword in (note.get_text() or '').lower()]
else:
matches = []
if keyword and not matches:
'No matches found for '
keyword
else:
'
'
for letter in str('ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
href("#%s" % letter, letter)
'
'
table = Table()
table.column(name='Name')
table.column(last_editor='Last Editor')
table.column(last_edit_time='Last Edit Time')
if matches:
table.column(context='First Match')
def format_context:xml(keyword, note):
text = (note.get_text() or '').lower()
delta = 40
length = 80
pos = text.find(keyword)
if pos >= 0:
start = max(0, pos - delta)
sample = note.get_text()[start:start + length]
match_start = pos - start
match_end = match_start + len(keyword)
match = sample[match_start:match_end]
sample[:match_start]
'%s' % match
sample[match_end:]
for name, note in matches:
table.row(
name=href(name, note.get_title(), name=name[0].upper()),
last_editor=note.get_user().get_id(),
last_edit_time=format_date_time(note.get_timestamp()),
context=format_context(keyword, note))
else:
for name, note in get_note_mapping().iteritems():
table.row(
name=href(name, note.get_title(), name=name[0].upper()),
last_editor=note.get_user().get_id(),
last_edit_time=format_date_time(note.get_timestamp()))
''
table.render(css_class='shaded', width="100%")
'
'
footer(self.title)
class WikiPageUI (Directory):
word_pattern = re.compile(r'\b\w+\b')
def __init__(self, note):
self.note = note
def get_exports(self):
yield ('', 'index', self.note.get_title(), None)
yield ('edit', 'edit', 'Edit', 'Edit this wiki page.')
attachment_count = len(self.note.get_attachments())
if attachment_count:
yield ('file', None, 'Files %d' % attachment_count,
'%d File(s) attached to %s' % (attachment_count,
self.note.get_title()))
else:
yield ('file', None, 'Files',
'No Files attached to %s' % self.note.get_title())
yield ('delete', 'delete', 'Delete', 'Delete this wiki page.')
def index:xml(self):
header(self.note.get_title())
wikified_text = self.wikify_text(self.note.get_text())
''
if self.note.get_text():
'
%s
' % format_text(wikified_text)
format_attachments(self.note, 'file/')
if self.note.get_timestamp() and self.note.get_user():
'
%s %s
' % (
format_user(self.note.get_user(), name=False, email=False),
'
(%s)' % (
format_date_time(self.note.get_timestamp())))
'
'
footer(self.note.get_title())
@classmethod
def get_words(klass, text):
return set(klass.word_pattern.findall(text or ''))
@staticmethod
def word_link(word, url, text):
return re.sub(r'\b(%s)\b' % word, stringify(href(url, word)), text)
def wikify_text(self, text):
is_wiki_name = get_note_mapping().get_note
text = stringify(xml_quote(text))
for word in self.get_words(text):
if is_wiki_name(word):
text = self.word_link(word,"../" + word, text)
return nl2br(xml(text))
def edit(self):
return edit_wiki_page(self.note)
def delete(self):
form = Form()
form.add_submit('delete', value="Delete")
form.add_submit('cancel', value="Cancel")
if form.get('cancel'):
redirect('.')
if not form.is_submitted():
return page("Delete %s?" % self.note.get_title(),
'Really delete "%s"?' % self.note.get_title(),
form.render())
get_note_mapping().remove_note(self.note)
redirect('..')
def _q_lookup(self, component):
def _wiki_file_decorate:xml(attachable, body, title):
page(title + ' for ' + self.note.get_title(),
'', body, '
')
if component == 'file':
return AttachmentUI(self.note,
decorate=_wiki_file_decorate)
def edit_wiki_page(wiki_page):
form = Form()
if wiki_page.get_title() is None: # New page.
form.add_string('name', title='Name', required=True)
if not form.has_errors():
name = form.get('name', '') or ''
if ' ' in name:
form.set_error('name', 'No spaces allowed in wiki names.')
elif name and len(WikiPageUI.word_pattern.findall(name)) != 1:
form.set_error('name', 'Name must be one word.')
if get_note_mapping().get_note(name):
form.set_error('name', 'That name is already used.')
form.add_text('text', title="Text", value=wiki_page.get_text(),
cols=90, rows=30)
form.add_submit('submit', value="Submit")
form.add_submit('cancel', value="Cancel")
if form.get('cancel'):
redirect('.')
if not form.is_submitted() or form.has_errors():
return page(wiki_page.get_title(), form.render())
if form.get('name'):
wiki_page.set_title(form.get('name'))
wiki_page.set_text(form.get('text') or '')
wiki_page.set_timestamp()
wiki_page.set_user()
get_note_mapping().add_note(wiki_page)
redirect('.')
def format_wiki_css:str(COLOR_MAP):
'''
div.wiki_page {
margin: 1em;
}
div.wiki_text {
margin: 1em;
}
div.wiki_author {
margin: 1em;
float: right;
}
span.wiki_date {
font-size: smaller;
}
div.wiki_nav {
position:fixed;
right:0em;
width:1em;
float:right;
background: %(CRUMBS_BG)s;
}
div.wiki_nav a {
display: block;
text-decoration: none;
text-align: center;
}
span.match {
background: yellow;
}
div.wiki_page td.context {
font-size: smaller;
}
div.wiki_page td.last_edit_time {
font-size: smaller;
}
''' % COLOR_MAP