Commit 594c827ad16566b9d79bbceee0ee58002835b94d
Merge ../patchbay into fix-emoji-size
Anders Rune Jensen committed on 4/28/2017, 7:23:58 PMParent: 17137bf4950b512a952c46b8d1a92203756ed979
Parent: 90260656032e2a7c7b7a9d84c4afd6c1203422e5
Files changed
README.md | changed |
app/html/app.js | changed |
app/html/filter.js | changed |
app/html/filter.mcss | changed |
app/html/menu.js | changed |
app/html/page/channel.js | changed |
app/html/page/search.js | changed |
app/html/search-bar.js | changed |
app/html/tabs.js | added |
app/sync/catch-keyboard-shortcut.js | changed |
app/sync/addPage.js | added |
app/sync/goTo.js | added |
message/html/compose.js | changed |
message/html/layout/default.mcss | changed |
message/html/render/follow.js | changed |
package.json | changed |
README.md | ||
---|---|---|
@@ -16,8 +16,13 @@ | ||
16 | 16 … | ```sh |
17 | 17 … | sudo apt-get install m4 libtool eclipse-cdt-autotools |
18 | 18 … | ``` |
19 | 19 … | |
20 … | +On MacOS you may need the following packages installed (in this example, via [Homebrew](https://brew.sh/)): | |
21 … | +```sh | |
22 … | +brew install libtool automake autoconf | |
23 … | +``` | |
24 … | + | |
20 | 25 … | Install Scuttlebot (your gossip server) |
21 | 26 … | |
22 | 27 … | ```sh |
23 | 28 … | npm install scuttlebot@latest -g |
app/html/app.js | ||
---|---|---|
@@ -1,8 +1,7 @@ | ||
1 … | +const nest = require('depnest') | |
1 | 2 … | const { h } = require('mutant') |
2 | -const nest = require('depnest') | |
3 | 3 … | const insertCss = require('insert-css') |
4 | -const Tabs = require('hypertabs') | |
5 | 4 … | |
6 | 5 … | exports.gives = nest('app.html.app') |
7 | 6 … | |
8 | 7 … | exports.needs = nest({ |
@@ -12,13 +11,13 @@ | ||
12 | 11 … | }, |
13 | 12 … | html: { |
14 | 13 … | error: 'first', |
15 | 14 … | externalConfirm: 'first', |
16 | - menu: 'first', | |
17 | - page: 'first', | |
18 | - searchBar: 'first' | |
15 … | + tabs: 'first', | |
16 … | + page: 'first' | |
19 | 17 … | }, |
20 | 18 … | sync: { |
19 … | + addPage: 'first', | |
21 | 20 … | catchKeyboardShortcut: 'first' |
22 | 21 … | } |
23 | 22 … | }, |
24 | 23 … | 'styles.css': 'reduce' |
@@ -30,41 +29,16 @@ | ||
30 | 29 … | function app () { |
31 | 30 … | const css = values(api.styles.css()).join('\n') |
32 | 31 … | insertCss(css) |
33 | 32 … | |
34 | - const handleSelection = (path, change) => { | |
35 | - if (tabs.has(path)) { | |
36 | - tabs.select(path) | |
37 | - return true | |
38 | - } | |
33 … | + const initialTabs = ['/public', '/private', '/notifications'] | |
34 … | + const tabs = api.app.html.tabs(initialTabs) | |
35 … | + const { addPage } = api.app.sync | |
39 | 36 … | |
40 | - addPage(path, true, false) | |
41 | - return change | |
42 | - } | |
43 | - const search = api.app.html.searchBar(handleSelection) | |
44 | - const menu = api.app.html.menu(handleSelection) | |
45 | - | |
46 | - const tabs = Tabs(onSelect, { append: h('div.navExtra', [ search, menu ]) }) | |
47 | - function onSelect (indexes) { | |
48 | - search.input.value = tabs.get(indexes[0]).content.id | |
49 | - } | |
50 | - | |
51 | 37 … | const App = h('App', tabs) |
52 | 38 … | |
53 | - function addPage (link, change, split) { | |
54 | - const page = api.app.html.page(link) | |
55 | - if (!page) return | |
56 | - | |
57 | - page.id = page.id || link | |
58 | - tabs.add(page, change, split) | |
59 | - } | |
60 | - | |
61 | - const initialTabs = ['/public', '/private', '/notifications'] | |
62 | - initialTabs.forEach(p => addPage(p)) | |
63 | - tabs.select(0) | |
64 | - | |
65 | 39 … | // Catch keyboard shortcuts |
66 | - api.app.sync.catchKeyboardShortcut(window, { tabs, search }) | |
40 … | + api.app.sync.catchKeyboardShortcut(window, { tabs }) | |
67 | 41 … | |
68 | 42 … | // Catch link clicks |
69 | 43 … | api.app.async.catchLinkClick(App, (link, { ctrlKey: openBackground, isExternal }) => { |
70 | 44 … | if (isExternal) return api.app.html.externalConfirm(link) |
app/html/filter.js | ||
---|---|---|
@@ -47,8 +47,9 @@ | ||
47 | 47 … | h('i', { |
48 | 48 … | classList: when(showFilters, 'fa fa-filter -active', 'fa fa-filter'), |
49 | 49 … | 'ev-click': () => showFilters.set(!showFilters()) |
50 | 50 … | }), |
51 … | + h('i.fa.fa-angle-up', { 'ev-click': draw }), | |
51 | 52 … | h('div', { className: when(showFilters, '', '-hidden') }, [ |
52 | 53 … | h('header', [ |
53 | 54 … | 'Filter', |
54 | 55 … | h('i.fa.fa-filter') |
@@ -58,12 +59,8 @@ | ||
58 | 59 … | h('label', 'Show author'), |
59 | 60 … | authorInput |
60 | 61 … | ]), |
61 | 62 … | toggle({ obs: onlyPeopleIFollow, label: 'Only people I follow' }), |
62 | - h('div.refresh', { 'ev-click': draw }, [ | |
63 | - h('label', 'refresh'), | |
64 | - h('i.fa.fa-refresh') | |
65 | - ]), | |
66 | 63 … | h('div.message-types', [ |
67 | 64 … | h('header', 'Show messages'), |
68 | 65 … | toggle({ obs: showPost, label: 'post' }), |
69 | 66 … | toggle({ obs: showVote, label: 'like' }), |
app/html/filter.mcss | ||
---|---|---|
@@ -1,21 +1,28 @@ | ||
1 | 1 … | Filter { |
2 | 2 … | display: flex |
3 | 3 … | flex-direction: column |
4 | 4 … | |
5 | - i.fa-filter { | |
5 … | + color: #444 | |
6 … | + | |
7 … | + i { | |
6 | 8 … | position: absolute |
7 | 9 … | top: 3rem |
8 | - right: 2rem | |
9 | 10 … | |
10 | 11 … | font-size: 1.2rem |
11 | 12 … | color: #b3b3b3 |
12 | 13 … | cursor: pointer |
14 … | + } | |
15 … | + i.fa-filter { | |
16 … | + right: 2rem | |
13 | 17 … | |
14 | 18 … | -active { |
15 | 19 … | color: #555 |
16 | 20 … | } |
17 | 21 … | } |
22 … | + i.fa-angle-up { | |
23 … | + left: 1rem | |
24 … | + } | |
18 | 25 … | |
19 | 26 … | div { |
20 | 27 … | margin: 0 .5rem .5rem .5rem |
21 | 28 … | padding: .5rem |
@@ -41,19 +48,12 @@ | ||
41 | 48 … | flex-wrap: wrap |
42 | 49 … | justify-content: space-between |
43 | 50 … | align-items: center |
44 | 51 … | |
45 | - div.refresh { | |
52 … | + div.author { | |
46 | 53 … | label { |
47 | 54 … | margin-right: .4rem |
48 | 55 … | } |
49 | - | |
50 | - } | |
51 | - | |
52 | - div.author { | |
53 | - label { | |
54 | - margin-right: .4rem | |
55 | - } | |
56 | 56 … | input { |
57 | 57 … | border: 1px gainsboro solid |
58 | 58 … | font-size: 1rem |
59 | 59 … | } |
@@ -61,9 +61,9 @@ | ||
61 | 61 … | |
62 | 62 … | div.message-types { |
63 | 63 … | margin: .6rem 0 |
64 | 64 … | display: flex |
65 | - | |
65 … | + | |
66 | 66 … | header { |
67 | 67 … | margin-right: 1rem |
68 | 68 … | } |
69 | 69 … | } |
app/html/menu.js | ||
---|---|---|
@@ -5,19 +5,23 @@ | ||
5 | 5 … | |
6 | 6 … | exports.needs = nest('app.html.menuItem', 'map') |
7 | 7 … | |
8 | 8 … | exports.create = function (api) { |
9 | - return nest('app.html.menu', menu) | |
9 … | + var _menu | |
10 | 10 … | |
11 | - function menu (handleClick) { | |
11 … | + return nest('app.html.menu', function menu (handleClick) { | |
12 … | + if (_menu) return _menu | |
13 … | + | |
12 | 14 … | var state = Value('') |
13 | 15 … | |
14 | - return h('Menu', { | |
16 … | + _menu = h('Menu', { | |
15 | 17 … | classList: [ state ], |
16 | 18 … | 'ev-mouseover': () => state.set('-active'), |
17 | 19 … | 'ev-mouseout': () => state.set('') |
18 | 20 … | }, [ |
19 | 21 … | h('div', api.app.html.menuItem(handleClick)) |
20 | 22 … | ]) |
21 | - } | |
23 … | + | |
24 … | + return _menu | |
25 … | + }) | |
22 | 26 … | } |
23 | 27 … |
app/html/page/channel.js | ||
---|---|---|
@@ -4,10 +4,13 @@ | ||
4 | 4 … | |
5 | 5 … | exports.gives = nest('app.html.page') |
6 | 6 … | |
7 | 7 … | exports.needs = nest({ |
8 … | + 'app.html': { | |
9 … | + filter: 'first', | |
10 … | + scroller: 'first' | |
11 … | + }, | |
8 | 12 … | 'feed.pull.channel': 'first', |
9 | - 'app.html.scroller': 'first', | |
10 | 13 … | message: { |
11 | 14 … | html: { |
12 | 15 … | compose: 'first', |
13 | 16 … | render: 'first' |
@@ -21,24 +24,31 @@ | ||
21 | 24 … | |
22 | 25 … | function channelView (path) { |
23 | 26 … | if (path && !path.match(/#[^\s]+/)) return |
24 | 27 … | |
25 | - var channel = path.substr(1) | |
28 … | + const channel = path.substr(1) | |
29 … | + const composer = api.message.html.compose({ meta: { type: 'post', channel } }) | |
30 … | + const { filterMenu, filterDownThrough, filterUpThrough, resetFeed } = api.app.html.filter(draw) | |
31 … | + const { container, content } = api.app.html.scroller({ prepend: [composer, filterMenu] }) | |
26 | 32 … | |
27 | - var composer = api.message.html.compose({ meta: { type: 'post', channel } }) | |
28 | - var { container, content } = api.app.html.scroller({ prepend: composer }) | |
33 … | + function draw () { | |
34 … | + resetFeed({ container, content }) | |
29 | 35 … | |
30 | - var openChannelSource = api.feed.pull.channel(channel) | |
36 … | + const openChannelSource = api.feed.pull.channel(channel) | |
31 | 37 … | |
32 | - pull( | |
33 | - openChannelSource({old: false}), | |
34 | - Scroller(container, content, api.message.html.render, true, false) | |
35 | - ) | |
38 … | + pull( | |
39 … | + openChannelSource({old: false}), | |
40 … | + filterUpThrough(), | |
41 … | + Scroller(container, content, api.message.html.render, true, false) | |
42 … | + ) | |
36 | 43 … | |
37 | - pull( | |
38 | - openChannelSource({reverse: true}), | |
39 | - Scroller(container, content, api.message.html.render, false, false) | |
40 | - ) | |
44 … | + pull( | |
45 … | + openChannelSource({reverse: true}), | |
46 … | + filterDownThrough(), | |
47 … | + Scroller(container, content, api.message.html.render, false, false) | |
48 … | + ) | |
49 … | + } | |
50 … | + draw() | |
41 | 51 … | |
42 | 52 … | return container |
43 | 53 … | } |
44 | 54 … | } |
app/html/page/search.js | ||
---|---|---|
@@ -8,9 +8,12 @@ | ||
8 | 8 … | |
9 | 9 … | exports.gives = nest('app.html.page') |
10 | 10 … | |
11 | 11 … | exports.needs = nest({ |
12 | - 'app.html.scroller': 'first', | |
12 … | + 'app.html': { | |
13 … | + filter: 'first', | |
14 … | + scroller: 'first' | |
15 … | + }, | |
13 | 16 … | 'message.html.render': 'first', |
14 | 17 … | 'sbot.pull': { |
15 | 18 … | log: 'first', |
16 | 19 … | search: 'first' |
@@ -108,41 +111,50 @@ | ||
108 | 111 … | when(hasNoFulltextMatches, h('div.matches', 'No matches')) |
109 | 112 … | ]) |
110 | 113 … | ) |
111 | 114 … | ]) |
112 | - var { container, content } = api.app.html.scroller({ prepend: searchHeader }) | |
115 … | + const { filterMenu, filterDownThrough, filterUpThrough, resetFeed } = api.app.html.filter(draw) | |
116 … | + const { container, content } = api.app.html.scroller({ prepend: [searchHeader, filterMenu] }) | |
113 | 117 … | container.id = path // helps tabs find this tab |
114 | 118 … | |
115 | 119 … | function renderMsg (msg) { |
116 | 120 … | var el = api.message.html.render(msg) |
117 | 121 … | highlight(el, createOrRegExp(query)) |
118 | 122 … | return el |
119 | 123 … | } |
120 | 124 … | |
121 | - pull( | |
122 | - api.sbot.pull.log({old: false}), | |
123 | - pull.filter(matchesQuery), | |
124 | - Scroller(container, content, renderMsg, true, false) | |
125 | - ) | |
125 … | + function draw () { | |
126 … | + resetFeed({ container, content }) | |
126 | 127 … | |
127 | - pull( | |
128 | - next(api.sbot.pull.search, {query: queryStr, reverse: true, limit: 500, live: false}), | |
129 | - fallback((err) => { | |
130 | - if (err === true) { | |
131 | - search.fulltext.isDone.set(true) | |
132 | - } else if (/^no source/.test(err.message)) { | |
133 | - search.isLinear.set(true) | |
134 | - return pull( | |
135 | - next(api.sbot.pull.log, {reverse: true, limit: 500, live: false}), | |
136 | - pull.through(() => search.linear.checked.set(search.linear.checked() + 1)), | |
137 | - pull.filter(matchesQuery) | |
138 | - ) | |
139 | - } | |
140 | - }), | |
141 | - pull.through(() => search.matches.set(search.matches() + 1)), | |
142 | - Scroller(container, content, renderMsg, false, false) | |
143 | - ) | |
128 … | + pull( | |
129 … | + api.sbot.pull.log({old: false}), | |
130 … | + pull.filter(matchesQuery), | |
131 … | + filterUpThrough(), | |
132 … | + Scroller(container, content, renderMsg, true, false) | |
133 … | + ) | |
144 | 134 … | |
135 … | + pull( | |
136 … | + next(api.sbot.pull.search, {query: queryStr, reverse: true, limit: 500, live: false}), | |
137 … | + fallback((err) => { | |
138 … | + if (err === true) { | |
139 … | + search.fulltext.isDone.set(true) | |
140 … | + } else if (/^no source/.test(err.message)) { | |
141 … | + search.isLinear.set(true) | |
142 … | + return pull( | |
143 … | + next(api.sbot.pull.log, {reverse: true, limit: 500, live: false}), | |
144 … | + pull.through(() => search.linear.checked.set(search.linear.checked() + 1)), | |
145 … | + pull.filter(matchesQuery) | |
146 … | + ) | |
147 … | + } | |
148 … | + }), | |
149 … | + filterDownThrough(), | |
150 … | + pull.through(() => search.matches.set(search.matches() + 1)), | |
151 … | + Scroller(container, content, renderMsg, false, false) | |
152 … | + ) | |
153 … | + } | |
154 … | + | |
155 … | + draw() | |
156 … | + | |
145 | 157 … | return container |
146 | 158 … | } |
147 | 159 … | } |
148 | 160 … |
app/html/search-bar.js | ||
---|---|---|
@@ -4,16 +4,20 @@ | ||
4 | 4 … | |
5 | 5 … | exports.gives = nest('app.html.searchBar') |
6 | 6 … | |
7 | 7 … | exports.needs = nest({ |
8 … | + 'app.sync.goTo': 'first', | |
8 | 9 … | 'about.async.suggest': 'first', |
9 | 10 … | 'channel.async.suggest': 'first' |
10 | 11 … | }) |
11 | 12 … | |
12 | 13 … | exports.create = function (api) { |
13 | - return nest('app.html.searchBar', searchBar) | |
14 … | + var _search | |
14 | 15 … | |
15 | - function searchBar (go) { | |
16 … | + return nest('app.html.searchBar', function searchBar () { | |
17 … | + if (_search) return _search | |
18 … | + | |
19 … | + const goTo = api.app.sync.goTo | |
16 | 20 … | const getProfileSuggestions = api.about.async.suggest() |
17 | 21 … | const getChannelSuggestions = api.channel.async.suggest() |
18 | 22 … | |
19 | 23 … | const input = h('input', { |
@@ -21,9 +25,9 @@ | ||
21 | 25 … | placeholder: '?search, @name, #channel', |
22 | 26 … | 'ev-keyup': ev => { |
23 | 27 … | switch (ev.keyCode) { |
24 | 28 … | case 13: // enter |
25 | - if (go(input.value.trim(), !ev.ctrlKey)) { | |
29 … | + if (goTo(input.value.trim(), !ev.ctrlKey)) { | |
26 | 30 … | input.blur() |
27 | 31 … | } |
28 | 32 … | return |
29 | 33 … | case 27: // escape |
@@ -32,19 +36,18 @@ | ||
32 | 36 … | return |
33 | 37 … | } |
34 | 38 … | } |
35 | 39 … | }) |
36 | - input.go = go // crude navigation api | |
37 | 40 … | input.addEventListener('suggestselect', ev => { |
38 | 41 … | input.value = ev.detail.id // HACK : this over-rides the markdown value |
39 | 42 … | |
40 | - // if (go(input.value.trim(), !ev.ctrlKey)) | |
43 … | + // if (goTo(input.value.trim(), !ev.ctrlKey)) | |
41 | 44 … | // input.blur() |
42 | 45 … | }) |
43 | - const search = h('SearchBar', input) | |
44 | 46 … | |
45 | - search.input = input | |
46 | - search.activate = (sigil, ev) => { | |
47 … | + _search = h('SearchBar', input) | |
48 … | + _search.input = input | |
49 … | + _search.activate = (sigil, ev) => { | |
47 | 50 … | input.focus() |
48 | 51 … | ev.preventDefault() |
49 | 52 … | if (input.value[0] === sigil) { |
50 | 53 … | input.selectionStart = 1 |
@@ -61,8 +64,8 @@ | ||
61 | 64 … | cb(null, getChannelSuggestions(inputText.slice(1))) |
62 | 65 … | } |
63 | 66 … | }, {cls: 'SuggestBox'}) |
64 | 67 … | |
65 | - return search | |
66 | - } | |
68 … | + return _search | |
69 … | + }) | |
67 | 70 … | } |
68 | 71 … |
app/html/tabs.js | ||
---|---|---|
@@ -1,0 +1,46 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | +const { h } = require('mutant') | |
3 … | +const Tabs = require('hypertabs') | |
4 … | + | |
5 … | +exports.gives = nest({ | |
6 … | + app: { | |
7 … | + 'html.tabs': true | |
8 … | + } | |
9 … | +}) | |
10 … | + | |
11 … | +exports.needs = nest({ | |
12 … | + 'app.html': { | |
13 … | + menu: 'first', | |
14 … | + page: 'first', | |
15 … | + searchBar: 'first' | |
16 … | + }, | |
17 … | + 'app.sync.addPage': 'first' | |
18 … | +}) | |
19 … | + | |
20 … | +exports.create = function (api) { | |
21 … | + var _tabs | |
22 … | + | |
23 … | + function tabs (initialTabs = []) { | |
24 … | + if (_tabs) return _tabs | |
25 … | + | |
26 … | + const search = api.app.html.searchBar() | |
27 … | + const menu = api.app.html.menu() | |
28 … | + const onSelect = (indexes) => { | |
29 … | + search.input.value = _tabs.get(indexes[0]).content.id | |
30 … | + } | |
31 … | + _tabs = Tabs(onSelect, { | |
32 … | + append: h('div.navExtra', [ search, menu ]) | |
33 … | + }) | |
34 … | + _tabs.getCurrent = () => _tabs.get(_tabs.selected[0]) | |
35 … | + | |
36 … | + // # TODO: review - this works but is strange | |
37 … | + initialTabs.forEach(p => api.app.sync.addPage(p)) | |
38 … | + _tabs.select(0) | |
39 … | + return _tabs | |
40 … | + } | |
41 … | + | |
42 … | + return nest({ | |
43 … | + 'app.html.tabs': tabs | |
44 … | + }) | |
45 … | +} | |
46 … | + |
app/sync/catch-keyboard-shortcut.js | ||
---|---|---|
@@ -1,24 +1,36 @@ | ||
1 | 1 … | const nest = require('depnest') |
2 | 2 … | |
3 | 3 … | exports.gives = nest('app.sync.catchKeyboardShortcut') |
4 | 4 … | |
5 … | +exports.needs = nest({ | |
6 … | + 'app.html': { | |
7 … | + searchBar: 'first', | |
8 … | + tabs: 'first' | |
9 … | + }, | |
10 … | + 'app.sync': { | |
11 … | + goTo: 'first' | |
12 … | + } | |
13 … | +}) | |
14 … | + | |
5 | 15 … | exports.create = function (api) { |
6 | 16 … | return nest('app.sync.catchKeyboardShortcut', catchKeyboardShortcut) |
7 | 17 … | |
8 | - function catchKeyboardShortcut (root, opts) { | |
18 … | + function catchKeyboardShortcut (root) { | |
9 | 19 … | var gPressed = false |
10 | 20 … | |
21 … | + var tabs = api.app.html.tabs() | |
22 … | + var search = api.app.html.searchBar() | |
23 … | + var goTo = api.app.sync.goTo | |
24 … | + | |
11 | 25 … | root.addEventListener('keydown', (ev) => { |
12 | 26 … | isTextFieldEvent(ev) |
13 | 27 … | ? textFieldShortcuts(ev) |
14 | - : genericShortcuts(ev, opts) | |
28 … | + : genericShortcuts(ev, { tabs, search, goTo }) | |
15 | 29 … | }) |
16 | 30 … | } |
17 | 31 … | } |
18 | 32 … | |
19 | -// TODO build better apis for navigation, search, and publishing | |
20 | - | |
21 | 33 … | function isTextFieldEvent (ev) { |
22 | 34 … | const tag = ev.target.nodeName |
23 | 35 … | return (tag === 'INPUT' || tag === 'TEXTAREA') |
24 | 36 … | } |
@@ -28,30 +40,30 @@ | ||
28 | 40 … | ev.target.publish() // expects the textField to have a publish method |
29 | 41 … | } |
30 | 42 … | } |
31 | 43 … | |
32 | -function genericShortcuts (ev, { tabs, search }) { | |
44 … | +function genericShortcuts (ev, { tabs, goTo, search }) { | |
33 | 45 … | // Messages |
34 | 46 … | if (ev.keyCode === 71) { // gg = scroll to top |
35 | 47 … | if (!gPressed) { |
36 | 48 … | gPressed = true |
37 | 49 … | return |
38 | 50 … | } |
39 | - tabs.get(tabs.selected[0]).firstChild.scroll('first') | |
51 … | + tabs.getCurrent().firstChild.scroll('first') | |
40 | 52 … | } |
41 | 53 … | gPressed = false |
42 | 54 … | |
43 | 55 … | switch (ev.keyCode) { |
44 | 56 … | |
45 | 57 … | // Messages (cont'd) |
46 | 58 … | case 74: // j = older |
47 | - return tabs.get(tabs.selected[0]).firstChild.scroll(1) | |
59 … | + return tabs.getCurrent().firstChild.scroll(1) | |
48 | 60 … | case 75: // k = newer |
49 | - return tabs.get(tabs.selected[0]).firstChild.scroll(-1) | |
61 … | + return tabs.getCurrent().firstChild.scroll(-1) | |
50 | 62 … | case 13: // enter = open |
51 | - return goToMessage(ev, tabs) | |
63 … | + return goToMessage(ev, { tabs, goTo }) | |
52 | 64 … | case 79: // o = open |
53 | - return goToMessage(ev, tabs) | |
65 … | + return goToMessage(ev, { tabs, goTo }) | |
54 | 66 … | case 192: // ` = toggle raw message view |
55 | 67 … | return toggleRawMessage(ev) |
56 | 68 … | |
57 | 69 … | // Tabs |
@@ -84,28 +96,25 @@ | ||
84 | 96 … | return |
85 | 97 … | } |
86 | 98 … | } |
87 | 99 … | |
88 | -function goToMessage (ev, tabs) { | |
100 … | +function goToMessage (ev, { tabs, goTo }) { | |
89 | 101 … | const msg = ev.target |
90 | 102 … | if (!msg.classList.contains('Message')) return |
91 | 103 … | |
92 | - // this uses a crudely exported nav api | |
93 | - const search = document.querySelector('input[type=search]') | |
94 | - | |
95 | 104 … | const { root, id } = msg.dataset |
96 | - if (!root) return search.go(id) | |
105 … | + if (!root) return goTo(id) | |
97 | 106 … | |
98 | - search.go(root) | |
107 … | + goTo(root) | |
99 | 108 … | scrollDownToMessage(id, tabs) |
100 | 109 … | } |
101 | 110 … | |
102 | 111 … | function scrollDownToMessage (id, tabs) { |
103 | - tabs.get(tabs.selected[0]).firstChild.scroll('first') | |
112 … | + tabs.getCurrent().firstChild.scroll('first') | |
104 | 113 … | locateKey() |
105 | 114 … | |
106 | 115 … | function locateKey () { |
107 | - const msg = tabs.get(tabs.selected[0]).querySelector(`[data-id='${id}']`) | |
116 … | + const msg = tabs.getCurrent().querySelector(`[data-id='${id}']`) | |
108 | 117 … | if (msg === null) return setTimeout(locateKey, 100) |
109 | 118 … | |
110 | 119 … | ;(msg.scrollIntoViewIfNeeded || msg.scrollIntoView).call(msg) |
111 | 120 … | msg.focus() |
app/sync/addPage.js | ||
---|---|---|
@@ -1,0 +1,26 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | + | |
3 … | +exports.gives = nest({ 'app.sync.addPage': true }) | |
4 … | + | |
5 … | +exports.needs = nest({ | |
6 … | + 'app.html.tabs': 'first', | |
7 … | + 'app.html.page': 'first' | |
8 … | +}) | |
9 … | + | |
10 … | +exports.create = function (api) { | |
11 … | + return nest({ | |
12 … | + 'app.sync': { addPage } | |
13 … | + }) | |
14 … | + | |
15 … | + // TODO : make it so error catching doesn't need this, move it into goTo | |
16 … | + function addPage (link, change, split) { | |
17 … | + const tabs = api.app.html.tabs() | |
18 … | + | |
19 … | + const page = api.app.html.page(link) | |
20 … | + if (!page) return | |
21 … | + | |
22 … | + page.id = page.id || link | |
23 … | + tabs.add(page, change, split) | |
24 … | + } | |
25 … | +} | |
26 … | + |
app/sync/goTo.js | ||
---|---|---|
@@ -1,0 +1,23 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | + | |
3 … | +exports.gives = nest({ 'app.sync.goTo': true }) | |
4 … | + | |
5 … | +exports.needs = nest({ | |
6 … | + 'app.html.tabs': 'first', | |
7 … | + 'app.sync.addPage': 'first' | |
8 … | +}) | |
9 … | + | |
10 … | +exports.create = function (api) { | |
11 … | + return nest('app.sync.goTo', function goTo (path, change) { | |
12 … | + const tabs = api.app.html.tabs() | |
13 … | + | |
14 … | + if (tabs.has(path)) { | |
15 … | + tabs.select(path) | |
16 … | + return true | |
17 … | + } | |
18 … | + | |
19 … | + api.app.sync.addPage(path, true, false) | |
20 … | + return change | |
21 … | + }) | |
22 … | +} | |
23 … | + |
message/html/compose.js | ||
---|---|---|
@@ -48,8 +48,9 @@ | ||
48 | 48 … | blurTimeout = setTimeout(() => channelInputFocused.set(false), 200) |
49 | 49 … | }, |
50 | 50 … | 'ev-focus': send(channelInputFocused.set, true), |
51 | 51 … | placeholder: '#channel (optional)', |
52 … | + value: computed(meta.channel, ch => ch ? '#' + ch : null), | |
52 | 53 … | disabled: when(meta.channel, true), |
53 | 54 … | title: when(meta.channel, 'Reply is in same channel as original message') |
54 | 55 … | }) |
55 | 56 … |
message/html/layout/default.mcss | ||
---|---|---|
@@ -95,8 +95,12 @@ | ||
95 | 95 … | font-size: .9rem |
96 | 96 … | a { |
97 | 97 … | margin-left: .5em |
98 | 98 … | } |
99 … | + | |
100 … | + a.unlike { | |
101 … | + _textSubtle | |
102 … | + } | |
99 | 103 … | } |
100 | 104 … | |
101 | 105 … | footer.backlinks { |
102 | 106 … | flex-basis: 100% |
message/html/render/follow.js | ||
---|---|---|
@@ -1,6 +1,7 @@ | ||
1 | 1 … | const nest = require('depnest') |
2 | 2 … | const extend = require('xtend') |
3 … | +const { isFeed } = require('ssb-ref') | |
3 | 4 … | |
4 | 5 … | exports.gives = nest('message.html.render') |
5 | 6 … | |
6 | 7 … | exports.needs = nest({ |
@@ -16,9 +17,9 @@ | ||
16 | 17 … | |
17 | 18 … | function follow (msg, opts) { |
18 | 19 … | const { type, contact, following } = msg.value.content |
19 | 20 … | if (type !== 'contact') return |
20 | - if (!contact) return | |
21 … | + if (!isFeed(contact)) return | |
21 | 22 … | |
22 | 23 … | const element = api.message.html.layout(msg, extend({ |
23 | 24 … | content: renderContent({ contact, following }), |
24 | 25 … | layout: 'mini' |
package.json | ||
---|---|---|
@@ -1,7 +1,7 @@ | ||
1 | 1 … | { |
2 | 2 … | "name": "patchbay", |
3 | - "version": "7.4.2", | |
3 … | + "version": "7.4.5", | |
4 | 4 … | "description": "patchbay 2, built on patchcore", |
5 | 5 … | "main": "index.js", |
6 | 6 … | "scripts": { |
7 | 7 … | "lint": "standard", |
@@ -52,9 +52,9 @@ | ||
52 | 52 … | "patchcore": "^0.5.0", |
53 | 53 … | "pull-abortable": "^4.1.1", |
54 | 54 … | "pull-cat": "^1.1.11", |
55 | 55 … | "pull-next": "1.0.0", |
56 | - "pull-scroll": "^1.0.3", | |
56 … | + "pull-scroll": "^1.0.4", | |
57 | 57 … | "pull-stream": "^3.5.0", |
58 | 58 … | "read-directory": "^2.0.0", |
59 | 59 … | "setimmediate": "^1.0.5", |
60 | 60 … | "ssb-horcrux": "0.1.2", |
Built with git-ssb-web