""" 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