Commit dc2e96d4d0a86072d9393a2d15d786a80cb341ff
Merge pull request #176 from ssbc/async_router
Async router normalisationmix irving authored on 2/21/2018, 6:54:47 AM
GitHub committed on 2/21/2018, 6:54:47 AM
Parent: 084946f795412a5d314bb4c3f4848d887219c041
Parent: db575953239b738e0d908ed0e7005bc4aa45d14c
Files changed
app/async/catch-link-click.js | changed |
app/html/app.js | changed |
app/html/scroller.js | changed |
app/html/tabs.js | changed |
app/page/thread.js | changed |
app/sync/catch-keyboard-shortcut.js | changed |
app/sync/goTo.js | changed |
app/sync/locationId.js | added |
router/async/normalise.js | added |
router/async/router.js | added |
app/async/catch-link-click.js | |||
---|---|---|---|
@@ -5,9 +5,9 @@ | |||
5 | 5 … | ||
6 | 6 … | exports.needs = nest({ | |
7 | 7 … | 'app.html.externalConfirm': 'first', | |
8 | 8 … | 'app.sync.goTo': 'first', | |
9 | - 'router.sync.normalise': 'first' | ||
9 … | + 'router.async.normalise': 'first' | ||
10 | 10 … | }) | |
11 | 11 … | ||
12 | 12 … | exports.create = function (api) { | |
13 | 13 … | return nest('app.async.catchLinkClick', catchLinkClick) | |
@@ -46,9 +46,10 @@ | |||
46 | 46 … | ||
47 | 47 … | function defaultCallback (link, { ctrlKey, isExternal }) { | |
48 | 48 … | if (isExternal) return api.app.html.externalConfirm(link) | |
49 | 49 … | ||
50 | - const location = api.router.sync.normalise(link) | ||
51 | 50 … | const openBackground = ctrlKey | |
52 | - api.app.sync.goTo(location, openBackground) | ||
51 … | + api.router.async.normalise(link, (err, location) => { | ||
52 … | + if (location) api.app.sync.goTo(location, { openBackground }) | ||
53 … | + }) | ||
53 | 54 … | } | |
54 | 55 … | } |
app/html/app.js | ||
---|---|---|
@@ -10,9 +10,8 @@ | ||
10 | 10 … | 'app.sync.initialise': 'first', |
11 | 11 … | 'app.sync.window': 'reduce', |
12 | 12 … | 'history.obs.location': 'first', |
13 | 13 … | 'history.sync.push': 'first', |
14 | - 'router.sync.router': 'first', | |
15 | 14 … | 'settings.sync.get': 'first', |
16 | 15 … | 'settings.sync.set': 'first' |
17 | 16 … | }) |
18 | 17 … |
app/html/scroller.js | ||
---|---|---|
@@ -42,21 +42,23 @@ | ||
42 | 42 … | } |
43 | 43 … | } |
44 | 44 … | } |
45 | 45 … | |
46 … | + return function scroll (d) { | |
47 … | + selectChild((!curMsgEl || d === 'first') ? container.firstChild | |
48 … | + : d < 0 ? curMsgEl.previousElementSibling || container.firstChild | |
49 … | + : d > 0 ? curMsgEl.nextElementSibling || container.lastChild | |
50 … | + : curMsgEl) | |
51 … | + | |
52 … | + return curMsgEl | |
53 … | + } | |
54 … | + | |
46 | 55 … | function selectChild (el) { |
47 | 56 … | if (!el) { return } |
48 | 57 … | |
58 … | + if (!el.scrollIntoViewIfNeeded && !el.scrollIntoView) return | |
49 | 59 … | ;(el.scrollIntoViewIfNeeded || el.scrollIntoView).call(el) |
50 | 60 … | el.focus() |
51 | 61 … | curMsgEl = el |
52 | 62 … | } |
53 | 63 … | |
54 | - return function scroll (d) { | |
55 | - selectChild((!curMsgEl || d === 'first') ? container.firstChild | |
56 | - : d < 0 ? curMsgEl.previousElementSibling || container.firstChild | |
57 | - : d > 0 ? curMsgEl.nextElementSibling || container.lastChild | |
58 | - : curMsgEl) | |
59 | - | |
60 | - return curMsgEl | |
61 | - } | |
62 | 64 … | } |
app/html/tabs.js | ||
---|---|---|
@@ -9,8 +9,9 @@ | ||
9 | 9 … | exports.needs = nest({ |
10 | 10 … | 'app.html.menu': 'first', |
11 | 11 … | 'app.html.searchBar': 'first', |
12 | 12 … | 'app.sync.goTo': 'first', |
13 … | + 'app.sync.locationId': 'first', | |
13 | 14 … | 'history.obs.store': 'first', |
14 | 15 … | 'history.sync.push': 'first' |
15 | 16 … | }) |
16 | 17 … | |
@@ -38,9 +39,9 @@ | ||
38 | 39 … | } |
39 | 40 … | const onClose = (page) => { |
40 | 41 … | var history = api.history.obs.store() |
41 | 42 … | const prunedHistory = history().filter(loc => { |
42 | - return JSON.stringify(loc) != page.id | |
43 … | + return api.app.sync.locationId(loc) != page.id | |
43 | 44 … | }) |
44 | 45 … | history.set(prunedHistory) |
45 | 46 … | } |
46 | 47 … |
app/page/thread.js | ||
---|---|---|
@@ -1,13 +1,16 @@ | ||
1 | 1 … | const { h, Struct, Value, when, computed, map, resolve, onceTrue } = require('mutant') |
2 | 2 … | const nest = require('depnest') |
3 … | +const get = require('lodash/get') | |
3 | 4 … | const { isFeed } = require('ssb-ref') |
4 | 5 … | |
5 | 6 … | exports.gives = nest('app.page.thread') |
6 | 7 … | |
7 | 8 … | exports.needs = nest({ |
8 | 9 … | 'about.html.avatar': 'first', |
9 | 10 … | 'app.html.scroller': 'first', |
11 … | + 'app.html.tabs': 'first', | |
12 … | + 'app.sync.locationId': 'first', | |
10 | 13 … | 'contact.obs.following': 'first', |
11 | 14 … | 'feed.obs.thread': 'first', |
12 | 15 … | 'keys.sync.id': 'first', |
13 | 16 … | 'message.html.compose': 'first', |
@@ -21,13 +24,15 @@ | ||
21 | 24 … | exports.create = function (api) { |
22 | 25 … | return nest('app.page.thread', threadPage) |
23 | 26 … | |
24 | 27 … | function threadPage (location) { |
25 | - const { key } = location | |
28 … | + const root = get(location, 'value.content.root', location.key) | |
29 … | + const msg = location.key | |
30 … | + if (msg !== root) scrollDownToMessage(msg) | |
26 | 31 … | |
27 | 32 … | const myId = api.keys.sync.id() |
28 | 33 … | const ImFollowing = api.contact.obs.following(myId) |
29 | - const { messages, isPrivate, rootId, lastId, channel, recps } = api.feed.obs.thread(key) | |
34 … | + const { messages, isPrivate, rootId, lastId, channel, recps } = api.feed.obs.thread(root) | |
30 | 35 … | const meta = Struct({ |
31 | 36 … | type: 'post', |
32 | 37 … | root: rootId, |
33 | 38 … | branch: lastId, |
@@ -64,15 +69,15 @@ | ||
64 | 69 … | placeholder: 'Write a reply', |
65 | 70 … | shrink: false |
66 | 71 … | }) |
67 | 72 … | const content = h('section.content', map(messages, m => { |
68 | - return api.message.html.render(resolve(m), {pageId: key}) | |
73 … | + return api.message.html.render(resolve(m), {pageId: root}) | |
69 | 74 … | })) |
70 | 75 … | const { container } = api.app.html.scroller({ prepend: header, content, append: composer }) |
71 | 76 … | |
72 | 77 … | container.classList.add('Thread') |
73 | - container.title = key | |
74 | - api.message.async.name(key, (err, name) => { | |
78 … | + container.title = msg | |
79 … | + api.message.async.name(msg, (err, name) => { | |
75 | 80 … | if (err) throw err |
76 | 81 … | container.title = name |
77 | 82 … | }) |
78 | 83 … | |
@@ -82,6 +87,26 @@ | ||
82 | 87 … | channelInput.disabled = true |
83 | 88 … | }) |
84 | 89 … | |
85 | 90 … | return container |
91 … | + | |
92 … | + function scrollDownToMessage (id) { | |
93 … | + const locationId = api.app.sync.locationId(location) | |
94 … | + const tabs = api.app.html.tabs() | |
95 … | + locateKey() | |
96 … | + | |
97 … | + function locateKey () { | |
98 … | + // wait till we're on the right page | |
99 … | + if (tabs.currentPage().id !== locationId) return setTimeout(locateKey, 200) | |
100 … | + | |
101 … | + if (!tabs.currentPage().scroll) return setTimeout(locateKey, 200) | |
102 … | + | |
103 … | + tabs.currentPage().scroll('first') | |
104 … | + const msg = tabs.currentPage().querySelector(`[data-id='${id}']`) | |
105 … | + if (!msg) return setTimeout(locateKey, 200) | |
106 … | + | |
107 … | + ;(msg.scrollIntoViewIfNeeded || msg.scrollIntoView).call(msg) | |
108 … | + msg.focus() | |
109 … | + } | |
110 … | + } | |
86 | 111 … | } |
87 | 112 … | } |
app/sync/catch-keyboard-shortcut.js | ||
---|---|---|
@@ -4,9 +4,9 @@ | ||
4 | 4 … | |
5 | 5 … | exports.needs = nest({ |
6 | 6 … | 'app.html.searchBar': 'first', |
7 | 7 … | 'app.html.tabs': 'first', |
8 | - 'app.sync.goTo': 'first' | |
8 … | + 'app.sync.goTo': 'first', | |
9 | 9 … | }) |
10 | 10 … | |
11 | 11 … | var gPressed = false |
12 | 12 … | |
@@ -42,9 +42,9 @@ | ||
42 | 42 … | return ev.target.blur() |
43 | 43 … | } |
44 | 44 … | } |
45 | 45 … | |
46 | -function genericShortcuts (ev, { tabs, search, goTo }) { | |
46 … | +function genericShortcuts (ev, { tabs, search, goTo, back }) { | |
47 | 47 … | // Messages |
48 | 48 … | if (ev.keyCode === 71) { // gg = scroll to top |
49 | 49 … | if (!gPressed) { |
50 | 50 … | gPressed = true |
@@ -63,11 +63,11 @@ | ||
63 | 63 … | return tabs.currentPage().scroll(1) |
64 | 64 … | case 75: // k = newer |
65 | 65 … | return tabs.currentPage().scroll(-1) |
66 | 66 … | case 13: // enter = open |
67 | - return goToMessage(ev, { tabs, goTo }) | |
67 … | + return goToMessage(ev, { goTo }) | |
68 | 68 … | case 79: // o = open |
69 | - return goToMessage(ev, { tabs, goTo }) | |
69 … | + return goToMessage(ev, { goTo }) | |
70 | 70 … | case 192: // ` = toggle raw message view |
71 | 71 … | return toggleRawMessage(ev) |
72 | 72 … | |
73 | 73 … | // Tabs |
@@ -99,32 +99,16 @@ | ||
99 | 99 … | if (ev.shiftKey) search.activate('%', ev) |
100 | 100 … | } |
101 | 101 … | } |
102 | 102 … | |
103 | -function goToMessage (ev, { tabs, goTo }) { | |
103 … | +function goToMessage (ev, { goTo }) { | |
104 | 104 … | const msg = ev.target |
105 | 105 … | if (!msg.classList.contains('Message')) return |
106 | 106 … | |
107 | - const { root, id } = msg.dataset | |
108 | - if (!root) return goTo(id) | |
109 | - | |
110 | - goTo(root) | |
111 | - setTimeout(() => scrollDownToMessage(id, tabs), 250) | |
107 … | + goTo(msg.dataset.id) | |
108 … | + // TODO - rm the dataset.root decorator | |
112 | 109 … | } |
113 | 110 … | |
114 | -function scrollDownToMessage (id, tabs) { | |
115 | - tabs.currentPage().scroll('first') | |
116 | - locateKey() | |
117 | - | |
118 | - function locateKey () { | |
119 | - const msg = tabs.currentPage().querySelector(`[data-id='${id}']`) | |
120 | - if (msg === null) return setTimeout(locateKey, 100) | |
121 | - | |
122 | - ;(msg.scrollIntoViewIfNeeded || msg.scrollIntoView).call(msg) | |
123 | - msg.focus() | |
124 | - } | |
125 | -} | |
126 | - | |
127 | 111 … | function toggleRawMessage (ev) { |
128 | 112 … | const msg = ev.target |
129 | 113 … | if (!msg.classList.contains('Message')) return |
130 | 114 … |
app/sync/goTo.js | ||
---|---|---|
@@ -3,12 +3,13 @@ | ||
3 | 3 … | exports.gives = nest({ 'app.sync.goTo': true }) |
4 | 4 … | |
5 | 5 … | exports.needs = nest({ |
6 | 6 … | 'app.html.tabs': 'first', |
7 … | + 'app.sync.locationId': 'first', | |
7 | 8 … | 'history.obs.store': 'first', |
8 | 9 … | 'history.sync.push': 'first', |
9 | - 'router.sync.normalise': 'first', | |
10 | - 'router.sync.router': 'first' | |
10 … | + 'router.async.normalise': 'first', | |
11 … | + 'router.async.router': 'first' | |
11 | 12 … | }) |
12 | 13 … | |
13 | 14 … | exports.create = function (api) { |
14 | 15 … | return nest('app.sync.goTo', goTo) |
@@ -20,34 +21,44 @@ | ||
20 | 21 … | // allows a refactor of catch-keyboard-shortcut + patch-inbox |
21 | 22 … | // - extracts scrollToMessage into app.page.thread |
22 | 23 … | // - router.sync.router would take (location, { position }) ? |
23 | 24 … | |
24 | - function goTo (location, openBackground = false, split = false) { | |
25 | - location = api.router.sync.normalise(location) | |
26 | - const locationId = JSON.stringify(location) | |
25 … | + function goTo (location, options = {}) { | |
26 … | + const { | |
27 … | + openBackground = false, | |
28 … | + split = false | |
29 … | + } = options | |
27 | 30 … | |
28 | 31 … | const tabs = api.app.html.tabs() |
29 | - if (tabs.has(locationId)) { | |
30 | - tabs.select(locationId) | |
31 | - api.history.sync.push(location) | |
32 | - return true | |
33 | - } | |
34 | 32 … | |
35 | - const page = api.router.sync.router(location) | |
36 | - if (!page) return | |
33 … | + // currently do normalisation here only to generate normalised locationId | |
34 … | + api.router.async.normalise(location, (err, location) => { | |
35 … | + const locationId = api.app.sync.locationId(location) | |
37 | 36 … | |
38 | - page.id = page.id || locationId | |
39 | - tabs.add(page, !openBackground, split) | |
37 … | + if (tabs.has(locationId)) { | |
38 … | + tabs.select(locationId) | |
39 … | + api.history.sync.push(location) | |
40 | 40 … | |
41 | - if (openBackground) { | |
42 | - const history = api.history.obs.store() | |
43 | - var _history = history() | |
44 | - var current = _history.pop() | |
41 … | + return true | |
42 … | + } | |
45 | 43 … | |
46 | - history.set([ ..._history, location, current ]) | |
47 | - } else { | |
48 | - api.history.sync.push(location) | |
49 | - } | |
44 … | + api.router.async.router(location, (err, page) => { | |
45 … | + if (err) throw err | |
50 | 46 … | |
51 | - return openBackground | |
47 … | + if (!page) return | |
48 … | + | |
49 … | + page.id = page.id || locationId | |
50 … | + tabs.add(page, !openBackground, split) | |
51 … | + | |
52 … | + if (openBackground) { | |
53 … | + const history = api.history.obs.store() | |
54 … | + var _history = history() | |
55 … | + var current = _history.pop() | |
56 … | + | |
57 … | + history.set([ ..._history, location, current ]) | |
58 … | + } else { | |
59 … | + api.history.sync.push(location) | |
60 … | + } | |
61 … | + }) | |
62 … | + }) | |
52 | 63 … | } |
53 | 64 … | } |
app/sync/locationId.js | ||
---|---|---|
@@ -1,0 +1,21 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | +const get = require('lodash/get') | |
3 … | +const { isMsg } = require('ssb-ref') | |
4 … | + | |
5 … | +exports.gives = nest({ 'app.sync.locationId': true }) | |
6 … | + | |
7 … | +exports.create = function (api) { | |
8 … | + return nest('app.sync.locationId', locationId) | |
9 … | + | |
10 … | + function locationId (location) { | |
11 … | + if (typeof location === 'string') return string | |
12 … | + | |
13 … | + if (isMsg(location.key)) { | |
14 … | + location = { | |
15 … | + key: get(location, 'value.content.root', location.key) | |
16 … | + } | |
17 … | + } | |
18 … | + | |
19 … | + return JSON.stringify(location) | |
20 … | + } | |
21 … | +} |
router/async/normalise.js | ||
---|---|---|
@@ -1,0 +1,39 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | +const { isBlob, isFeed, isMsg } = require('ssb-ref') | |
3 … | + | |
4 … | +exports.gives = nest('router.async.normalise') | |
5 … | + | |
6 … | +exports.needs = nest({ | |
7 … | + 'message.sync.unbox': 'first', | |
8 … | + 'sbot.async.get': 'first' | |
9 … | +}) | |
10 … | + | |
11 … | +exports.create = (api) => { | |
12 … | + return nest('router.async.normalise', normalise) | |
13 … | + | |
14 … | + function normalise (location, cb) { | |
15 … | + if (typeof location === 'object') cb(null, location) | |
16 … | + else if (isMsg(location)) { | |
17 … | + api.sbot.async.get(location, (err, value) => { | |
18 … | + if (err) cb(err) | |
19 … | + else { | |
20 … | + if (typeof value.content === 'string') value = api.message.sync.unbox(value) | |
21 … | + cb(null, {key: location, value}) | |
22 … | + } | |
23 … | + }) | |
24 … | + } else if (isBlob(location)) cb(null, { blob: location }) | |
25 … | + else if (isChannel(location)) cb(null, { channel: location }) | |
26 … | + else if (isFeed(location)) cb(null, { feed: location }) | |
27 … | + else if (isPage(location)) cb(null, { page: location.substring(1) }) | |
28 … | + | |
29 … | + return true | |
30 … | + } | |
31 … | +} | |
32 … | + | |
33 … | +function isChannel (str) { | |
34 … | + return typeof str === 'string' && str[0] === '#' && str.length > 1 | |
35 … | +} | |
36 … | + | |
37 … | +function isPage (str) { | |
38 … | + return typeof str === 'string' && str[0] === '/' && str.length > 1 | |
39 … | +} |
router/async/router.js | ||
---|---|---|
@@ -1,0 +1,36 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | + | |
3 … | +exports.gives = nest('router.async.router') | |
4 … | + | |
5 … | +exports.needs = nest({ | |
6 … | + 'router.async.normalise': 'first', | |
7 … | + 'router.sync.routes': 'reduce' | |
8 … | +}) | |
9 … | + | |
10 … | +exports.create = (api) => { | |
11 … | + var router = null | |
12 … | + | |
13 … | + return nest('router.async.router', (location, cb) => { | |
14 … | + if (!router) { | |
15 … | + router = Router(api.router.sync.routes()) | |
16 … | + } | |
17 … | + | |
18 … | + api.router.async.normalise(location, (err, normLocation) => { | |
19 … | + if (err) return cb(err) | |
20 … | + | |
21 … | + router(normLocation, cb) | |
22 … | + }) | |
23 … | + | |
24 … | + // stop depject 'first' after this method | |
25 … | + return true | |
26 … | + }) | |
27 … | +} | |
28 … | + | |
29 … | +function Router (routes) { | |
30 … | + return (location, cb) => { | |
31 … | + const route = routes.find(([validator]) => validator(location)) | |
32 … | + // signature of a route is [ routeValidator, routeFunction ] | |
33 … | + | |
34 … | + if (route) cb(null, route[1](location)) | |
35 … | + } | |
36 … | +} |
Built with git-ssb-web