Mercurial > wikked
changeset 48:9658edea3121
Now using RequireJS' "text" extension to load all HTML templates.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sat, 26 Jan 2013 22:17:51 -0800 |
parents | 86ee1b696070 |
children | fb6ae96756c1 |
files | wikked/static/js/text.js wikked/static/js/wikked.js wikked/static/js/wikked/app.js wikked/static/js/wikked/client.js wikked/static/js/wikked/models.js wikked/static/js/wikked/views.js wikked/static/tpl/inlinks-page.html wikked/static/tpl/login.html wikked/templates/index.html |
diffstat | 9 files changed, 573 insertions(+), 180 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wikked/static/js/text.js Sat Jan 26 22:17:51 2013 -0800 @@ -0,0 +1,323 @@ +/** + * @license RequireJS text 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/text for details + */ +/*jslint regexp: true */ +/*global require: false, XMLHttpRequest: false, ActiveXObject: false, + define: false, window: false, process: false, Packages: false, + java: false, location: false */ + +define(['module'], function (module) { + 'use strict'; + + var text, fs, + progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], + xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, + bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im, + hasLocation = typeof location !== 'undefined' && location.href, + defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), + defaultHostName = hasLocation && location.hostname, + defaultPort = hasLocation && (location.port || undefined), + buildMap = [], + masterConfig = (module.config && module.config()) || {}; + + text = { + version: '2.0.4', + + strip: function (content) { + //Strips <?xml ...?> declarations so that external SVG and XML + //documents can be added to a document without worry. Also, if the string + //is an HTML document, only the part inside the body tag is returned. + if (content) { + content = content.replace(xmlRegExp, ""); + var matches = content.match(bodyRegExp); + if (matches) { + content = matches[1]; + } + } else { + content = ""; + } + return content; + }, + + jsEscape: function (content) { + return content.replace(/(['\\])/g, '\\$1') + .replace(/[\f]/g, "\\f") + .replace(/[\b]/g, "\\b") + .replace(/[\n]/g, "\\n") + .replace(/[\t]/g, "\\t") + .replace(/[\r]/g, "\\r") + .replace(/[\u2028]/g, "\\u2028") + .replace(/[\u2029]/g, "\\u2029"); + }, + + createXhr: masterConfig.createXhr || function () { + //Would love to dump the ActiveX crap in here. Need IE 6 to die first. + var xhr, i, progId; + if (typeof XMLHttpRequest !== "undefined") { + return new XMLHttpRequest(); + } else if (typeof ActiveXObject !== "undefined") { + for (i = 0; i < 3; i += 1) { + progId = progIds[i]; + try { + xhr = new ActiveXObject(progId); + } catch (e) {} + + if (xhr) { + progIds = [progId]; // so faster next time + break; + } + } + } + + return xhr; + }, + + /** + * Parses a resource name into its component parts. Resource names + * look like: module/name.ext!strip, where the !strip part is + * optional. + * @param {String} name the resource name + * @returns {Object} with properties "moduleName", "ext" and "strip" + * where strip is a boolean. + */ + parseName: function (name) { + var modName, ext, temp, + strip = false, + index = name.indexOf("."), + isRelative = name.indexOf('./') === 0 || + name.indexOf('../') === 0; + + if (index !== -1 && (!isRelative || index > 1)) { + modName = name.substring(0, index); + ext = name.substring(index + 1, name.length); + } else { + modName = name; + } + + temp = ext || modName; + index = temp.indexOf("!"); + if (index !== -1) { + //Pull off the strip arg. + strip = temp.substring(index + 1) === "strip"; + temp = temp.substring(0, index); + if (ext) { + ext = temp; + } else { + modName = temp; + } + } + + return { + moduleName: modName, + ext: ext, + strip: strip + }; + }, + + xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, + + /** + * Is an URL on another domain. Only works for browser use, returns + * false in non-browser environments. Only used to know if an + * optimized .js version of a text resource should be loaded + * instead. + * @param {String} url + * @returns Boolean + */ + useXhr: function (url, protocol, hostname, port) { + var uProtocol, uHostName, uPort, + match = text.xdRegExp.exec(url); + if (!match) { + return true; + } + uProtocol = match[2]; + uHostName = match[3]; + + uHostName = uHostName.split(':'); + uPort = uHostName[1]; + uHostName = uHostName[0]; + + return (!uProtocol || uProtocol === protocol) && + (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && + ((!uPort && !uHostName) || uPort === port); + }, + + finishLoad: function (name, strip, content, onLoad) { + content = strip ? text.strip(content) : content; + if (masterConfig.isBuild) { + buildMap[name] = content; + } + onLoad(content); + }, + + load: function (name, req, onLoad, config) { + //Name has format: some.module.filext!strip + //The strip part is optional. + //if strip is present, then that means only get the string contents + //inside a body tag in an HTML string. For XML/SVG content it means + //removing the <?xml ...?> declarations so the content can be inserted + //into the current doc without problems. + + // Do not bother with the work if a build and text will + // not be inlined. + if (config.isBuild && !config.inlineText) { + onLoad(); + return; + } + + masterConfig.isBuild = config.isBuild; + + var parsed = text.parseName(name), + nonStripName = parsed.moduleName + + (parsed.ext ? '.' + parsed.ext : ''), + url = req.toUrl(nonStripName), + useXhr = (masterConfig.useXhr) || + text.useXhr; + + //Load the text. Use XHR if possible and in a browser. + if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { + text.get(url, function (content) { + text.finishLoad(name, parsed.strip, content, onLoad); + }, function (err) { + if (onLoad.error) { + onLoad.error(err); + } + }); + } else { + //Need to fetch the resource across domains. Assume + //the resource has been optimized into a JS module. Fetch + //by the module name + extension, but do not include the + //!strip part to avoid file system issues. + req([nonStripName], function (content) { + text.finishLoad(parsed.moduleName + '.' + parsed.ext, + parsed.strip, content, onLoad); + }); + } + }, + + write: function (pluginName, moduleName, write, config) { + if (buildMap.hasOwnProperty(moduleName)) { + var content = text.jsEscape(buildMap[moduleName]); + write.asModule(pluginName + "!" + moduleName, + "define(function () { return '" + + content + + "';});\n"); + } + }, + + writeFile: function (pluginName, moduleName, req, write, config) { + var parsed = text.parseName(moduleName), + extPart = parsed.ext ? '.' + parsed.ext : '', + nonStripName = parsed.moduleName + extPart, + //Use a '.js' file name so that it indicates it is a + //script that can be loaded across domains. + fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; + + //Leverage own load() method to load plugin value, but only + //write out values that do not have the strip argument, + //to avoid any potential issues with ! in file names. + text.load(nonStripName, req, function (value) { + //Use own write() method to construct full module value. + //But need to create shell that translates writeFile's + //write() to the right interface. + var textWrite = function (contents) { + return write(fileName, contents); + }; + textWrite.asModule = function (moduleName, contents) { + return write.asModule(moduleName, fileName, contents); + }; + + text.write(pluginName, nonStripName, textWrite, config); + }, config); + } + }; + + if (masterConfig.env === 'node' || (!masterConfig.env && + typeof process !== "undefined" && + process.versions && + !!process.versions.node)) { + //Using special require.nodeRequire, something added by r.js. + fs = require.nodeRequire('fs'); + + text.get = function (url, callback) { + var file = fs.readFileSync(url, 'utf8'); + //Remove BOM (Byte Mark Order) from utf8 files if it is there. + if (file.indexOf('\uFEFF') === 0) { + file = file.substring(1); + } + callback(file); + }; + } else if (masterConfig.env === 'xhr' || (!masterConfig.env && + text.createXhr())) { + text.get = function (url, callback, errback) { + var xhr = text.createXhr(); + xhr.open('GET', url, true); + + //Allow overrides specified in config + if (masterConfig.onXhr) { + masterConfig.onXhr(xhr, url); + } + + xhr.onreadystatechange = function (evt) { + var status, err; + //Do not explicitly handle errors, those should be + //visible via console output in the browser. + if (xhr.readyState === 4) { + status = xhr.status; + if (status > 399 && status < 600) { + //An http 4xx or 5xx error. Signal an error. + err = new Error(url + ' HTTP status: ' + status); + err.xhr = xhr; + errback(err); + } else { + callback(xhr.responseText); + } + } + }; + xhr.send(null); + }; + } else if (masterConfig.env === 'rhino' || (!masterConfig.env && + typeof Packages !== 'undefined' && typeof java !== 'undefined')) { + //Why Java, why is this so awkward? + text.get = function (url, callback) { + var stringBuffer, line, + encoding = "utf-8", + file = new java.io.File(url), + lineSeparator = java.lang.System.getProperty("line.separator"), + input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), + content = ''; + try { + stringBuffer = new java.lang.StringBuffer(); + line = input.readLine(); + + // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 + // http://www.unicode.org/faq/utf_bom.html + + // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 + if (line && line.length() && line.charAt(0) === 0xfeff) { + // Eat the BOM, since we've already found the encoding on this file, + // and we plan to concatenating this buffer with others; the BOM should + // only appear at the top of a file. + line = line.substring(1); + } + + stringBuffer.append(line); + + while ((line = input.readLine()) !== null) { + stringBuffer.append(lineSeparator); + stringBuffer.append(line); + } + //Make sure we return a JavaScript string and not a Java string. + content = String(stringBuffer.toString()); //String + } finally { + input.close(); + } + callback(content); + }; + } + + return text; +});
--- a/wikked/static/js/wikked.js Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/static/js/wikked.js Sat Jan 26 22:17:51 2013 -0800 @@ -44,9 +44,10 @@ 'wikked/app', 'wikked/handlebars', 'backbone', - 'bootstrap' + 'bootstrap', + 'text' ], - function(app, hb, Backbone) { + function(app, hb, Backbone, textExtension) { var router = new app.Router(); Backbone.history.start();//{ pushState: true });
--- a/wikked/static/js/wikked/app.js Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/static/js/wikked/app.js Sat Jan 26 22:17:51 2013 -0800 @@ -32,8 +32,8 @@ readPage: function(path) { path_clean = this.stripQuery(path); no_redirect = this.getQueryVariable('no_redirect', path); - var view = new Views.PageReadView({ - el: $('#app'), + var view = new Views.PageReadView({ + el: $('#app'), model: new Models.PageReadModel({ path: path_clean }) }); if (no_redirect) { @@ -47,7 +47,7 @@ this.readPage('main-page'); }, editPage: function(path) { - var view = new Views.PageEditView({ + var view = new Views.PageEditView({ el: $('#app'), model: new Models.PageEditModel({ path: path }) }); @@ -76,7 +76,7 @@ readPageRevision: function(path, rev) { var view = new Views.PageRevisionView({ el: $('#app'), - rev: rev, + rev: rev, model: new Models.PageRevisionModel({ path: path, rev: rev }) }); view.model.setApp(this); @@ -122,6 +122,7 @@ model: new Models.LoginModel() }); view.model.setApp(this); + view.render(); this.navigate('/login'); }, doLogout: function() { @@ -140,10 +141,24 @@ model: new Models.SpecialPagesModel() }); view.model.setApp(this); + view.render(); this.navigate('/special'); }, showSpecialPage: function(page) { - var view = new Views.GenericSpecialPageView({ + var viewType = false; + switch (page) { + case "changes": + viewType = Views.SpecialChangesView; + break; + case "orphans": + viewType = Views.SpecialOrphansView; + break; + } + if (viewType === false) { + console.error("Unsupported special page: ", page); + return; + } + var view = new viewType({ el: $('#app'), model: new Models.GenericSpecialPageModel({ page: page }) });
--- a/wikked/static/js/wikked/client.js Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/static/js/wikked/client.js Sat Jan 26 22:17:51 2013 -0800 @@ -132,7 +132,7 @@ if ( directory === ".." && directories.length && prev !== ".." && prev !== "." && prev !== undefined && (prev !== "" || keepBlanks)) { directories.pop(); - prev = directories.slice(-1)[0] + prev = directories.slice(-1)[0]; } else { if (prev === ".") directories.pop(); directories.push(directory); @@ -158,7 +158,7 @@ if (link[0] == '/') { abs_link = link.substring(1); } else { - raw_abs_link = this.baseUrl + link + raw_abs_link = this.baseUrl + link; abs_link = normalizeArray(raw_abs_link.split('/')).join('/'); } ansi_link = removeDiacritics(abs_link); @@ -166,7 +166,7 @@ }, formatText: function(text) { var $f = this; - text = text.replace(/^\[\[((__|\+)?[a-zA-Z][a-zA-Z0-9_\-]+)\:\s*(.*)\]\]\s*$/gm, function(m, a, b, c) { + text = text.replace(/^\{\{((__|\+)?[a-zA-Z][a-zA-Z0-9_\-]+)\:\s*(.*)\}\}\s*$/gm, function(m, a, b, c) { if (!c) { c = 'true'; }
--- a/wikked/static/js/wikked/models.js Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/static/js/wikked/models.js Sat Jan 26 22:17:51 2013 -0800 @@ -109,7 +109,7 @@ $model.app.navigate('/', { trigger: true }); }) .error(function() { - alert("Error while logging in..."); + $model.set('has_error', true); }); } }); @@ -159,11 +159,11 @@ this.set('content', new Handlebars.SafeString(text)); } }); - + var PageStateModel = exports.PageStateModel = PageModel.extend({ urlRoot: '/api/state/' }); - + var MasterPageModel = exports.MasterPageModel = PageModel.extend({ initialize: function() { this.nav = new NavigationModel({ id: this.id }); @@ -216,8 +216,14 @@ initialize: function() { PageReadModel.__super__.initialize.apply(this, arguments); this.on('change', this._onChange, this); + + // Add extra links to the footer. + var model = this; + this.footer.addExtraUrl('Pages Linking Here', function() { return '/#/inlinks/' + model.id; }, 1); + this.footer.addExtraUrl('JSON', function() { return '/api/read/' + model.id; }); }, _onChange: function() { + // Handle redirects. if (this.getMeta('redirect') && !this.get('no_redirect')) { var oldPath = this.get('path'); this.set('path', this.getMeta('redirect')); @@ -228,11 +234,6 @@ }); this.app.navigate('/read/' + this.getMeta('redirect'), { replace: true }); } - }, - _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); } });
--- a/wikked/static/js/wikked/views.js Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/static/js/wikked/views.js Sat Jan 26 22:17:51 2013 -0800 @@ -8,9 +8,31 @@ 'handlebars', './client', './models', - './util' + './util', + 'text!/tpl/read-page.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, Client, Models, Util) { + function($, _, Backbone, Handlebars, Client, Models, Util, + tplReadPage, tplEditPage, tplHistoryPage, tplRevisionPage, tplDiffPage, tplInLinksPage, + tplNav, tplFooter, tplSearchResults, tplLogin, + tplErrorNotAuthorized, tplErrorNotFound, tplErrorUnauthorizedEdit, tplStateWarning, + tplSpecialNav, tplSpecialPages, tplSpecialChanges, tplSpecialOrphans) { var exports = {}; @@ -23,24 +45,24 @@ var $view = this; this.model.on("change", function() { $view._onModelChange(); }); } + if (this.templateSource !== undefined) { + this.template = Handlebars.compile(_.result(this, 'templateSource')); + } return this; }, render: function(view) { - if (this.templateName !== undefined) { - this.renderTemplate(_.result(this, 'templateName'), this.renderCallback); + console.log("Rendering!"); + if (this.template !== undefined) { + this.renderTemplate(this.template); + if (this.renderCallback !== undefined) { + this.renderCallback(); + } } this.renderTitle(this.titleFormat); return this; }, - renderTemplate: function(tpl_name, callback) { - var $view = this; - Util.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); - } - }); + renderTemplate: function(tpl) { + this.$el.html(tpl(this.model.toJSON())); }, renderTitle: function(formatter) { var title = this.model.title(); @@ -50,100 +72,100 @@ document.title = title; }, _onModelChange: function() { + console.log("Model changed!"); this.render(); } }); _.extend(PageView, Backbone.Events); var NavigationView = exports.NavigationView = PageView.extend({ - templateName: 'nav', + templateSource: tplNav, initialize: function() { NavigationView.__super__.initialize.apply(this, arguments); - this.render(); return this; }, render: function() { - this.renderTemplate(this.templateName); + console.log("Rendering navigation!"); + this.renderTemplate(this.template); + this.origPageEl = $('#app .page'); + 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; }, - postRender: function() { - var model = this.model; - this.$('#search').submit(function(e) { - e.preventDefault(); - model.doSearch(this); - return false; - }); - var $view = this; - Util.TemplateLoader.get('search-results', function(src) { - var template = Handlebars.compile(src); - var origPageEl = $('#app .page'); - - $view.$('#search .search-query') - .on('input', function() { - var curPreviewEl = $('#app .page[class~="preview-search-results"]'); - - // Restore the original content if the query is now - // empty. Otherwise, run a search and render only the - // `.page` portion of the results page. - var query = $(this).val(); - if (query && query.length > 0) { - model.doPreviewSearch(query, function(data) { - data.is_instant = true; - var resultList = $(template(data)); - var inner = $('.page', resultList) - .addClass('preview-search-results') - .detach(); - if (origPageEl.is(':visible')) { - inner.insertAfter(origPageEl); - origPageEl.hide(); - } else { - curPreviewEl.replaceWith(inner); - } - }); - } else { - curPreviewEl.remove(); - origPageEl.show(); - } - }) - .keyup(function(e) { - if (e.keyCode == 27) { - // Clear search on `Esc`. - $(this).val('').trigger('input'); - } - }); - }); + _previewSearch: function(e) { + // Restore the original content if the query is now + // empty. Otherwise, run a search and render only the + // `.page` portion of the results page. + var origPageEl = this.origPageEl; + var curPreviewEl = $('#app .page[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 = $('.page', resultList) + .addClass('preview-search-results') + .detach(); + 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({ - templateName: 'footer', + templateSource: tplFooter, initialize: function() { FooterView.__super__.initialize.apply(this, arguments); - this.render(); return this; }, render: function() { - this.renderTemplate('footer'); - }, - postRender: function() { + console.log("Rendering footer!"); + this.renderTemplate(this.template); + return this; } }); var LoginView = exports.LoginView = PageView.extend({ - templateName: 'login', + templateSource: tplLogin, initialize: function() { LoginView.__super__.initialize.apply(this, arguments); - this.render(); return this; }, render: function() { - this.renderTemplate('login', function(view, model) { - this.$('#login').submit(function(e) { - e.preventDefault(); - model.doLogin(this); - return false; - }); - }); + this.renderTemplate(this.template); document.title = 'Login'; + return this; + }, + events: { + "submit #login": "_submitLogin" + }, + _submitLogin: function(e) { + e.preventDefault(); + this.model.doLogin(e.currentTarget); + return false; } }); @@ -152,23 +174,22 @@ MasterPageView.__super__.initialize.apply(this, arguments); this.nav = this._createNavigation(this.model.nav); this.footer = this._createFooter(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(); + renderCallback: function() { + this.$el.prepend('<div id="nav"></div>'); + this.$el.append('<div id="footer"></div>'); + this.nav.setElement(this.$('#nav')).render(); + this.footer.setElement(this.$('#footer')).render(); }, - templateName: function() { + templateSource: function() { switch (this.model.get('error_code')) { case 401: - return 'error-unauthorized'; + return tplErrorNotAuthorized; case 404: - return 'error-not-found'; + return tplErrorNotFound; default: - return _.result(this, 'defaultTemplateName'); + return _.result(this, 'defaultTemplateSource'); } }, _createNavigation: function(model) { @@ -180,12 +201,15 @@ }); var PageReadView = exports.PageReadView = MasterPageView.extend({ - defaultTemplateName: 'read-page', + defaultTemplateSource: tplReadPage, initialize: function() { + console.log("Initializing PageReadView"); PageReadView.__super__.initialize.apply(this, arguments); + this.warningTemplate = Handlebars.compile(tplStateWarning); return this; }, - renderCallback: function(view, model) { + renderCallback: function() { + console.log("Rendering PageReadView: " + this.model.get('path')); PageReadView.__super__.renderCallback.apply(this, arguments); // Replace all wiki links with proper hyperlinks using the JS app's // URL scheme. @@ -197,21 +221,38 @@ jel.attr('href', '/#/read/' + jel.attr('data-wiki-url')); }); }, - _fetchState: function() { + events: { + "click .wiki-link": "_navigateLink" + }, + _navigateLink: function(e) { + var url = $(e.currentTarget).attr('data-wiki-url'); + this.model.app.navigate('/read/' + url); + this.model.set('path', url); + this.model.fetch(); + e.preventDefault(); + return false; + }, + _lastFetchedStatePath: false, + _onModelChange: function() { + PageReadView.__super__._onModelChange.apply(this, arguments); + + // Fetch the state if the current page changed. + if (this._lastFetchedStatePath == this.model.get('path')) + return; + this._lastFetchedStatePath = this.model.get('path'); + + var stateTpl = this.warningTemplate; var stateModel = new Models.PageStateModel({ path: this.model.get('path') }); stateModel.fetch({ success: function(model, response, options) { if (model.get('state') == 'new' || model.get('state') == 'modified') { - Util.TemplateLoader.get('state-warning', function(src) { - var template = Handlebars.compile(src); - var warning = $(template(model.toJSON())); - warning.css('display', 'none'); - warning.prependTo($('#app .page')); - warning.slideDown(); - $('.dismiss', warning).click(function() { - warning.slideUp(); - return false; - }); + var warning = $(stateTpl(model.toJSON())); + warning.css('display', 'none'); + warning.prependTo($('#app .page')); + warning.slideDown(); + $('.dismiss', warning).click(function() { + warning.slideUp(); + return false; }); } } @@ -220,15 +261,13 @@ }); var PageEditView = exports.PageEditView = MasterPageView.extend({ - templateName: function() { - switch (this.model.get('error_code')) { - case 401: - return 'error-unauthorized-edit'; - default: - return 'edit-page'; + templateSource: function() { + if (this.model.get('error_code') == 401) { + return tplErrorUnauthorizedEdit; } + return tplEditPage; }, - renderCallback: function(view, model) { + renderCallback: function() { PageEditView.__super__.renderCallback.apply(this, arguments); // Create the Markdown editor. @@ -242,42 +281,43 @@ editor.run(); var editor_control = this.$('textarea#wmd-input'); editor_control.outerWidth(this.$('.wmd-input-wrapper').innerWidth()); - + }, + events: { + "mousedown .wmd-input-grip": "_inputGripMouseDown", + "click .wmd-preview-wrapper>h3>a": "_togglePreview", + "submit #page-edit": "_submitEditedPage" + }, + _inputGripMouseDown: function(e) { // Input area resizing with the grip. var last_pageY; - this.$(".wmd-input-grip") - .mousedown(function(e) { + last_pageY = e.pageY; + $('body') + .on('mousemove.wikked.editor_resize', function(e) { + editor_control.height(editor_control.height() + e.pageY - last_pageY); last_pageY = e.pageY; - $('body') - .on('mousemove.wikked.editor_resize', function(e) { - 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'); - }); + }) + .on('mouseup.wikked.editor_resize mouseleave.wikked.editor_resize', function(e) { + $('body').off('.wikked.editor_resize'); }); - + }, + _togglePreview: function(e) { // Show/hide live preview. - this.$('.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'); - } - }); + this.$('#wmd-preview').fadeToggle(function() { + var icon = this.$('.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'); + } }); - + }, + _submitEditedPage: function(e) { // Make the model submit the form. - this.$('#page-edit').submit(function(e) { - e.preventDefault(); - model.doEdit(this); - return false; - }); + e.preventDefault(); + this.model.doEdit(e.currentTarget); + return false; }, titleFormat: function(title) { return 'Editing: ' + title; @@ -285,14 +325,14 @@ }); var PageHistoryView = exports.PageHistoryView = MasterPageView.extend({ - defaultTemplateName: 'history-page', - renderCallback: function(view, model) { - PageHistoryView.__super__.renderCallback.apply(this, arguments); - this.$('#diff-page').submit(function(e) { - e.preventDefault(); - model.doDiff(this); - return false; - }); + defaultTemplateSource: tplHistoryPage, + events: { + "submit #diff-page": "_submitDiffPage" + }, + _submitDiffPage: function(e) { + e.preventDefault(); + this.model.doDiff(this); + return false; }, titleFormat: function(title) { return 'History: ' + title; @@ -300,50 +340,47 @@ }); var PageRevisionView = exports.PageRevisionView = MasterPageView.extend({ - defaultTemplateName: 'revision-page', + defaultTemplateSource: tplRevisionPage, titleFormat: function(title) { return title + ' [' + this.model.get('rev') + ']'; } }); var PageDiffView = exports.PageDiffView = MasterPageView.extend({ - defaultTemplateName: 'diff-page', + defaultTemplateSource: tplDiffPage, titleFormat: function(title) { return title + ' [' + this.model.get('rev1') + '-' + this.model.get('rev2') + ']'; } }); var IncomingLinksView = exports.IncomingLinksView = MasterPageView.extend({ - defaultTemplateName: 'inlinks-page', + defaultTemplateSource: tplInLinksPage, titleFormat: function(title) { return 'Incoming Links: ' + title; } }); var WikiSearchView = exports.WikiSearchView = MasterPageView.extend({ - defaultTemplateName: 'search-results' + defaultTemplateSource: tplSearchResults }); var SpecialNavigationView = exports.SpecialNavigationView = NavigationView.extend({ - defaultTemplateName: 'special-nav' + templateSource: tplSpecialNav }); - var SpecialPagesView = exports.SpecialPagesView = MasterPageView.extend({ - defaultTemplateName: 'special-pages', + var SpecialMasterPageView = exports.SpecialMasterPageView = MasterPageView.extend({ _createNavigation: function(model) { - model.set('show_root_link', false); + model.set('show_root_link', true); return new SpecialNavigationView({ model: model }); } }); - var GenericSpecialPageView = exports.GenericSpecialPageView = MasterPageView.extend({ - defaultTemplateName: function() { - return 'special-' + this.model.get('page'); - }, - _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) { @@ -369,10 +406,14 @@ } this.model.set('history', history); } - this.render(); + SpecialChangesView.__super__._onModelChange.apply(this, arguments); } }); + var SpecialOrphansView = exports.SpecialOrphansView = SpecialMasterPageView.extend({ + defaultTemplateSource: tplSpecialOrphans + }); + return exports; });
--- a/wikked/static/tpl/inlinks-page.html Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/static/tpl/inlinks-page.html Sat Jan 26 22:17:51 2013 -0800 @@ -8,7 +8,7 @@ {{#if missing}} <a class="wiki-link missing" href="/#/edit/{{url}}">{{url}}</a> {{else}} - <a class="wiki-link" href="/#/read/{{url}}">{{meta.title}}</a> + <a class="wiki-link" href="/#/read/{{url}}">{{title}}</a> {{/if}} </li> {{/each}}
--- a/wikked/static/tpl/login.html Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/static/tpl/login.html Sat Jan 26 22:17:51 2013 -0800 @@ -1,6 +1,16 @@ +<nav class="row"> + <div class="span12"> + <a href="/">Home</a> + </div> +</nav> <div class="row"> <div class="page span12"> <h1>Login</h1> + {{#if has_error}} + <div class="alert alert-error"> + <strong>Begone!</strong> Those credentials don't seem to work here. + </div> + {{/if}} <form id="login"> <div class="control-group input-prepend"> <label for="username" class="control-label add-on">Username: </label> @@ -19,3 +29,5 @@ </form> </div> </div> +<div class="row meta"> +</div> \ No newline at end of file
--- a/wikked/templates/index.html Fri Jan 25 22:35:56 2013 -0800 +++ b/wikked/templates/index.html Sat Jan 26 22:17:51 2013 -0800 @@ -15,6 +15,6 @@ deps: ["/js/wikked.js{{cache_bust}}"] }; </script> - <script src="/js/require.js{{cache_bust}}"></script> + <script src="/js/require.js"></script> </body> </html>