# HG changeset patch # User Ludovic Chabant # Date 1356834104 28800 # Node ID aa6951805e1ab944bce96efe8e7d8d21b04130df # Parent 6ac0b74a57f7c8907418b279f9c89377a0471926 New features and bug fixes: - Extracted navigation and footer parts into their own model/view. - Added search. - Better typography styles. - Fixed some bugs in the Handlebars helpers. diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/indexer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/indexer.py Sat Dec 29 18:21:44 2012 -0800 @@ -0,0 +1,98 @@ +import os +import os.path +import logging +from whoosh.index import create_in, open_dir +from whoosh.fields import Schema, ID, KEYWORD, TEXT, STORED +from whoosh.qparser import QueryParser + + +class WikiIndex(object): + def __init__(self, store_dir, logger=None): + self.store_dir = store_dir + self.logger = logger + if logger is None: + self.logger = logging.getLogger('wikked.index') + + def update(self, pages): + raise NotImplementedError() + + def search(self, query): + raise NotImplementedError() + + +class WhooshWikiIndex(WikiIndex): + def __init__(self, store_dir, logger=None): + WikiIndex.__init__(self, store_dir, logger) + if not os.path.isdir(store_dir): + os.makedirs(store_dir) + schema = Schema( + url=ID(stored=True), + title=TEXT(stored=True), + content=TEXT, + path=STORED, + time=STORED + ) + self.ix = create_in(store_dir, schema) + else: + self.ix = open_dir(store_dir) + + def update(self, pages): + to_reindex = set() + already_indexed = set() + + with self.ix.searcher() as searcher: + writer = self.ix.writer() + + for fields in searcher.all_stored_fields(): + indexed_url = fields['url'] + indexed_path = fields['path'] + indexed_time = fields['time'] + + if not os.path.isfile(indexed_path): + # File was deleted. + writer.delete_by_term('url', indexed_url) + else: + already_indexed.add(indexed_path) + if os.path.getmtime(indexed_path) > fields['time']: + # File as changed since last index. + writer.delete_by_term('url', indexed_url) + to_reindex.add(indexed_path) + + for page in pages: + page._ensureMeta() + page_path = page._meta['path'] + if page_path in to_reindex or page_path not in already_indexed: + self._indexPage(writer, page) + + writer.commit() + + def search(self, query): + with self.ix.searcher() as searcher: + title_qp = QueryParser("title", self.ix.schema).parse(query) + content_qp = QueryParser("content", self.ix.schema).parse(query) + comp_query = title_qp | content_qp + results = searcher.search(comp_query) + + page_infos = [] + for hit in results: + page_info = { + 'title': hit['title'], + 'url': hit['url'] + } + page_info['title_highlights'] = hit.highlights('title') + with open(hit['path']) as f: + content = unicode(f.read()) + page_info['content_highlights'] = hit.highlights('content', text=content) + page_infos.append(page_info) + return page_infos + + def _indexPage(self, writer, page): + self.logger.debug("Indexing: %s" % page.url) + writer.add_document( + url=unicode(page.url), + title=unicode(page.title), + content=unicode(page.raw_text), + path=page._meta['path'], + time=os.path.getmtime(page._meta['path']) + ) + diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/css/wikked.less --- a/wikked/static/css/wikked.less Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/css/wikked.less Sat Dec 29 18:21:44 2012 -0800 @@ -83,6 +83,9 @@ sup, sub { line-height: 0; } +li { + line-height: @baseLineHeight; +} // Global classes a.wiki-link { @@ -99,17 +102,16 @@ .decorator { text-transform: uppercase; font-weight: lighter; - font-size: 0.8em; + font-size: 0.7em; letter-spacing: 0.1em; color: @colorBlueDark; -} -.rev_id { - font-family: monospace; - font-size: 0.8em; - color: @colorCode; + .rev_id { + font-family: monospace; + color: @colorCode; + } } -.wrapper { +.container { nav, div.meta { color: @colorNavLink; font-size: @smallFontSize; @@ -141,6 +143,10 @@ } } +form.search { + display: inline-block; + margin: 0; +} form.page-edit { textarea { height: 10em; diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/js/wikked.js --- a/wikked/static/js/wikked.js Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/js/wikked.js Sat Dec 29 18:21:44 2012 -0800 @@ -18,6 +18,7 @@ loadedTemplates: {}, get: function(name, callback) { if (name in this.loadedTemplates) { + console.log('Returning cached template "{0}".'.format(name)); callback(this.loadedTemplates[name]); } else { var $loader = this; @@ -37,6 +38,9 @@ * Handlebars helper: reverse iterator. */ Handlebars.registerHelper('eachr', function(context, options) { + if (context === undefined) { + return ''; + } data = undefined; if (options.data) { data = Handlebars.createFrame(options.data); @@ -62,6 +66,12 @@ } return options.inverse(this); }); +Handlebars.registerHelper('ifneq', function(context, options) { + if (context != options.hash.to) { + return options.fn(this); + } + return options.inverse(this); +}); //-------------------------------------------------------------// @@ -103,8 +113,46 @@ /** * Wiki page models. */ + var NavigationModel = Backbone.Model.extend({ + idAttribute: 'path', + defaults: function() { + return { + path: "main-page", + action: "read" + }; + }, + initialize: function() { + this.on('change:path', function(model, path) { + model._onChangePath(path); + }); + this._onChangePath(this.get('path')); + return this; + }, + _onChangePath: function(path) { + this.set('url_home', '/#/read/main-page'); + this.set('url_read', '/#/read/' + path); + this.set('url_edit', '/#/edit/' + path); + this.set('url_hist', '/#/changes/' + path); + this.set('url_search', '/search'); + } + }); + + var FooterModel = Backbone.Model.extend({ + defaults: function() { + return { + url_extras: [ { name: 'Home', url: '/' } ] + }; + }, + addExtraUrl: function(name, url, index) { + if (index === undefined) { + this.get('url_extras').push({ name: name, url: url }); + } else { + this.get('url_extras').splice(index, 0, { name: name, url: url }); + } + } + }); + var PageModel = Backbone.Model.extend({ - urlRoot: '/api/read/', idAttribute: 'path', defaults: function() { return { @@ -120,6 +168,7 @@ }); this._onChangePath(this.get('path')); this._onChangeText(''); + return this; }, url: function() { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); @@ -137,14 +186,6 @@ return meta[key]; }, _onChangePath: function(path) { - this.set('url_home', '/#/read/main-page'); - this.set('url_read', '/#/read/' + path); - this.set('url_edit', '/#/edit/' + path); - this.set('url_hist', '/#/changes/' + path); - this.set('url_rev', '/#/revision/' + path); - this.set('url_diffc', '/#/diff/c/' + path); - this.set('url_diffr', '/#/diff/r/' + path); - this.set('url_inlinks', '/#/inlinks/' + path); }, _onChangeText: function(text) { this.set('content', new Handlebars.SafeString(text)); @@ -154,22 +195,59 @@ var PageStateModel = PageModel.extend({ urlRoot: '/api/state/' }); - - var PageSourceModel = PageModel.extend({ - urlRoot: '/api/raw/' + + var MasterPageModel = PageModel.extend({ + initialize: function() { + this.nav = new NavigationModel({ id: this.id }); + this.footer = new FooterModel(); + MasterPageModel.__super__.initialize.apply(this, arguments); + if (this.action !== undefined) { + this.nav.set('action', this.action); + this.footer.set('action', this.action); + } + return this; + }, + _onChangePath: function(path) { + MasterPageModel.__super__._onChangePath.apply(this, arguments); + this.nav.set('path', path); + } }); - var PageEditModel = PageModel.extend({ - urlRoot: '/api/edit/' + var PageReadModel = MasterPageModel.extend({ + urlRoot: '/api/read/', + action: 'read', + _onChangePath: function(path) { + PageReadModel.__super__._onChangePath.apply(this, arguments); + this.footer.addExtraUrl('Pages Linking Here', '/#/inlinks/' + path, 1); + this.footer.addExtraUrl('JSON', '/api/read/' + path); + } + }); + + var PageSourceModel = MasterPageModel.extend({ + urlRoot: '/api/raw/', + action: 'source' }); - var PageHistoryModel = PageModel.extend({ - urlRoot: '/api/history/' + var PageEditModel = MasterPageModel.extend({ + urlRoot: '/api/edit/', + action: 'edit' }); - var PageRevisionModel = PageModel.extend({ + var PageHistoryModel = MasterPageModel.extend({ + urlRoot: '/api/history/', + action: 'history', + _onChangePath: function(path) { + PageHistoryModel.__super__._onChangePath.apply(this, arguments); + this.set('url_rev', '/#/revision/' + path); + this.set('url_diffc', '/#/diff/c/' + path); + this.set('url_diffr', '/#/diff/r/' + path); + } + }); + + var PageRevisionModel = MasterPageModel.extend({ urlRoot: '/api/revision/', idAttribute: 'path_and_rev', + action: 'revision', defaults: function() { return { path: "main-page", @@ -177,6 +255,7 @@ }; }, initialize: function() { + PageRevisionModel.__super__.initialize.apply(this, arguments); this.on('change:path', function(model, path) { model._onChangePathOrRev(path, model.get('rev')); }); @@ -184,7 +263,7 @@ model._onChangePathOrRev(model.get('path'), rev); }); this._onChangePathOrRev(this.get('path'), this.get('rev')); - PageRevisionModel.__super__.initialize.call(this); + return this; }, _onChangePathOrRev: function(path, rev) { this.set('path_and_rev', path + '/' + rev); @@ -195,9 +274,10 @@ } }); - var PageDiffModel = PageModel.extend({ + var PageDiffModel = MasterPageModel.extend({ urlRoot: '/api/diff/', idAttribute: 'path_and_revs', + action: 'diff', defaults: function() { return { path: "main-page", @@ -206,6 +286,7 @@ }; }, initialize: function() { + PageDiffModel.__super__.initialize.apply(this, arguments); this.on('change:path', function(model, path) { model._onChangePathOrRevs(path, model.get('rev')); }); @@ -216,7 +297,7 @@ model._onChangePathOrRevs(model.get('path'), model.get('rev1'), rev2); }); this._onChangePathOrRevs(this.get('path'), this.get('rev1'), this.get('rev2')); - PageRevisionModel.__super__.initialize.call(this); + return this; }, _onChangePathOrRevs: function(path, rev1, rev2) { this.set('path_and_revs', path + '/' + rev1 + '/' + rev2); @@ -234,52 +315,130 @@ } }); - var IncomingLinksModel = PageModel.extend({ - urlRoot: '/api/inlinks/' + var IncomingLinksModel = MasterPageModel.extend({ + urlRoot: '/api/inlinks/', + action: 'inlinks' + }); + + var WikiSearchModel = MasterPageModel.extend({ + urlRoot: '/api/search/', + action: 'search', + title: function() { + return 'Search'; + }, + execute: function(query) { + var $model = this; + $.getJSON('/api/search', { q: query }) + .success(function (data) { + $model.set('hits', data.hits); + }) + .error(function() { + alert("Error searching..."); + }); + } }); /** * Wiki page views. */ - var PageReadView = Backbone.View.extend({ - tagName: "div", + var PageView = Backbone.View.extend({ + tagName: 'div', + className: 'wrapper', initialize: function() { + PageView.__super__.initialize.apply(this, arguments); var $view = this; - var model = new PageModel({ path: this.id }); - model.fetch({ - success: function(model, response, options) { - TemplateLoader.get('read-page', function(src) { - var template_data = model.toJSON(); - var template = Handlebars.compile(src); - $view.$el.html(template(template_data)); - $('a.wiki-link[data-wiki-url]').each(function(i, el) { - var jel = $(el); - if (jel.hasClass('missing')) - jel.attr('href', '/#/edit/' + jel.attr('data-wiki-url')); - else - jel.attr('href', '/#/read/' + jel.attr('data-wiki-url')); - }); - document.title = model.title(); - }); - }, - error: function(model, xhr, options) { - TemplateLoader.get('404', function(src) { - var template_data = model.toJSON(); - var template = Handlebars.compile(src); - $view.$el.html(template(template_data)); - }); + this.model.on("change", function() { $view.render(); }); + return this; + }, + render: function(view) { + if (this.templateName !== undefined) { + this.renderTemplate(this.templateName, this.renderCallback); + } + this.renderTitle(this.titleFormat); + return this; + }, + renderTemplate: function(tpl_name, callback) { + var $view = this; + TemplateLoader.get(tpl_name, function(src) { + var template = Handlebars.compile(src); + $view.$el.html(template($view.model.toJSON())); + if (callback !== undefined) { + callback.call($view, $view, $view.model); } }); + }, + renderTitle: function(formatter) { + var title = this.model.title(); + if (formatter !== undefined) { + title = formatter.call(this, title); + } + document.title = title; + } + }); + _.extend(PageView, Backbone.Events); + + var NavigationView = PageView.extend({ + templateName: 'nav', + initialize: function() { + NavigationView.__super__.initialize.apply(this, arguments); + this.render(); + return this; + }, + render: function() { + this.renderTemplate('nav'); + }, + postRender: function() { + var $view = this; + this.$('#search').submit(function() { + app.navigate('/search/' + $(this.q).val(), { trigger: true }); + return false; + }); + } + }); + + var FooterView = PageView.extend({ + templateName: 'footer', + initialize: function() { + FooterView.__super__.initialize.apply(this, arguments); + this.render(); + return this; + }, + render: function() { + this.renderTemplate('footer'); + }, + postRender: function() { + } + }); + + var MasterPageView = PageView.extend({ + initialize: function() { + MasterPageView.__super__.initialize.apply(this, arguments); + this.nav = new NavigationView({ model: this.model.nav }); + this.footer = new FooterView({ model: this.model.footer }); + this.render(); + return this; + }, + renderCallback: function(view, model) { + this.nav.$el.prependTo(this.$el); + this.nav.postRender(); + this.footer.$el.appendTo(this.$el); + this.footer.postRender(); + } + }); + + var PageReadView = MasterPageView.extend({ + templateName: 'read-page', + initialize: function() { + PageReadView.__super__.initialize.apply(this, arguments); // Also get the current state, and show a warning // if the page is new or modified. - var stateModel = new PageStateModel({ id: this.id }); + var stateModel = new PageStateModel({ path: this.model.get('path') }); stateModel.fetch({ success: function(model, response, options) { if (model.get('state') == 'new' || model.get('state') == 'modified') { TemplateLoader.get('state-warning', function(src) { - var template_data = model.toJSON(); var template = Handlebars.compile(src); - var warning = $(template(template_data)); + var warning = $(template(model.toJSON())); warning.css('display', 'none'); warning.prependTo($('#app')); warning.slideDown(); @@ -292,34 +451,35 @@ } }); return this; + }, + renderCallback: function(view, model) { + PageReadView.__super__.renderCallback.apply(this, arguments); + // Replace all wiki links with proper hyperlinks using the JS app's + // URL scheme. + this.$('a.wiki-link[data-wiki-url]').each(function(i) { + var jel = $(this); + if (jel.hasClass('missing')) + jel.attr('href', '/#/edit/' + jel.attr('data-wiki-url')); + else + jel.attr('href', '/#/read/' + jel.attr('data-wiki-url')); + }); } }); - var PageEditView = Backbone.View.extend({ - initialize: function() { - var $view = this; - var model = new PageEditModel({ path: this.id }); - model.fetch({ - success: function(model, response, options) { - TemplateLoader.get('edit-page', function(src) { - var template_data = model.toJSON(); - var template = Handlebars.compile(src); - $view.$el.html(template(template_data)); - document.title = 'Editing: ' + model.title(); - - $('#page-edit').submit(function() { - $view._submitText(this, model.get('path')); - return false; - }); - }); - }, - error: function(model, xhr, options) { - } + var PageEditView = MasterPageView.extend({ + templateName: 'edit-page', + renderCallback: function(view, model) { + PageEditView.__super__.renderCallback.apply(this, arguments); + this.$('#page-edit').submit(function() { + view._submitText(this, model.get('path')); + return false; }); - return this; + }, + titleFormat: function(title) { + return 'Editing: ' + title; }, _submitText: function(form, path) { - $.post('/api/edit/' + path, $(form).serialize()) + $.post('/api/edit/' + path, this.$(form).serialize()) .success(function(data) { app.navigate('/read/' + path, { trigger: true }); }) @@ -329,28 +489,17 @@ } }); - var PageHistoryView = Backbone.View.extend({ - initialize: function() { - var $view = this; - var model = new PageHistoryModel({ path: this.id }); - model.fetch({ - success: function(model, response, options) { - TemplateLoader.get('history-page', function(src) { - var template_data = model.toJSON(); - var template = Handlebars.compile(src); - $view.$el.html(template(template_data)); - document.title = 'Changes: ' + model.title(); - - $('#diff-page').submit(function() { - $view._triggerDiff(this, model.get('path')); - return false; - }); - }); - }, - error: function() { - } + var PageHistoryView = MasterPageView.extend({ + templateName: 'history-page', + renderCallback: function(view, model) { + PageHistoryView.__super__.renderCallback.apply(this, arguments); + this.$('#diff-page').submit(function() { + view._triggerDiff(this, model.get('path')); + return false; }); - return this; + }, + titleFormat: function(title) { + return 'History: ' + title; }, _triggerDiff: function(form, path) { var rev1 = $('input[name=rev1]:checked', form).val(); @@ -359,60 +508,31 @@ } }); - var PageRevisionView = Backbone.View.extend({ - initialize: function() { - var $view = this; - var model = new PageRevisionModel({ path: this.id, rev: this.options.rev }); - model.fetch({ - success: function(model, response, options) { - TemplateLoader.get('revision-page', function(src) { - var template_data = model.toJSON(); - var template = Handlebars.compile(src); - $view.$el.html(template(template_data)); - document.title = model.title() + ' [' + model.get('rev') + ']'; - }); - } - }); + var PageRevisionView = MasterPageView.extend({ + templateName: 'revision-page', + titleFormat: function(title) { + return title + ' [' + this.model.get('rev') + ']'; } }); - var PageDiffView = Backbone.View.extend({ - initialize: function() { - var $view = this; - var model = new PageDiffModel({ path: this.id, rev1: this.options.rev1, rev2: this.options.rev2 }); - model.fetch({ - success: function(model, response, options) { - TemplateLoader.get('diff-page', function(src) { - var template_data = model.toJSON(); - var template = Handlebars.compile(src); - $view.$el.html(template(template_data)); - document.title = model.title() + ' [' + model.get('rev1') + '-' + model.get('rev2') + ']'; - }); - } - }); + var PageDiffView = MasterPageView.extend({ + templateName: 'diff-page', + titleFormat: function(title) { + return title + ' [' + this.model.get('rev1') + '-' + this.model.get('rev2') + ']'; } }); - var IncomingLinksView = Backbone.View.extend({ - initialize: function() { - var $view = this; - var model = new IncomingLinksModel({ path: this.id }); - model.fetch({ - success: function(model, response, options) { - TemplateLoader.get('inlinks-page', function(src) { - var template_data = model.toJSON(); - var template = Handlebars.compile(src); - $view.$el.html(template(template_data)); - document.title = 'Incoming Links: ' + model.title(); - }); - }, - error: function() { - } - }); - return this; + var IncomingLinksView = MasterPageView.extend({ + templateName: 'inlinks-page', + titleFormat: function(title) { + return 'Incoming Links: ' + title; } }); + var WikiSearchView = MasterPageView.extend({ + templateName: 'search-results' + }); + /** * Main URL router. */ @@ -425,38 +545,93 @@ 'inlinks/*path': "showIncomingLinks", 'revision/*path/:rev': "readPageRevision", 'diff/c/*path/:rev': "showDiffWithPrevious", - 'diff/r/*path/:rev1/:rev2':"showDiff" + 'diff/r/*path/:rev1/:rev2':"showDiff", + 'search/:query': "showSearchResults" }, readPage: function(path) { - var page_view = new PageReadView({ id: path, el: $('#app') }); + var view = new PageReadView({ + el: $('#app'), + model: new PageReadModel({ path: path }) + }); + view.model.fetch(); this.navigate('/read/' + path); }, readMainPage: function() { this.readPage('main-page'); }, editPage: function(path) { - var edit_view = new PageEditView({ id: path, el: $('#app') }); + var view = new PageEditView({ + el: $('#app'), + model: new PageEditModel({ path: path }) + }); + view.model.fetch(); this.navigate('/edit/' + path); }, showPageHistory: function(path) { - var changes_view = new PageHistoryView({ id: path, el: $('#app') }); + var view = new PageHistoryView({ + el: $('#app'), + model: new PageHistoryModel({ path: path }) + }); + view.model.fetch(); this.navigate('/changes/' + path); }, showIncomingLinks: function(path) { - var in_view = new IncomingLinksView({ id: path, el: $('#app') }); + var view = new IncomingLinksView({ + el: $('#app'), + model: new IncomingLinksModel({ path: path }) + }); + view.model.fetch(); this.navigate('/inlinks/' + path); }, readPageRevision: function(path, rev) { - var rev_view = new PageRevisionView({ id: path, rev: rev, el: $('#app') }); + var view = new PageRevisionView({ + el: $('#app'), + rev: rev, + model: new PageRevisionModel({ path: path, rev: rev }) + }); + view.model.fetch(); this.navigate('/revision/' + path + '/' + rev); }, showDiffWithPrevious: function(path, rev) { - var diff_view = new PageDiffView({ id: path, rev1: rev, el: $('#app') }); + var view = new PageDiffView({ + el: $('#app'), + rev1: rev, + model: new PageDiffModel({ path: path, rev1: rev }) + }); + view.model.fetch(); this.navigate('/diff/c/' + path + '/' + rev); }, showDiff: function(path, rev1, rev2) { - var diff_view = new PageDiffView({ id: path, rev1: rev1, rev2: rev2, el: $('#app') }); + var view = new PageDiffView({ + el: $('#app'), + rev1: rev1, + rev2: rev2, + model: new PageDiffModel({ path: path, rev1: rev1, rev2: rev2 }) + }); + view.model.fetch(); this.navigate('/diff/r/' + path + '/' + rev1 + '/' + rev2); + }, + showSearchResults: function(query) { + if (query === '') { + query = this.getQueryVariable('q'); + } + var view = new WikiSearchView({ + el: $('#app'), + model: new WikiSearchModel() + }); + view.model.execute(query); + this.navigate('/search/' + query); + }, + getQueryVariable: function(variable) { + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + if (pair[0] == variable) { + return unescape(pair[1]); + } + } + return false; } }); diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/diff-page.html --- a/wikked/static/tpl/diff-page.html Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/tpl/diff-page.html Sat Dec 29 18:21:44 2012 -0800 @@ -1,29 +1,15 @@ -
- -
-
-

- {{meta.title}} - Diff - {{#if disp_rev2}} - {{disp_rev1}} to {{disp_rev2}} - {{else}} - change {{disp_rev1}} - {{/if}} - -

-
{{{diff}}}
-
-
-
- +
+
+

+ {{meta.title}} + Diff: + {{#if disp_rev2}} + {{disp_rev1}} to {{disp_rev2}} + {{else}} + change {{disp_rev1}} + {{/if}} + +

+
{{{diff}}}
-
+ diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/edit-page.html --- a/wikked/static/tpl/edit-page.html Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/tpl/edit-page.html Sat Dec 29 18:21:44 2012 -0800 @@ -1,94 +1,87 @@ -
- -
-
-

{{meta.title}} Editing

-
-
-
-
-
-
- -
-
+
+
+

{{meta.title}} Editing

+ +
+
+
+
+
+ +
-
-
-

Preview

-
-
-
-
-
-
- - -
-
- - -
-
-
-
- - Cancel +
+
+
+

Preview

+
- -
-
- - - - + + + -
+ }); + $('.wmd-preview-wrapper>h3>a').on('click', function(e) { + $('#wmd-preview').fadeToggle(function() { + var icon = $('.wmd-preview-wrapper>h3>a i'); + if (icon.hasClass('icon-minus')) { + icon.removeClass('icon-minus'); + icon.addClass('icon-plus'); + } else { + icon.removeClass('icon-plus'); + icon.addClass('icon-minus'); + } + }); + }); + })(); + diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/footer.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/tpl/footer.html Sat Dec 29 18:21:44 2012 -0800 @@ -0,0 +1,8 @@ +
+
+ {{#each url_extras}} + {{name}} + {{/each}} + +
+
diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/history-page.html --- a/wikked/static/tpl/history-page.html Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/tpl/history-page.html Sat Dec 29 18:21:44 2012 -0800 @@ -1,43 +1,34 @@ -
- -
-
-

{{meta.title}} History

-

Here's the revision log for {{meta.title}}.

-
- - - - - - - - - - - - {{#eachr history}} - - - - - - - - {{/eachr}} - -
Rev.DateAuthorComment
{{index}}{{timestamp}}{{author}}{{description}} - - - with previous -
-
-
-
-
+
+
+

{{meta.title}} History

+

Here's the revision log for {{meta.title}}.

+
+ + + + + + + + + + + + {{#eachr history}} + + + + + + + + {{/eachr}} + +
Rev.DateAuthorComment
{{index}}{{timestamp}}{{author}}{{description}} + + + with previous +
+
-
+
diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/inlinks-page.html --- a/wikked/static/tpl/inlinks-page.html Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/tpl/inlinks-page.html Sat Dec 29 18:21:44 2012 -0800 @@ -1,24 +1,17 @@ -
- -
-
-

{{meta.title}} Incoming Links

-

The following pages link to {{meta.title}}:

-
    - {{#each in_links}} -
  • - {{#if missing}} - {{url}} - {{else}} - {{meta.title}} - {{/if}} -
  • - {{/each}} -
-
-
-
+
+
+

{{meta.title}} Incoming Links

+

The following pages link to {{meta.title}}:

+
    + {{#each in_links}} +
  • + {{#if missing}} + {{url}} + {{else}} + {{meta.title}} + {{/if}} +
  • + {{/each}} +
+
+
diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/nav.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/tpl/nav.html Sat Dec 29 18:21:44 2012 -0800 @@ -0,0 +1,11 @@ + diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/read-page.html --- a/wikked/static/tpl/read-page.html Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/tpl/read-page.html Sat Dec 29 18:21:44 2012 -0800 @@ -1,22 +1,6 @@ -
- -
-
-

{{meta.title}}

- {{content}} -
-
-
- +
+
+

{{meta.title}}

+ {{content}}
-
+ diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/revision-page.html --- a/wikked/static/tpl/revision-page.html Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/static/tpl/revision-page.html Sat Dec 29 18:21:44 2012 -0800 @@ -1,20 +1,6 @@ -
- -
-
-

{{meta.title}} Revision {{disp_rev}}

-
{{text}}
-
-
-
- +
+
+

{{meta.title}} Revision: {{disp_rev}}

+
{{text}}
-
+ diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/static/tpl/search-results.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/tpl/search-results.html Sat Dec 29 18:21:44 2012 -0800 @@ -0,0 +1,10 @@ +
+
+

Search Results

+ +
+
diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/templates/index.html --- a/wikked/templates/index.html Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/templates/index.html Sat Dec 29 18:21:44 2012 -0800 @@ -8,7 +8,8 @@ -
{% block app %}{% endblock %}
+
+
diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/views.py --- a/wikked/views.py Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/views.py Sat Dec 29 18:21:44 2012 -0800 @@ -35,6 +35,16 @@ return render_template('index.html', cache_bust=('?%d' % time.time())) +@app.route('/read/') +def read(): + return render_template('index.html', cache_bust=('?%d' % time.time())) + + +@app.route('/search') +def search(): + return render_template('index.html', cache_bust=('?%d' % time.time())) + + @app.route('/api/list') def api_list_all_pages(): return list_pages(None) @@ -84,7 +94,7 @@ formatter = get_formatter_by_name('html') diff = highlight(diff, lexer, formatter) if rev2 is None: - meta = dict(page.all_meta, change=rev) + meta = dict(page.all_meta, change=rev1) else: meta = dict(page.all_meta, rev1=rev1, rev2=rev2) result = { 'path': url, 'meta': meta, 'diff': diff } @@ -207,3 +217,10 @@ result = { 'url': url, 'meta': page.all_meta, 'history': hist_data } return jsonify(result) +@app.route('/api/search') +def api_search(): + query = request.args.get('q') + hits = wiki.index.search(query) + result = { 'query': query, 'hits': hits } + return jsonify(result) + diff -r 6ac0b74a57f7 -r aa6951805e1a wikked/wiki.py --- a/wikked/wiki.py Sat Dec 22 22:33:11 2012 -0800 +++ b/wikked/wiki.py Sat Dec 29 18:21:44 2012 -0800 @@ -8,6 +8,7 @@ from fs import FileSystem from cache import Cache from scm import MercurialSourceControl +from indexer import WhooshWikiIndex class FormatterNotFound(Exception): @@ -73,7 +74,7 @@ return text def _formatWikiLink(self, ctx, display, url): - slug = re.sub(r'[^A-Za-z0-9_\.\-\(\)/]+', '-', url.lower()) + slug = Page.title_to_url(url) ctx.out_links.append(slug) css_class = 'wiki-link' @@ -188,6 +189,10 @@ self.wiki.logger.debug("Updated cached %s for page '%s'." % (cache_key, self.url)) self.wiki.cache.write(cache_key, data) + @staticmethod + def title_to_url(title): + return re.sub(r'[^A-Za-z0-9_\.\-\(\)/]+', '-', title.lower()) + class Wiki(object): def __init__(self, root=None, logger=None): @@ -202,17 +207,23 @@ self.fs = FileSystem(root) self.scm = MercurialSourceControl(root, self.logger) self.cache = None #Cache(os.path.join(root, '.cache')) + self.index = WhooshWikiIndex(os.path.join(root, '.index'), logger=self.logger) if self.cache is not None: self.fs.excluded.append(self.cache.cache_dir) if self.scm is not None: self.fs.excluded += self.scm.getSpecialDirs() + if self.index is not None: + self.fs.excluded.append(self.index.store_dir) self.formatters = { markdown.markdown: [ 'md', 'mdown', 'markdown' ], self._passthrough: [ 'txt', 'text', 'html' ] } + if self.index is not None: + self.index.update(self.getPages()) + @property def root(self): return self.fs.root @@ -249,6 +260,9 @@ } self.scm.commit([ path ], commit_meta) + if self.index is not None: + self.index.update([ self.getPage(url) ]) + def pageExists(self, url): return self.fs.pageExists(url)