git ssb

16+

Dominic / patchbay



Commit 594c827ad16566b9d79bbceee0ee58002835b94d

Merge ../patchbay into fix-emoji-size

Anders Rune Jensen committed on 4/28/2017, 7:23:58 PM
Parent: 17137bf4950b512a952c46b8d1a92203756ed979
Parent: 90260656032e2a7c7b7a9d84c4afd6c1203422e5

Files changed

README.mdchanged
app/html/app.jschanged
app/html/filter.jschanged
app/html/filter.mcsschanged
app/html/menu.jschanged
app/html/page/channel.jschanged
app/html/page/search.jschanged
app/html/search-bar.jschanged
app/html/tabs.jsadded
app/sync/catch-keyboard-shortcut.jschanged
app/sync/addPage.jsadded
app/sync/goTo.jsadded
message/html/compose.jschanged
message/html/layout/default.mcsschanged
message/html/render/follow.jschanged
package.jsonchanged
README.mdView
@@ -16,8 +16,13 @@
1616 ```sh
1717 sudo apt-get install m4 libtool eclipse-cdt-autotools
1818 ```
1919
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 +
2025 Install Scuttlebot (your gossip server)
2126
2227 ```sh
2328 npm install scuttlebot@latest -g
app/html/app.jsView
@@ -1,8 +1,7 @@
1 +const nest = require('depnest')
12 const { h } = require('mutant')
2-const nest = require('depnest')
33 const insertCss = require('insert-css')
4-const Tabs = require('hypertabs')
54
65 exports.gives = nest('app.html.app')
76
87 exports.needs = nest({
@@ -12,13 +11,13 @@
1211 },
1312 html: {
1413 error: 'first',
1514 externalConfirm: 'first',
16- menu: 'first',
17- page: 'first',
18- searchBar: 'first'
15 + tabs: 'first',
16 + page: 'first'
1917 },
2018 sync: {
19 + addPage: 'first',
2120 catchKeyboardShortcut: 'first'
2221 }
2322 },
2423 'styles.css': 'reduce'
@@ -30,41 +29,16 @@
3029 function app () {
3130 const css = values(api.styles.css()).join('\n')
3231 insertCss(css)
3332
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
3936
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-
5137 const App = h('App', tabs)
5238
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-
6539 // Catch keyboard shortcuts
66- api.app.sync.catchKeyboardShortcut(window, { tabs, search })
40 + api.app.sync.catchKeyboardShortcut(window, { tabs })
6741
6842 // Catch link clicks
6943 api.app.async.catchLinkClick(App, (link, { ctrlKey: openBackground, isExternal }) => {
7044 if (isExternal) return api.app.html.externalConfirm(link)
app/html/filter.jsView
@@ -47,8 +47,9 @@
4747 h('i', {
4848 classList: when(showFilters, 'fa fa-filter -active', 'fa fa-filter'),
4949 'ev-click': () => showFilters.set(!showFilters())
5050 }),
51 + h('i.fa.fa-angle-up', { 'ev-click': draw }),
5152 h('div', { className: when(showFilters, '', '-hidden') }, [
5253 h('header', [
5354 'Filter',
5455 h('i.fa.fa-filter')
@@ -58,12 +59,8 @@
5859 h('label', 'Show author'),
5960 authorInput
6061 ]),
6162 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- ]),
6663 h('div.message-types', [
6764 h('header', 'Show messages'),
6865 toggle({ obs: showPost, label: 'post' }),
6966 toggle({ obs: showVote, label: 'like' }),
app/html/filter.mcssView
@@ -1,21 +1,28 @@
11 Filter {
22 display: flex
33 flex-direction: column
44
5- i.fa-filter {
5 + color: #444
6 +
7 + i {
68 position: absolute
79 top: 3rem
8- right: 2rem
910
1011 font-size: 1.2rem
1112 color: #b3b3b3
1213 cursor: pointer
14 + }
15 + i.fa-filter {
16 + right: 2rem
1317
1418 -active {
1519 color: #555
1620 }
1721 }
22 + i.fa-angle-up {
23 + left: 1rem
24 + }
1825
1926 div {
2027 margin: 0 .5rem .5rem .5rem
2128 padding: .5rem
@@ -41,19 +48,12 @@
4148 flex-wrap: wrap
4249 justify-content: space-between
4350 align-items: center
4451
45- div.refresh {
52 + div.author {
4653 label {
4754 margin-right: .4rem
4855 }
49-
50- }
51-
52- div.author {
53- label {
54- margin-right: .4rem
55- }
5656 input {
5757 border: 1px gainsboro solid
5858 font-size: 1rem
5959 }
@@ -61,9 +61,9 @@
6161
6262 div.message-types {
6363 margin: .6rem 0
6464 display: flex
65-
65 +
6666 header {
6767 margin-right: 1rem
6868 }
6969 }
app/html/menu.jsView
@@ -5,19 +5,23 @@
55
66 exports.needs = nest('app.html.menuItem', 'map')
77
88 exports.create = function (api) {
9- return nest('app.html.menu', menu)
9 + var _menu
1010
11- function menu (handleClick) {
11 + return nest('app.html.menu', function menu (handleClick) {
12 + if (_menu) return _menu
13 +
1214 var state = Value('')
1315
14- return h('Menu', {
16 + _menu = h('Menu', {
1517 classList: [ state ],
1618 'ev-mouseover': () => state.set('-active'),
1719 'ev-mouseout': () => state.set('')
1820 }, [
1921 h('div', api.app.html.menuItem(handleClick))
2022 ])
21- }
23 +
24 + return _menu
25 + })
2226 }
2327
app/html/page/channel.jsView
@@ -4,10 +4,13 @@
44
55 exports.gives = nest('app.html.page')
66
77 exports.needs = nest({
8 + 'app.html': {
9 + filter: 'first',
10 + scroller: 'first'
11 + },
812 'feed.pull.channel': 'first',
9- 'app.html.scroller': 'first',
1013 message: {
1114 html: {
1215 compose: 'first',
1316 render: 'first'
@@ -21,24 +24,31 @@
2124
2225 function channelView (path) {
2326 if (path && !path.match(/#[^\s]+/)) return
2427
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] })
2632
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 })
2935
30- var openChannelSource = api.feed.pull.channel(channel)
36 + const openChannelSource = api.feed.pull.channel(channel)
3137
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 + )
3643
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()
4151
4252 return container
4353 }
4454 }
app/html/page/search.jsView
@@ -8,9 +8,12 @@
88
99 exports.gives = nest('app.html.page')
1010
1111 exports.needs = nest({
12- 'app.html.scroller': 'first',
12 + 'app.html': {
13 + filter: 'first',
14 + scroller: 'first'
15 + },
1316 'message.html.render': 'first',
1417 'sbot.pull': {
1518 log: 'first',
1619 search: 'first'
@@ -108,41 +111,50 @@
108111 when(hasNoFulltextMatches, h('div.matches', 'No matches'))
109112 ])
110113 )
111114 ])
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] })
113117 container.id = path // helps tabs find this tab
114118
115119 function renderMsg (msg) {
116120 var el = api.message.html.render(msg)
117121 highlight(el, createOrRegExp(query))
118122 return el
119123 }
120124
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 })
126127
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 + )
144134
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 +
145157 return container
146158 }
147159 }
148160
app/html/search-bar.jsView
@@ -4,16 +4,20 @@
44
55 exports.gives = nest('app.html.searchBar')
66
77 exports.needs = nest({
8 + 'app.sync.goTo': 'first',
89 'about.async.suggest': 'first',
910 'channel.async.suggest': 'first'
1011 })
1112
1213 exports.create = function (api) {
13- return nest('app.html.searchBar', searchBar)
14 + var _search
1415
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
1620 const getProfileSuggestions = api.about.async.suggest()
1721 const getChannelSuggestions = api.channel.async.suggest()
1822
1923 const input = h('input', {
@@ -21,9 +25,9 @@
2125 placeholder: '?search, @name, #channel',
2226 'ev-keyup': ev => {
2327 switch (ev.keyCode) {
2428 case 13: // enter
25- if (go(input.value.trim(), !ev.ctrlKey)) {
29 + if (goTo(input.value.trim(), !ev.ctrlKey)) {
2630 input.blur()
2731 }
2832 return
2933 case 27: // escape
@@ -32,19 +36,18 @@
3236 return
3337 }
3438 }
3539 })
36- input.go = go // crude navigation api
3740 input.addEventListener('suggestselect', ev => {
3841 input.value = ev.detail.id // HACK : this over-rides the markdown value
3942
40- // if (go(input.value.trim(), !ev.ctrlKey))
43 + // if (goTo(input.value.trim(), !ev.ctrlKey))
4144 // input.blur()
4245 })
43- const search = h('SearchBar', input)
4446
45- search.input = input
46- search.activate = (sigil, ev) => {
47 + _search = h('SearchBar', input)
48 + _search.input = input
49 + _search.activate = (sigil, ev) => {
4750 input.focus()
4851 ev.preventDefault()
4952 if (input.value[0] === sigil) {
5053 input.selectionStart = 1
@@ -61,8 +64,8 @@
6164 cb(null, getChannelSuggestions(inputText.slice(1)))
6265 }
6366 }, {cls: 'SuggestBox'})
6467
65- return search
66- }
68 + return _search
69 + })
6770 }
6871
app/html/tabs.jsView
@@ -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.jsView
@@ -1,24 +1,36 @@
11 const nest = require('depnest')
22
33 exports.gives = nest('app.sync.catchKeyboardShortcut')
44
5 +exports.needs = nest({
6 + 'app.html': {
7 + searchBar: 'first',
8 + tabs: 'first'
9 + },
10 + 'app.sync': {
11 + goTo: 'first'
12 + }
13 +})
14 +
515 exports.create = function (api) {
616 return nest('app.sync.catchKeyboardShortcut', catchKeyboardShortcut)
717
8- function catchKeyboardShortcut (root, opts) {
18 + function catchKeyboardShortcut (root) {
919 var gPressed = false
1020
21 + var tabs = api.app.html.tabs()
22 + var search = api.app.html.searchBar()
23 + var goTo = api.app.sync.goTo
24 +
1125 root.addEventListener('keydown', (ev) => {
1226 isTextFieldEvent(ev)
1327 ? textFieldShortcuts(ev)
14- : genericShortcuts(ev, opts)
28 + : genericShortcuts(ev, { tabs, search, goTo })
1529 })
1630 }
1731 }
1832
19-// TODO build better apis for navigation, search, and publishing
20-
2133 function isTextFieldEvent (ev) {
2234 const tag = ev.target.nodeName
2335 return (tag === 'INPUT' || tag === 'TEXTAREA')
2436 }
@@ -28,30 +40,30 @@
2840 ev.target.publish() // expects the textField to have a publish method
2941 }
3042 }
3143
32-function genericShortcuts (ev, { tabs, search }) {
44 +function genericShortcuts (ev, { tabs, goTo, search }) {
3345 // Messages
3446 if (ev.keyCode === 71) { // gg = scroll to top
3547 if (!gPressed) {
3648 gPressed = true
3749 return
3850 }
39- tabs.get(tabs.selected[0]).firstChild.scroll('first')
51 + tabs.getCurrent().firstChild.scroll('first')
4052 }
4153 gPressed = false
4254
4355 switch (ev.keyCode) {
4456
4557 // Messages (cont'd)
4658 case 74: // j = older
47- return tabs.get(tabs.selected[0]).firstChild.scroll(1)
59 + return tabs.getCurrent().firstChild.scroll(1)
4860 case 75: // k = newer
49- return tabs.get(tabs.selected[0]).firstChild.scroll(-1)
61 + return tabs.getCurrent().firstChild.scroll(-1)
5062 case 13: // enter = open
51- return goToMessage(ev, tabs)
63 + return goToMessage(ev, { tabs, goTo })
5264 case 79: // o = open
53- return goToMessage(ev, tabs)
65 + return goToMessage(ev, { tabs, goTo })
5466 case 192: // ` = toggle raw message view
5567 return toggleRawMessage(ev)
5668
5769 // Tabs
@@ -84,28 +96,25 @@
8496 return
8597 }
8698 }
8799
88-function goToMessage (ev, tabs) {
100 +function goToMessage (ev, { tabs, goTo }) {
89101 const msg = ev.target
90102 if (!msg.classList.contains('Message')) return
91103
92- // this uses a crudely exported nav api
93- const search = document.querySelector('input[type=search]')
94-
95104 const { root, id } = msg.dataset
96- if (!root) return search.go(id)
105 + if (!root) return goTo(id)
97106
98- search.go(root)
107 + goTo(root)
99108 scrollDownToMessage(id, tabs)
100109 }
101110
102111 function scrollDownToMessage (id, tabs) {
103- tabs.get(tabs.selected[0]).firstChild.scroll('first')
112 + tabs.getCurrent().firstChild.scroll('first')
104113 locateKey()
105114
106115 function locateKey () {
107- const msg = tabs.get(tabs.selected[0]).querySelector(`[data-id='${id}']`)
116 + const msg = tabs.getCurrent().querySelector(`[data-id='${id}']`)
108117 if (msg === null) return setTimeout(locateKey, 100)
109118
110119 ;(msg.scrollIntoViewIfNeeded || msg.scrollIntoView).call(msg)
111120 msg.focus()
app/sync/addPage.jsView
@@ -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.jsView
@@ -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.jsView
@@ -48,8 +48,9 @@
4848 blurTimeout = setTimeout(() => channelInputFocused.set(false), 200)
4949 },
5050 'ev-focus': send(channelInputFocused.set, true),
5151 placeholder: '#channel (optional)',
52 + value: computed(meta.channel, ch => ch ? '#' + ch : null),
5253 disabled: when(meta.channel, true),
5354 title: when(meta.channel, 'Reply is in same channel as original message')
5455 })
5556
message/html/layout/default.mcssView
@@ -95,8 +95,12 @@
9595 font-size: .9rem
9696 a {
9797 margin-left: .5em
9898 }
99 +
100 + a.unlike {
101 + _textSubtle
102 + }
99103 }
100104
101105 footer.backlinks {
102106 flex-basis: 100%
message/html/render/follow.jsView
@@ -1,6 +1,7 @@
11 const nest = require('depnest')
22 const extend = require('xtend')
3 +const { isFeed } = require('ssb-ref')
34
45 exports.gives = nest('message.html.render')
56
67 exports.needs = nest({
@@ -16,9 +17,9 @@
1617
1718 function follow (msg, opts) {
1819 const { type, contact, following } = msg.value.content
1920 if (type !== 'contact') return
20- if (!contact) return
21 + if (!isFeed(contact)) return
2122
2223 const element = api.message.html.layout(msg, extend({
2324 content: renderContent({ contact, following }),
2425 layout: 'mini'
package.jsonView
@@ -1,7 +1,7 @@
11 {
22 "name": "patchbay",
3- "version": "7.4.2",
3 + "version": "7.4.5",
44 "description": "patchbay 2, built on patchcore",
55 "main": "index.js",
66 "scripts": {
77 "lint": "standard",
@@ -52,9 +52,9 @@
5252 "patchcore": "^0.5.0",
5353 "pull-abortable": "^4.1.1",
5454 "pull-cat": "^1.1.11",
5555 "pull-next": "1.0.0",
56- "pull-scroll": "^1.0.3",
56 + "pull-scroll": "^1.0.4",
5757 "pull-stream": "^3.5.0",
5858 "read-directory": "^2.0.0",
5959 "setimmediate": "^1.0.5",
6060 "ssb-horcrux": "0.1.2",

Built with git-ssb-web