From 499013f4fb4537a2472826e8c6f299603766f58a Mon Sep 17 00:00:00 2001 From: Lars Jung <lrsjng@gmail.com> Date: Thu, 18 Oct 2012 21:27:09 +0200 Subject: [PATCH] Initial switch to smart browsing. --- src/_h5ai/client/js/inc/core/entry.js | 12 +- src/_h5ai/client/js/inc/core/location.js | 117 +++++++++++++++--- src/_h5ai/client/js/inc/core/refresh.js | 4 +- src/_h5ai/client/js/inc/core/server.js | 4 +- src/_h5ai/client/js/inc/ext/crumb.js | 54 +++++--- src/_h5ai/client/js/inc/ext/custom.js | 34 +++-- .../client/js/inc/ext/link-hover-states.js | 19 ++- src/_h5ai/client/js/inc/main.js | 4 +- src/_h5ai/client/js/inc/model/entry.js | 24 +++- .../js/inc/view/{extended.js => items.js} | 61 +++++---- src/_h5ai/conf/options.json | 5 +- src/_h5ai/server/php/index.php.jade | 3 - 12 files changed, 244 insertions(+), 97 deletions(-) rename src/_h5ai/client/js/inc/view/{extended.js => items.js} (75%) diff --git a/src/_h5ai/client/js/inc/core/entry.js b/src/_h5ai/client/js/inc/core/entry.js index ace3e99f..03e374a4 100644 --- a/src/_h5ai/client/js/inc/core/entry.js +++ b/src/_h5ai/client/js/inc/core/entry.js @@ -1,13 +1,5 @@ -modulejs.define('core/entry', ['_', 'config', 'model/entry'], function (_, config, Entry) { +modulejs.define('core/entry', ['core/location'], function (location) { - _.each(config.entries || [], function (entry) { - - Entry.get(entry.absHref, entry.time, entry.size, entry.status, entry.content); - }); - - var entry = Entry.get(); - entry.status = '=h5ai='; - - return entry; + return location.getItem(); }); diff --git a/src/_h5ai/client/js/inc/core/location.js b/src/_h5ai/client/js/inc/core/location.js index bea9577b..8a870525 100644 --- a/src/_h5ai/client/js/inc/core/location.js +++ b/src/_h5ai/client/js/inc/core/location.js @@ -1,7 +1,13 @@ -modulejs.define('core/location', [], function () { +modulejs.define('core/location', ['_', 'modernizr', 'core/settings', 'core/event', 'core/notify'], function (_, modernizr, allsettings, event, notify) { - var doc = document, + var settings = _.extend({ + smartBrowsing: false + }, allsettings.view), + + doc = document, + + history = settings.smartBrowsing && modernizr.history ? window.history : null, forceEncoding = function (href) { @@ -17,36 +23,109 @@ modulejs.define('core/location', [], function () { .replace(/\=/g, '%3D'); }, - absHref = (function () { + reUriToPathname = /^.*:\/\/[^\/]*|[^\/]*$/g, + uriToPathname = function (uri) { - var rePrePathname = /.*:\/\/[^\/]*/, - rePostPathname = /[^\/]*$/, + return uri.replace(reUriToPathname, ''); + }, - uriToPathname = function (uri) { + hrefsAreDecoded = (function () { - return uri.replace(rePrePathname, '').replace(rePostPathname, ''); - }, - - testpathname = '/a b', - a = doc.createElement('a'), - isDecoded, location; + var testpathname = '/a b', + a = doc.createElement('a'); a.href = testpathname; - isDecoded = uriToPathname(a.href) === testpathname; + return uriToPathname(a.href) === testpathname; + }()), - a.href = doc.location.href; + encodedHref = function (href) { + + var a = doc.createElement('a'), + location; + + a.href = href; location = uriToPathname(a.href); - if (isDecoded) { + if (hrefsAreDecoded) { location = encodeURIComponent(location).replace(/%2F/ig, '/'); } return forceEncoding(location); - }()); + }; + + + var absHref = null, + + getDomain = function () { + + return doc.domain; + }, + + getAbsHref = function () { + + return absHref; + }, + + getItem = function () { + + return modulejs.require('model/entry').get(absHref); + }, + + setLocation = function (newAbsHref, keepBrowserUrl) { + + newAbsHref = encodedHref(newAbsHref); + if (absHref !== newAbsHref) { + absHref = newAbsHref; + event.pub('location.changed', absHref); + + notify.set('loading...'); + modulejs.require('core/refresh')(function () { notify.set(); }); + + if (history) { + if (keepBrowserUrl) { + history.replaceState({absHref: absHref}, '', absHref); + } else { + history.pushState({absHref: absHref}, '', absHref); + } + } + } + }, + + setLink = function ($el, item) { + + $el.attr('href', item.absHref); + + if (history && item.isFolder() && item.status === '=h5ai=') { + $el.on('click', function () { + + setLocation(item.absHref); + return false; + }); + } + + if (item.status !== '=h5ai=') { + $el.attr('target', '_blank'); + } + }; + + + if (history) { + window.onpopstate = function (e) { + + if (e.state && e.state.absHref) { + setLocation(e.state.absHref, true); + } + }; + } + return { - domain: doc.domain, - absHref: absHref, - forceEncoding: forceEncoding + forceEncoding: forceEncoding, + encodedHref: encodedHref, + getDomain: getDomain, + getAbsHref: getAbsHref, + getItem: getItem, + setLocation: setLocation, + setLink: setLink }; }); diff --git a/src/_h5ai/client/js/inc/core/refresh.js b/src/_h5ai/client/js/inc/core/refresh.js index 23d60b84..0ac66bcf 100644 --- a/src/_h5ai/client/js/inc/core/refresh.js +++ b/src/_h5ai/client/js/inc/core/refresh.js @@ -1,5 +1,5 @@ -modulejs.define('core/refresh', ['_', 'core/server', 'model/entry'], function (_, server, Entry) { +modulejs.define('core/refresh', ['_', 'config', 'core/server', 'model/entry', 'core/location'], function (_, config, server, Entry, location) { var parseJson = function (entry, json) { @@ -20,7 +20,7 @@ modulejs.define('core/refresh', ['_', 'core/server', 'model/entry'], function (_ refresh = function (callback) { - var entry = Entry.get(); + var entry = Entry.get(location.getAbsHref()); server.request({action: 'get', entries: true, entriesHref: entry.absHref, entriesWhat: 1}, function (json) { diff --git a/src/_h5ai/client/js/inc/core/server.js b/src/_h5ai/client/js/inc/core/server.js index 759b81bb..3ca818f6 100644 --- a/src/_h5ai/client/js/inc/core/server.js +++ b/src/_h5ai/client/js/inc/core/server.js @@ -1,5 +1,5 @@ -modulejs.define('core/server', ['$', '_', 'config'], function ($, _, config) { +modulejs.define('core/server', ['$', '_', 'config', 'core/location'], function ($, _, config, location) { var server = _.extend({}, config.server, { @@ -7,7 +7,7 @@ modulejs.define('core/server', ['$', '_', 'config'], function ($, _, config) { if (server.api) { $.ajax({ - url: '.', + url: location.getAbsHref(), data: data, type: 'POST', dataType: 'json', diff --git a/src/_h5ai/client/js/inc/ext/crumb.js b/src/_h5ai/client/js/inc/ext/crumb.js index da128980..72ebfbd3 100644 --- a/src/_h5ai/client/js/inc/ext/crumb.js +++ b/src/_h5ai/client/js/inc/ext/crumb.js @@ -1,5 +1,5 @@ -modulejs.define('ext/crumb', ['_', '$', 'core/settings', 'core/resource', 'core/event', 'core/entry'], function (_, $, allsettings, resource, event, entry) { +modulejs.define('ext/crumb', ['_', '$', 'core/settings', 'core/resource', 'core/event', 'core/location'], function (_, $, allsettings, resource, event, location) { var settings = _.extend({ enabled: false @@ -26,11 +26,11 @@ modulejs.define('ext/crumb', ['_', '$', 'core/settings', 'core/resource', 'core/ $html .addClass(entry.isFolder() ? 'folder' : 'file') + .data('item', entry) .data('status', entry.status); - $a - .attr('href', entry.absHref) - .find('span').text(entry.label).end(); + location.setLink($a, entry); + $a.find('span').text(entry.label).end(); if (entry.isDomain()) { $html.addClass('domain'); @@ -69,25 +69,49 @@ modulejs.define('ext/crumb', ['_', '$', 'core/settings', 'core/resource', 'core/ } }, - // creates the complete crumb from entry down to the root - init = function (entry) { + onLocationChanged = function (item) { + + var crumb = item.getCrumb(), + $ul = $('#navbar'), + found = false; + + $ul.find('.crumb').each(function () { + + var $html = $(this); + if ($html.data('item') === item) { + found = true; + $html.addClass('current'); + } else { + $html.removeClass('current'); + } + }); + + if (!found) { + $ul.find('.crumb').remove(); + _.each(crumb, function (e) { + + $ul.append(update(e)); + }); + } + }, + + init = function () { if (!settings.enabled) { return; } - var crumb = entry.getCrumb(), - $ul = $('#navbar'); - - _.each(crumb, function (e) { - - $ul.append(update(e)); - }); - // event.sub('entry.created', onContentChanged); // event.sub('entry.removed', onContentChanged); event.sub('entry.changed', onContentChanged); + + event.sub('location.changed', function () { + + onLocationChanged(location.getItem()); + }); + + onLocationChanged(location.getItem()); }; - init(entry); + init(); }); diff --git a/src/_h5ai/client/js/inc/ext/custom.js b/src/_h5ai/client/js/inc/ext/custom.js index 5da2495b..649fcc70 100644 --- a/src/_h5ai/client/js/inc/ext/custom.js +++ b/src/_h5ai/client/js/inc/ext/custom.js @@ -1,5 +1,5 @@ -modulejs.define('ext/custom', ['_', '$', 'core/settings', 'core/server'], function (_, $, allsettings, server) { +modulejs.define('ext/custom', ['_', '$', 'core/settings', 'core/server', 'core/event'], function (_, $, allsettings, server, event) { var settings = _.extend({ enabled: false, @@ -7,23 +7,35 @@ modulejs.define('ext/custom', ['_', '$', 'core/settings', 'core/server'], functi footer: '_h5ai.footer.html' }, allsettings.custom), + onLocationChanged = function () { + + $('#content-header, #content-footer').stop(true, true).slideUp(200); + + server.request({action: 'get', custom: true}, function (response) { + + if (response) { + if (response.custom.header) { + $('#content-header').html(response.custom.header).stop(true, true).slideDown(400); + } + if (response.custom.footer) { + $('#content-footer').html(response.custom.footer).stop(true, true).slideDown(400); + } + } + }); + }, + init = function () { if (!settings.enabled) { return; } - server.request({action: 'get', custom: true}, function (response) { + $('<div id="content-header"/>').hide().prependTo('#content'); + $('<div id="content-footer"/>').hide().appendTo('#content'); - if (response) { - if (response.custom.header) { - $('<div id="content-header">' + response.custom.header + '</div>').prependTo('#content'); - } - if (response.custom.footer) { - $('<div id="content-footer">' + response.custom.footer + '</div>').appendTo('#content'); - } - } - }); + event.sub('location.changed', onLocationChanged); + + onLocationChanged(); }; init(); diff --git a/src/_h5ai/client/js/inc/ext/link-hover-states.js b/src/_h5ai/client/js/inc/ext/link-hover-states.js index f3a84e0c..ee4eb910 100644 --- a/src/_h5ai/client/js/inc/ext/link-hover-states.js +++ b/src/_h5ai/client/js/inc/ext/link-hover-states.js @@ -1,5 +1,5 @@ -modulejs.define('ext/link-hover-states', ['_', '$', 'core/settings'], function (_, $, allsettings) { +modulejs.define('ext/link-hover-states', ['_', '$', 'core/settings', 'core/event'], function (_, $, allsettings, event) { var settings = _.extend({ enabled: false @@ -29,13 +29,22 @@ modulejs.define('ext/link-hover-states', ['_', '$', 'core/settings'], function ( selectLinks(href).removeClass('hover'); }, + onLocationChanged = function () { + + $('.hover').removeClass('hover'); + }, + init = function () { - if (settings.enabled) { - $('body') - .on('mouseenter', selector, onMouseEnter) - .on('mouseleave', selector, onMouseLeave); + if (!settings.enabled) { + return; } + + $('body') + .on('mouseenter', selector, onMouseEnter) + .on('mouseleave', selector, onMouseLeave); + + event.sub('location.changed', onLocationChanged); }; init(); diff --git a/src/_h5ai/client/js/inc/main.js b/src/_h5ai/client/js/inc/main.js index ed29852e..04e7a1cd 100644 --- a/src/_h5ai/client/js/inc/main.js +++ b/src/_h5ai/client/js/inc/main.js @@ -3,10 +3,12 @@ modulejs.define('main', ['_', 'core/event'], function (_, event) { event.pub('beforeView'); - modulejs.require('view/extended'); + modulejs.require('view/items'); modulejs.require('view/spacing'); modulejs.require('view/viewmode'); + modulejs.require('core/location').setLocation(document.location.href, true); + event.pub('beforeExt'); _.each(modulejs.state(), function (state, id) { diff --git a/src/_h5ai/client/js/inc/model/entry.js b/src/_h5ai/client/js/inc/model/entry.js index 21ebf124..d1e3cb6e 100644 --- a/src/_h5ai/client/js/inc/model/entry.js +++ b/src/_h5ai/client/js/inc/model/entry.js @@ -1,5 +1,6 @@ -modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/settings', 'core/location', 'core/server'], function ($, _, types, event, settings, location, server) { +modulejs.define('model/entry', ['$', '_', 'config', 'core/types', 'core/event', 'core/settings', 'core/server', 'core/location'], function ($, _, config, types, event, settings, server, location) { + var reEndsWithSlash = /\/$/, @@ -44,7 +45,7 @@ modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/sett getEntry = function (absHref, time, size, status, isContentFetched) { - absHref = location.forceEncoding(absHref || location.absHref); + absHref = location.forceEncoding(absHref); if (!startsWith(absHref, settings.rootAbsHref)) { return null; @@ -88,7 +89,7 @@ modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/sett removeEntry = function (absHref) { - absHref = location.forceEncoding(absHref || location.absHref); + absHref = location.forceEncoding(absHref); var self = cache[absHref]; @@ -128,6 +129,14 @@ modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/sett callback(self); }); } + }, + + init = function () { + + _.each(config.entries || [], function (entry) { + + getEntry(entry.absHref, entry.time, entry.size, entry.status, entry.content); + }); }; @@ -142,7 +151,7 @@ modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/sett this.absHref = absHref; this.type = types.getType(absHref); - this.label = createLabel(absHref === '/' ? location.domain : split.name); + this.label = createLabel(absHref === '/' ? location.getDomain() : split.name); this.time = null; this.size = null; this.parent = null; @@ -167,7 +176,7 @@ modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/sett isCurrentFolder: function () { - return this.absHref === location.absHref; + return this.absHref === location.getAbsHref(); }, isInCurrentFolder: function () { @@ -177,7 +186,7 @@ modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/sett isCurrentParentFolder: function () { - return this === getEntry().parent; + return this === getEntry(location.getAbsHref()).parent; }, isDomain: function () { @@ -259,6 +268,9 @@ modulejs.define('model/entry', ['$', '_', 'core/types', 'core/event', 'core/sett } }); + + init(); + return { get: getEntry, remove: removeEntry diff --git a/src/_h5ai/client/js/inc/view/extended.js b/src/_h5ai/client/js/inc/view/items.js similarity index 75% rename from src/_h5ai/client/js/inc/view/extended.js rename to src/_h5ai/client/js/inc/view/items.js index aecbe811..506bd301 100644 --- a/src/_h5ai/client/js/inc/view/extended.js +++ b/src/_h5ai/client/js/inc/view/items.js @@ -1,5 +1,5 @@ -modulejs.define('view/extended', ['_', '$', 'core/settings', 'core/resource', 'core/format', 'core/event', 'core/entry'], function (_, $, allsettings, resource, format, event, entry) { +modulejs.define('view/items', ['_', '$', 'core/settings', 'core/resource', 'core/format', 'core/event', 'core/location'], function (_, $, allsettings, resource, format, event, location) { var settings = _.extend({ setParentFolderLabels: false, @@ -25,6 +25,7 @@ modulejs.define('view/extended', ['_', '$', 'core/settings', 'core/resource', 'c '</li>' + '</ul>', emptyTemplate = '<div class="empty l10n-empty"/>', + contentTemplate = '<div id="content"><div id="extended" class="clearfix"/></div>', // updates this single entry update = function (entry, force) { @@ -40,14 +41,14 @@ modulejs.define('view/extended', ['_', '$', 'core/settings', 'core/resource', 'c $label = $html.find('.label'), $date = $html.find('.date'), $size = $html.find('.size'); - // escapedHref = entry.absHref.replace(/'/g, "%27").replace(/"/g, "%22"); $html .addClass(entry.isFolder() ? 'folder' : 'file') .data('entry', entry) .data('status', entry.status); - $a.attr('href', entry.absHref); + location.setLink($a, entry); + $imgSmall.attr('src', resource.icon(entry.type)).attr('alt', entry.type); $imgBig.attr('src', resource.icon(entry.type, true)).attr('alt', entry.type); $label.text(entry.label); @@ -94,32 +95,42 @@ modulejs.define('view/extended', ['_', '$', 'core/settings', 'core/resource', 'c event.pub('entry.mouseleave', entry); }, - // creates the view for entry content - init = function (entry) { + onLocationChanged = function (item) { var $extended = $('#extended'), - $ul = $(listTemplate), - $emtpy = $(emptyTemplate); + $ul = $extended.find('ul'), + $empty = $extended.find('.empty'); - format.setDefaultMetric(settings.binaryPrefix); + $ul.find('.entry').remove(); - if (entry.parent) { - $ul.append(update(entry.parent)); + if (item.parent) { + $ul.append(update(item.parent)); } - _.each(entry.content, function (e) { + _.each(item.content, function (e) { $ul.append(update(e)); }); - $extended.append($ul); - $extended.append($emtpy); - - if (!entry.isEmpty()) { - $emtpy.hide(); + if (item.isEmpty()) { + $empty.show(); + } else { + $empty.hide(); } + }, + + init = function () { + + var $content = $(contentTemplate), + $extended = $content.find('#extended'), + $ul = $(listTemplate), + $emtpy = $(emptyTemplate).hide(); + + format.setDefaultMetric(settings.binaryPrefix); $extended + .append($ul) + .append($emtpy) .on('mouseenter', '.entry a', onMouseenter) .on('mouseleave', '.entry a', onMouseleave); @@ -133,24 +144,32 @@ modulejs.define('view/extended', ['_', '$', 'core/settings', 'core/resource', 'c event.sub('entry.created', function (entry) { if (entry.isInCurrentFolder() && !entry.$extended) { - update(entry, true).hide().appendTo($ul).slideDown(400); - $emtpy.slideUp(400); + $emtpy.fadeOut(100, function () { + update(entry, true).hide().appendTo($ul).fadeIn(400); + }); } }); event.sub('entry.removed', function (entry) { if (entry.isInCurrentFolder() && entry.$extended) { - entry.$extended.slideUp(400, function () { + entry.$extended.fadeOut(400, function () { entry.$extended.remove(); if (entry.parent.isEmpty()) { - $emtpy.slideDown(400); + $emtpy.fadeIn(100); } }); } }); + + event.sub('location.changed', function () { + + onLocationChanged(location.getItem()); + }); + + $content.appendTo('body'); }; - init(entry); + init(); }); diff --git a/src/_h5ai/conf/options.json b/src/_h5ai/conf/options.json index ec68c0b6..dfbef51b 100644 --- a/src/_h5ai/conf/options.json +++ b/src/_h5ai/conf/options.json @@ -38,7 +38,8 @@ Options "setParentFolderLabels": true, "binaryPrefix": false, "indexFiles": ["index.html", "index.htm", "index.php"], - "ignore": ["^\\.", "^_{{pkg.name}}"] + "ignore": ["^\\.", "^_{{pkg.name}}"], + "smartBrowsing": true }, @@ -67,7 +68,7 @@ Options in each folder. */ "custom": { - "enabled": false, + "enabled": true, "header": "_{{pkg.name}}.header.html", "footer": "_{{pkg.name}}.footer.html" }, diff --git a/src/_h5ai/server/php/index.php.jade b/src/_h5ai/server/php/index.php.jade index c9f0bd2d..8ea6cbe1 100644 --- a/src/_h5ai/server/php/index.php.jade +++ b/src/_h5ai/server/php/index.php.jade @@ -27,9 +27,6 @@ html.no-js( lang="en" ) div#topbar.clearfix ul#navbar - div#content - div#extended.clearfix - div#bottombar.clearfix span.left a#h5ai-reference( href="{{pkg.url}}", title="{{pkg.name}} ยท {{pkg.description}}" )