diff --git a/Gruntfile.js b/Gruntfile.js index ebfba6db..486c4555 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -113,6 +113,7 @@ module.exports = function (grunt) { 'test/drop.coffee', 'test/revision.coffee', 'test/resolve.coffee', + 'test/state.coffee', 'test/wiki.coffee' ] } diff --git a/lib/legacy.coffee b/lib/legacy.coffee index 494dd3c8..79d9eb41 100644 --- a/lib/legacy.coffee +++ b/lib/legacy.coffee @@ -38,6 +38,8 @@ wiki.origin.get 'system/factories.json', (error, data) -> preLoadEditors data $ -> + state.syncDomWithLocation() + dialog.emit() # FUNCTIONS used by plugins and elsewhere diff --git a/lib/state.coffee b/lib/state.coffee index d7306f08..62aa2044 100644 --- a/lib/state.coffee +++ b/lib/state.coffee @@ -13,26 +13,67 @@ module.exports = state = {} state.inject = (link_) -> link = link_ +state.fromDOM = () -> + # [{site, slug},...] + slugs = state.pagesInDom() + sites = state.locsInDom() + slugs.map (slug, idx) -> + {site: sites[idx], slug: slug} + +state.fromLocation = (location) -> + # [{site, slug},...] + hash = new URLSearchParams(location.hash.substring(1)) + intent = (hash.get("sfw") || location.pathname) + .replace(/^\//,'') + toSiteSlug = (acc, item, idx) -> + if idx % 2 == 0 + acc.push({site: item}) + else + acc[acc.length-1].slug = item + acc + parts = intent.split('/') + if parts.length % 2 == 0 + parts.reduce(toSiteSlug, []) + else if parts.length == 1 and parts[0].endsWith(".html") + [{site: "view", slug: parts[0].replace(/\.html$/,'')}] + else + [{site: "view", slug: "welcome-visitors"}] + +toURL = (siteSlugs) -> + combine = (url, item) -> "#{url}/#{item.site}/#{item.slug}" + intent = siteSlugs.reduce(combine, "") + url = new URL(location) + if !!url.hash or url.pathname == '/' + url.hash = "sfw=#{intent.replace(/^\//,'')}" + else + url.pathname = intent + url + +unchanged = (url, location) -> + loc = new URL(location) + url.pathname == loc.pathname and + url.search == loc.search and + url.hash == loc.hash + state.pagesInDom = -> $.makeArray $(".page").map (_, el) -> el.id state.urlPages = -> - (i for i in $(location).attr('pathname').split('/') by 2)[1..] + state.fromLocation(location).map((it) -> it.slug) state.locsInDom = -> $.makeArray $(".page").map (_, el) -> $(el).data('site') or 'view' state.urlLocs = -> - (j for j in $(location).attr('pathname').split('/')[1..] by 2) + state.fromLocation(location).map((it) -> it.site) state.setUrl = -> document.title = lineup.bestTitle() if history and history.pushState - locs = state.locsInDom() - pages = state.pagesInDom() - url = ("/#{locs?[idx] or 'view'}/#{page}" for page, idx in pages).join('') - unless url is $(location).attr('pathname') + siteSlugs = state.fromDOM() + url = toURL(siteSlugs) + unless unchanged(url, location) history.pushState(null, null, url) state.debugStates = () -> @@ -45,7 +86,7 @@ state.show = (e) -> oldLocs = state.locsInDom() newLocs = state.urlLocs() - return if (!location.pathname or location.pathname is '/') + return if !location.hash and (!location.pathname or location.pathname is '/') matching = true for name, idx in oldPages @@ -73,3 +114,11 @@ state.first = -> for urlPage, idx in firstUrlPages when urlPage not in oldPages link.createPage(urlPage, firstUrlLocs[idx]) unless urlPage is '' +state.syncDomWithLocation = -> + main = "" + for {site, slug} in state.fromLocation(location) + dataSite = "" + if site != "view" + dataSite = """data-site="#{site}" """ + main += """
\n""" + $('section.main').html(main) diff --git a/test/state.coffee b/test/state.coffee new file mode 100644 index 00000000..6f4519c4 --- /dev/null +++ b/test/state.coffee @@ -0,0 +1,91 @@ +state = require('../lib/state') +lineup = require('../lib/lineup') +expect = require 'expect.js' + +# here we test manipulations of the lineup and window.location + +describe 'state', -> + actual = null + title = null + tests = [ + # [pathname, locs, pages] + ['/', [], []], + ['/view/welcome-visitors', ['view'], ['welcome-visitors']], + ['/view/foo/view/bar', ['view', 'view'], ['foo', 'bar']], + ['/view/welcome-visitors/fed.wiki.org/featured-sites', + ['view', 'fed.wiki.org'], ['welcome-visitors', 'featured-sites']], + ['/welcome-visitors.html', ['view'], ['welcome-visitors']], + ['/how-to-wiki.html', ['view'], ['how-to-wiki']] + ] + before -> + global.$ = (el) -> {attr: (key) -> el[key]} + global.history = + pushState: (state, title, url) -> actual = url + lineup.bestTitle = () -> title + + context 'using URL.pathname', -> + for [pathname, locs, pages] in tests + context pathname, -> + beforeEach -> + global.location = new URL("https://example.com#{pathname}") + it "urlPages() is [#{pages}]", -> + expect(state.urlPages()).to.eql(pages) + it "urlLocs() is [#{locs}]", -> + expect(state.urlLocs()).to.eql(locs) + describe 'setUrl', -> + beforeEach -> + actual = null + title = 'Welcome Visitors' + global.location = new URL('https://example.com/view/welcome-visitors') + global.document = {title: null} + it 'does not push url to history for the same location', -> + state.pagesInDom = -> ['welcome-visitors'] + state.locsInDom = -> ['view'] + state.setUrl() + expect(actual).to.be(null) + it 'pushes url to history when location changes', -> + state.pagesInDom = -> ['welcome-visitors', 'welcome-visitors'] + state.locsInDom = -> ['view', 'fed.wiki.org'] + state.setUrl() + expect(global.document.title).to.be('Welcome Visitors') + expect(actual.pathname).to.be('/view/welcome-visitors/fed.wiki.org/welcome-visitors') + + context 'using URL.hash', -> + for [sfw, locs, pages] in tests + context sfw, -> + beforeEach -> + global.location = new URL("https://example.com#sfw=#{sfw}") + it "urlPages() is [#{pages}]", -> + expect(state.urlPages()).to.eql(pages) + it "urlLocs() is [#{locs}]", -> + expect(state.urlLocs()).to.eql(locs) + describe 'setUrl', -> + beforeEach -> + actual = null + title = 'Welcome Visitors' + global.location = new URL('https://example.com#sfw=view/welcome-visitors') + global.document = {title: null} + it 'does not push url to history for the same location', -> + state.pagesInDom = -> ['welcome-visitors'] + state.locsInDom = -> ['view'] + state.setUrl() + expect(actual).to.be(null) + it 'pushes url to history when location changes', -> + state.pagesInDom = -> ['welcome-visitors', 'welcome-visitors'] + state.locsInDom = -> ['view', 'fed.wiki.org'] + state.setUrl() + expect(global.document.title).to.be('Welcome Visitors') + params = new URLSearchParams(actual.hash.substring(1)) + expect(params.get('sfw')) + .to.be('view/welcome-visitors/fed.wiki.org/welcome-visitors') + + it 'setUrl() defaults to URL.hash', -> + actual = null + global.location = new URL('https://example.com') + global.document = {title: null} + state.pagesInDom = -> ['welcome-visitors'] + state.locsInDom = -> ['view'] + state.setUrl() + expect(actual.pathname).to.be('/') + params = new URLSearchParams(actual.hash.substring(1)) + expect(params.get('sfw')).to.be('view/welcome-visitors')