changeset 93:6d962a238c03

Added full preview button for page editing.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 14 Apr 2013 23:05:56 -0700
parents 30784c036ead
children 3282a3e39fb4
files static/js/wikked/views.js static/tpl/edit-page.html wikked/resources/defaults.cfg wikked/views.py
diffstat 4 files changed, 131 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/static/js/wikked/views.js	Sun Apr 14 23:05:19 2013 -0700
+++ b/static/js/wikked/views.js	Sun Apr 14 23:05:56 2013 -0700
@@ -37,6 +37,31 @@
 
     var exports = {};
 
+    // JQuery feature for watching size changes in a DOM element.
+    jQuery.fn.watch = function(id, fn) {
+        return this.each(function() {
+            var self = this;
+            var oldVal = self[id];
+            $(self).data(
+                'watch_timer',
+                setInterval(
+                    function() {
+                        if (self[id] !== oldVal) {
+                            fn.call(self, id, oldVal, self[id]);
+                            oldVal = self[id];
+                        }
+                    },
+                    100
+                )
+            );
+        });
+    };
+    jQuery.fn.unwatch = function( id ) {
+        return this.each(function() {
+            clearInterval($(this).data('watch_timer'));
+        });
+    };
+
     var PageView = exports.PageView = Backbone.View.extend({
         tagName: 'div',
         className: 'wrapper',
@@ -277,12 +302,21 @@
 
     var PageEditView = exports.PageEditView = MasterPageView.extend({
         defaultTemplateSource: tplEditPage,
+        dispose: function() {
+            PageEditView.__super__.dispose.apply(this, arguments);
+            this._removePreview();
+        },
         renderCallback: function() {
             PageEditView.__super__.renderCallback.apply(this, arguments);
             if (this.isError) {
                 return;
             }
 
+            // Cache some stuff.
+            this._ctrlInput = $('#wmd-input');
+            this._ctrlPreview = $('#wmd-preview');
+            this._originalInputHeight = this._ctrlInput.height();
+
             // Create the Markdown editor.
             var formatter = new Client.PageFormatter();
             formatter.baseUrl = this.model.get('path').match(/.*\//);
@@ -290,17 +324,21 @@
             converter.hooks.chain("preConversion", function(text) {
                 return formatter.formatText(text);
             });
+            var $view = this;
             var editor = new Markdown.Editor(converter); //TODO: pass options
+            editor.hooks.chain("onPreviewRefresh", function() {
+                $view._updateUI(true);
+            });
             editor.run();
 
             // Setup UI.
             this._updateUI();
             $('#wmd-preview-wrapper').hide();
-            this.originalHeight = $('#wmd-input').height();
         },
         events: {
             "mousedown #wmd-input-grip": "_inputGripMouseDown",
             "click #wmd-preview-button": "_togglePreview",
+            "click #wmd-full-preview-button": "_toggleFullPreview",
             "submit #page-edit": "_submitEditedPage"
         },
         _inputGripMouseDown: function(e) {
@@ -320,8 +358,32 @@
         _togglePreview: function(e) {
             // Show/hide live preview.
             var w = $('body').width() - 40;
-            if ($('#wmd-preview').is(":visible")) {
+            if (this._ctrlPreview.is(":visible")) {
+                this._removePreview();
+            } else {
+                this._addPreview();
+            }
+            e.preventDefault();
+            return false;
+        },
+        _addPreview: function() {
+            $('#app')
+                    .removeClass('container')
+                    .addClass('container-fluid');
+                $('#page-edit')
+                    .removeClass('row')
+                    .addClass('row-fluid');
                 $('#wmd-form-wrapper')
+                    .removeClass('span12')
+                    .addClass('span6');
+                $('#wmd-preview-wrapper')
+                    .show()
+                    .addClass('span6');
+
+            this._updateUI(true);
+        },
+        _removePreview: function() {
+            $('#wmd-form-wrapper')
                     .removeClass('span6')
                     .addClass('span12');
                 $('#wmd-preview-wrapper')
@@ -333,31 +395,37 @@
                 $('#app')
                     .removeClass('container-fluid')
                     .addClass('container');
-                $('#wmd-input').height(this.originalHeight);
-                this._updateUI();
-            } else {
-                $('#app')
-                    .removeClass('container')
-                    .addClass('container-fluid');
-                $('#page-edit')
-                    .removeClass('row')
-                    .addClass('row-fluid');
-                $('#wmd-form-wrapper')
-                    .removeClass('span12')
-                    .addClass('span6');
-                $('#wmd-preview-wrapper')
-                    .show()
-                    .addClass('span6');
-                $('#wmd-input').height($('#wmd-preview').height());
-                this._updateUI();
-            }
+
+            this._ctrlInput.height(this._originalInputHeight);
+            this._updateUI();
+        },
+        _toggleFullPreview: function(e) {
+            var $view = this;
+            var previewData = {
+                url: this.model.get('path'),
+                text: $('#wmd-input').val()
+            };
+            $.post('/api/preview', previewData)
+                .success(function(data) {
+                    $('#wmd-preview').html(data.text);
+                    $view._updateUI(true);
+                })
+                .error(function() {
+                    $('#wmd-preview').html("Error running preview.");
+                });
             e.preventDefault();
             return false;
         },
-        _updateUI: function() {
+        _updateUI: function(setHeight) {
             var inputWidth = $('#wmd-input-wrapper').innerWidth();
-            $('#wmd-input')
-                .outerWidth(inputWidth);
+            this._ctrlInput.outerWidth(inputWidth);
+
+            if (setHeight === true) {
+                var maxHeight = Math.max(
+                    this._ctrlPreview.height(),
+                    this._ctrlInput.height());
+                this._ctrlInput.height(maxHeight);
+            }
         },
         _submitEditedPage: function(e) {
             // Make the model submit the form.
--- a/static/tpl/edit-page.html	Sun Apr 14 23:05:19 2013 -0700
+++ b/static/tpl/edit-page.html	Sun Apr 14 23:05:56 2013 -0700
@@ -37,6 +37,7 @@
         </div>
         <div id="wmd-preview-wrapper">
             <h3>Preview</h3>
+            <button id="wmd-full-preview-button" class="btn">Full Preview</button>
             <div id="wmd-preview"></div>
         </div>
     </form>
--- a/wikked/resources/defaults.cfg	Sun Apr 14 23:05:19 2013 -0700
+++ b/wikked/resources/defaults.cfg	Sun Apr 14 23:05:56 2013 -0700
@@ -1,3 +1,4 @@
 [wiki]
 scm=hg
 auto_update=False
+default_extension=md
--- a/wikked/views.py	Sun Apr 14 23:05:19 2013 -0700
+++ b/wikked/views.py	Sun Apr 14 23:05:56 2013 -0700
@@ -1,3 +1,4 @@
+import re
 import time
 import os.path
 from flask import render_template, abort, request, g, jsonify
@@ -6,9 +7,9 @@
 from pygments.lexers import get_lexer_by_name
 from pygments.formatters import get_formatter_by_name
 from web import app, login_manager
-from wiki import Page
+from page import Page, PageData
 from fs import PageNotFoundError
-from formatter import PageFormatter
+from formatter import PageFormatter, FormattingContext
 import scm
 
 
@@ -21,6 +22,30 @@
     }
 
 
+class DummyPage(Page):
+    def __init__(self, wiki, url, text):
+        Page.__init__(self, wiki, url)
+        self._text = text
+
+    def _loadCachedData(self):
+        extension = self.wiki.config.get('wiki', 'default_extension')
+        data = PageData()
+        data.path = '__preview__.' + extension
+        data.filename = '__preview__'
+        data.extension = extension
+        data.raw_text = self._text
+
+        ctx = FormattingContext(self.url, slugify=Page.title_to_url)
+        f = PageFormatter(self.wiki)
+        data.formatted_text = f.formatText(ctx, data.raw_text)
+        data.local_meta = ctx.meta
+        data.local_links = ctx.out_links
+
+        data.title = Page.url_to_title(self.url)
+
+        return data
+
+
 def get_page_or_none(url):
     try:
         page = g.wiki.getPage(url)
@@ -333,7 +358,7 @@
             links_to_remove |= set(links)
         app.logger.debug( links_to_remove)
         orphans = [o for o in orphans if o['path'] not in links_to_remove]
-        
+
     result = {'orphans': orphans}
     return make_auth_response(result)
 
@@ -367,6 +392,16 @@
     return make_auth_response(result)
 
 
+@app.route('/api/preview', methods=['POST'])
+def api_preview():
+    url = request.form.get('url')
+    text = request.form.get('text')
+    dummy = DummyPage(g.wiki, url, text)
+
+    result = {'text': dummy.text}
+    return jsonify(result)
+
+
 @app.route('/api/admin/reindex', methods=['POST'])
 def api_admin_reindex():
     if not current_user.is_authenticated() or not current_user.is_admin():