Mercurial > wikked
view static/js/wikked/views.js @ 99:58a1a7baca25
Added preliminary UI support for categories.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 21 Apr 2013 08:12:18 -0700 |
parents | 3282a3e39fb4 |
children | 827e236aa7c6 |
line wrap: on
line source
/** * Wikked views. */ define([ 'jquery', 'underscore', 'backbone', 'handlebars', 'bootstrap_tooltip', 'js/wikked/client', 'js/wikked/models', 'js/wikked/util', 'text!tpl/read-page.html', 'text!tpl/category.html', 'text!tpl/edit-page.html', 'text!tpl/history-page.html', 'text!tpl/revision-page.html', 'text!tpl/diff-page.html', 'text!tpl/inlinks-page.html', 'text!tpl/nav.html', 'text!tpl/footer.html', 'text!tpl/search-results.html', 'text!tpl/login.html', 'text!tpl/error-unauthorized.html', 'text!tpl/error-not-found.html', 'text!tpl/error-unauthorized-edit.html', 'text!tpl/state-warning.html', 'text!tpl/special-nav.html', 'text!tpl/special-pages.html', 'text!tpl/special-changes.html', 'text!tpl/special-orphans.html' ], function($, _, Backbone, Handlebars, BootstrapTooltip, Client, Models, Util, tplReadPage, tplCategory, tplEditPage, tplHistoryPage, tplRevisionPage, tplDiffPage, tplInLinksPage, tplNav, tplFooter, tplSearchResults, tplLogin, tplErrorNotAuthorized, tplErrorNotFound, tplErrorUnauthorizedEdit, tplStateWarning, tplSpecialNav, tplSpecialPages, tplSpecialChanges, tplSpecialOrphans) { 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', isMainPage: true, initialize: function() { PageView.__super__.initialize.apply(this, arguments); if (this.model !== undefined) { var $view = this; this.model.on("change", function() { $view._onModelChange(); }); } return this; }, dispose: function() { this.remove(); this.unbind(); if (this.model) { this.model.unbind(); } if (this._onDispose) { this._onDispose(); } }, render: function(view) { if (this.template === undefined && this.templateSource !== undefined) { this.template = Handlebars.compile(_.result(this, 'templateSource')); } if (this.template !== undefined) { this.renderTemplate(this.template); if (this.renderCallback !== undefined) { this.renderCallback(); } } if (this.isMainPage) { this.renderTitle(this.titleFormat); } return this; }, renderTemplate: function(tpl) { this.$el.html(tpl(this.model.toJSON())); }, renderTitle: function(formatter) { var title = this.model.title(); if (formatter !== undefined) { title = formatter.call(this, title); } document.title = title; }, _onModelChange: function() { this.render(); } }); _.extend(PageView, Backbone.Events); var NavigationView = exports.NavigationView = PageView.extend({ templateSource: tplNav, isMainPage: false, initialize: function() { NavigationView.__super__.initialize.apply(this, arguments); return this; }, render: function() { NavigationView.__super__.render.apply(this, arguments); this.origPageEl = $('.wrapper>article'); return this; }, events: { "submit #search": "_submitSearch", "input #search>.search-query": "_previewSearch", "keyup #search>.search-query": "_searchQueryChanged" }, _submitSearch: function(e) { e.preventDefault(); this.model.doSearch(e.currentTarget); return false; }, _previewSearch: function(e) { // Restore the original content if the query is now // empty. Otherwise, run a search and render only the // `article` portion of the results page. var origPageEl = this.origPageEl; var curPreviewEl = $('.wrapper>article[class~="preview-search-results"]'); var query = $(e.currentTarget).val(); if (query && query.length > 0) { var template = Handlebars.compile(tplSearchResults); this.model.doPreviewSearch(query, function(data) { data.is_instant = true; var resultList = $(template(data)); var inner = $(resultList) .addClass('preview-search-results'); if (origPageEl.is(':visible')) { inner.insertAfter(origPageEl); origPageEl.hide(); } else { curPreviewEl.replaceWith(inner); } }); } else { curPreviewEl.remove(); origPageEl.show(); } }, _searchQueryChanged: function(e) { if (e.keyCode == 27) { // Clear search on `Esc`. $(e.currentTarget).val('').trigger('input'); } } }); var FooterView = exports.FooterView = PageView.extend({ templateSource: tplFooter, isMainPage: false, initialize: function() { FooterView.__super__.initialize.apply(this, arguments); return this; }, render: function() { FooterView.__super__.render.apply(this, arguments); return this; } }); var LoginView = exports.LoginView = PageView.extend({ templateSource: tplLogin, events: { "submit #login": "_submitLogin" }, _submitLogin: function(e) { e.preventDefault(); this.model.doLogin(e.currentTarget); return false; } }); var MasterPageView = exports.MasterPageView = PageView.extend({ initialize: function() { MasterPageView.__super__.initialize.apply(this, arguments); this.nav = this._createNavigation(this.model.nav); this.footer = this._createFooter(this.model.footer); return this; }, renderCallback: function() { this.$el.prepend('<nav></nav>'); this.$el.append('<footer></footer>'); this.nav.setElement(this.$('>nav')).render(); this.footer.setElement(this.$('>footer')).render(); this.isError = (this.model.get('error_code') !== undefined); }, templateSource: function() { switch (this.model.get('error_code')) { case 401: return tplErrorNotAuthorized; case 404: return tplErrorNotFound; default: return _.result(this, 'defaultTemplateSource'); } }, _createNavigation: function(model) { return new NavigationView({ model: model }); }, _createFooter: function(model) { return new FooterView({ model: model }); } }); var PageReadView = exports.PageReadView = MasterPageView.extend({ defaultTemplateSource: tplReadPage, initialize: function() { PageReadView.__super__.initialize.apply(this, arguments); this.warningTemplate = Handlebars.compile(tplStateWarning); return this; }, renderCallback: function() { PageReadView.__super__.renderCallback.apply(this, arguments); if (this.isError) { return; } // 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('data-action') == 'edit') jel.attr('href', '/#/edit/' + jel.attr('data-wiki-url')); else jel.attr('href', '/#/read/' + jel.attr('data-wiki-url')); }); // If we've already rendered the content, and we need to display // a warning, do so now. if (this.model.get('content')) { this._showPageStateWarning(); } }, _showPageStateWarning: function() { if (this._pageState === undefined) return; var state = this._pageState.get('state'); if (state == 'new' || state == 'modified') { var warning = $(this.warningTemplate(this._pageState.toJSON())); $('[rel="tooltip"]', warning).tooltip({container:'body'}); //warning.css('display', 'none'); warning.prependTo($('.wrapper>article')); //warning.slideDown(); $('.dismiss', warning).click(function() { //warning.slideUp(); warning.remove(); return false; }); } }, _checkPageState: function() { var $view = this; var stateModel = new Models.PageStateModel({ path: this.model.get('path') }); stateModel.fetch({ success: function(model, response, options) { $view._pageState = model; // If we've already rendered the content, display // the warning, if any, now. if ($view.model && $view.model.get('content')) { $view._showPageStateWarning(); } } }); }, _firstRender: true, _onModelChange: function() { PageReadView.__super__._onModelChange.apply(this, arguments); // Fetch the state if the current page changed. if (!this.isError && (this.model.hasChanged('path') || this._firstRender)) { this._checkPageState(); this._firstRender = false; } } }); var CategoryView = exports.CategoryView = MasterPageView.extend({ defaultTemplateSource: tplCategory }); 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(/.*\//); var converter = new Markdown.Converter(); 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(); }, events: { "mousedown #wmd-input-grip": "_inputGripMouseDown", "click #wmd-preview-button": "_togglePreview", "click #wmd-full-preview-button": "_toggleFullPreview", "submit #page-edit": "_submitEditedPage" }, _inputGripMouseDown: function(e) { // Input area resizing with the grip. var last_pageY; last_pageY = e.pageY; $('body') .on('mousemove.wikked.editor_resize', function(e) { var editor_control = $('#wmd-input'); editor_control.height(editor_control.height() + e.pageY - last_pageY); last_pageY = e.pageY; }) .on('mouseup.wikked.editor_resize mouseleave.wikked.editor_resize', function(e) { $('body').off('.wikked.editor_resize'); }); }, _togglePreview: function(e) { // Show/hide live preview. var w = $('body').width() - 40; 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') .hide() .removeClass('span6'); $('#page-edit') .removeClass('row-fluid') .addClass('row'); $('#app') .removeClass('container-fluid') .addClass('container'); 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(setHeight) { var inputWidth = $('#wmd-input-wrapper').innerWidth(); 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. e.preventDefault(); this.model.doEdit(e.currentTarget); return false; }, titleFormat: function(title) { return 'Editing: ' + title; } }); var PageHistoryView = exports.PageHistoryView = MasterPageView.extend({ defaultTemplateSource: tplHistoryPage, events: { "submit #diff-page": "_submitDiffPage" }, _submitDiffPage: function(e) { e.preventDefault(); this.model.doDiff(e.currentTarget); return false; }, titleFormat: function(title) { return 'History: ' + title; } }); var PageRevisionView = exports.PageRevisionView = MasterPageView.extend({ defaultTemplateSource: tplRevisionPage, titleFormat: function(title) { return title + ' [' + this.model.get('rev') + ']'; }, events: { "submit #page-revert": "_submitPageRevert" }, _submitPageRevert: function(e) { e.preventDefault(); this.model.doRevert(e.currentTarget); return false; } }); var PageDiffView = exports.PageDiffView = MasterPageView.extend({ defaultTemplateSource: tplDiffPage, titleFormat: function(title) { return title + ' [' + this.model.get('rev1') + '-' + this.model.get('rev2') + ']'; } }); var IncomingLinksView = exports.IncomingLinksView = MasterPageView.extend({ defaultTemplateSource: tplInLinksPage, titleFormat: function(title) { return 'Incoming Links: ' + title; } }); var WikiSearchView = exports.WikiSearchView = MasterPageView.extend({ defaultTemplateSource: tplSearchResults }); var SpecialNavigationView = exports.SpecialNavigationView = NavigationView.extend({ templateSource: tplSpecialNav }); var SpecialMasterPageView = exports.SpecialMasterPageView = MasterPageView.extend({ className: 'wrapper special', _createNavigation: function(model) { model.set('show_root_link', true); return new SpecialNavigationView({ model: model }); } }); var SpecialPagesView = exports.SpecialPagesView = SpecialMasterPageView.extend({ defaultTemplateSource: tplSpecialPages }); var SpecialChangesView = exports.SpecialChangesView = SpecialMasterPageView.extend({ defaultTemplateSource: tplSpecialChanges, _onModelChange: function() { var history = this.model.get('history'); if (history) { for (var i = 0; i < history.length; ++i) { var rev = history[i]; rev.changes = []; for (var j = 0; j < rev.pages.length; ++j) { var page = rev.pages[j]; switch (page.action) { case 'edit': rev.changes.push({ is_edit: true, url: page.url }); break; case 'add': rev.changes.push({ is_add: true, url: page.url }); break; case 'delete': rev.changes.push({ is_delete: true, url: page.url }); break; } rev.pages[j] = page; } history[i] = rev; } this.model.set('history', history); } SpecialChangesView.__super__._onModelChange.apply(this, arguments); } }); var SpecialOrphansView = exports.SpecialOrphansView = SpecialMasterPageView.extend({ defaultTemplateSource: tplSpecialOrphans }); return exports; });