git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Commit 84f42d561913f861b364caecc35050feadc23744

scroll to reply box on reply, context message on full thread

fixes #615
Matt McKegg committed on 9/30/2017, 5:14:29 AM
Parent: 0c2767265b52527a46422d9f034af38bc974b503

Files changed

lib/catch-links.jschanged
lib/anchor-hook.jsadded
locales/en.jsonchanged
main-window.jschanged
modules/app/views.jschanged
modules/feed/html/rollup.jschanged
modules/message/html/compose.jschanged
modules/message/sheet/likes.jschanged
modules/page/html/render/message.jschanged
overrides/patchcore/message/html/action/reply.jsadded
lib/catch-links.jsView
@@ -21,9 +21,9 @@
2121 var url = Url.parse(href)
2222 if (url.host) {
2323 cb(href, true)
2424 } else if (href !== '#') {
25- cb(href, false)
25+ cb(href, false, anchor.anchor)
2626 }
2727 }
2828
2929 ev.preventDefault()
lib/anchor-hook.jsView
@@ -1,0 +1,16 @@
1+var watch = require('mutant/watch')
2+
3+module.exports = AnchorHook
4+
5+function AnchorHook (name, current, cb) {
6+ return function (element) {
7+ return watch(current, (current) => {
8+ if (current === name) {
9+ window.requestAnimationFrame(() => {
10+ element.scrollIntoView()
11+ if (typeof cb === 'function') cb(element)
12+ })
13+ }
14+ })
15+ }
16+}
locales/en.jsonView
@@ -135,6 +135,7 @@
135135 "OK": "OK",
136136 "Close": "Close",
137137 "New Message": "New Message",
138138 "unsubscribed from ": "unsubscribed from ",
139- "en": "en"
139+ "en": "en",
140+ "on ": "on "
140141 }
main-window.jsView
@@ -44,15 +44,15 @@
4444 'intl.sync.i18n': 'first',
4545 }))
4646
4747 setupContextMenuAndSpellCheck(api.config.sync.load())
48-
48+
4949 const i18n = api.intl.sync.i18n
5050
5151 var id = api.keys.sync.id()
5252 var latestUpdate = LatestUpdate()
5353 var subscribedChannels = api.channel.obs.subscribed(id)
54-
54+
5555 // prompt to setup profile on first use
5656 onceTrue(api.sbot.obs.connection, (sbot) => {
5757 sbot.latestSequence(sbot.id, (_, key) => {
5858 if (key == null) {
@@ -125,9 +125,9 @@
125125 ),
126126 views.html
127127 ])
128128
129- catchLinks(container, (href, external) => {
129+ catchLinks(container, (href, external, anchor) => {
130130 if (external) {
131131 electron.shell.openExternal(href)
132132 } else if (ref.isBlob(href)) {
133133 electron.shell.openExternal(api.blob.sync.url(href))
@@ -136,13 +136,13 @@
136136 if (err) throw err
137137 if (handler) {
138138 handler(href)
139139 } else {
140- api.app.navigate(href)
140+ api.app.navigate(href, anchor)
141141 }
142142 })
143143 } else {
144- api.app.navigate(href)
144+ api.app.navigate(href, anchor)
145145 }
146146 })
147147
148148 return container
@@ -210,10 +210,10 @@
210210 }, title)
211211 return element
212212 }
213213
214- function setView (href) {
215- views.setView(href)
214+ function setView (href, anchor) {
215+ views.setView(href, anchor)
216216 }
217217
218218 function getExternalHandler (key, cb) {
219219 api.sbot.async.get(key, function (err, value) {
modules/app/views.jsView
@@ -98,9 +98,9 @@
9898 }
9999 }
100100 }
101101
102- function setView (view) {
102+ function setView (view, anchor) {
103103 loadView(view)
104104
105105 if (views.has(view)) {
106106 if (lastViewed[view] !== true) {
@@ -110,8 +110,14 @@
110110 if (currentView() && lastViewed[currentView()] !== true) {
111111 lastViewed[currentView()] = Date.now()
112112 }
113113
114+ var viewElement = views.get(view)
115+
116+ if (viewElement && typeof viewElement.setAnchor === 'function') {
117+ viewElement.setAnchor(anchor)
118+ }
119+
114120 if (view !== currentView()) {
115121 canGoForward.set(false)
116122 canGoBack.set(true)
117123 forwardHistory.length = 0
modules/feed/html/rollup.jsView
@@ -208,16 +208,22 @@
208208 meta,
209209 renderedMessage,
210210 when(replyElements.length, [
211211 when(replies.length > replyElements.length || partial,
212- h('a.full', {href: item.key}, [i18n('View full thread') +' (', replies.length, ')'])
212+ h('a.full', {href: item.key, anchor: getFirstId(replyElements)}, [i18n('View full thread') + ' (', replies.length, ')'])
213213 ),
214214 h('div.replies', replyElements)
215215 ])
216216 ])
217217 }
218218 })
219219
220+ function getFirstId (elements) {
221+ if (Array.isArray(elements) && elements.length) {
222+ return elements[0].dataset.id
223+ }
224+ }
225+
220226 function names (ids) {
221227 var items = map(Array.from(ids), api.about.obs.name)
222228 return computed([items], (names) => names.map((n) => `- ${n}`).join('\n'))
223229 }
modules/message/html/compose.jsView
@@ -22,9 +22,9 @@
2222 exports.gives = nest('message.html.compose')
2323
2424 exports.create = function (api) {
2525 const i18n = api.intl.sync.i18n
26- return nest('message.html.compose', function ({shrink = true, meta, prepublish, placeholder = 'Write a message'}, cb) {
26+ return nest('message.html.compose', function ({shrink = true, meta, hooks, prepublish, placeholder = 'Write a message'}, cb) {
2727 var files = []
2828 var filesById = {}
2929 var focused = Value(false)
3030 var hasContent = Value(false)
@@ -105,8 +105,9 @@
105105 publishBtn
106106 ])
107107
108108 var composer = h('Compose', {
109+ hooks,
109110 classList: [
110111 when(expanded, '-expanded', '-contracted')
111112 ]
112113 }, [
@@ -114,8 +115,12 @@
114115 warning,
115116 actions
116117 ])
117118
119+ composer.focus = function () {
120+ textArea.focus()
121+ }
122+
118123 addSuggest(textArea, (inputText, cb) => {
119124 if (inputText[0] === '@') {
120125 cb(null, getProfileSuggestions(inputText.slice(1)))
121126 } else if (inputText[0] === '#') {
modules/message/sheet/likes.jsView
@@ -27,11 +27,11 @@
2727 }, [i18n('Liked by')]),
2828 renderContactBlock(ids)
2929 ])
3030
31- catchLinks(content, (href, external) => {
31+ catchLinks(content, (href, external, anchor) => {
3232 if (!external) {
33- api.app.navigate(href)
33+ api.app.navigate(href, anchor)
3434 close()
3535 }
3636 })
3737
modules/page/html/render/message.jsView
@@ -1,7 +1,8 @@
11 var { h, when, map, Proxy, Struct, Value, computed } = require('mutant')
22 var nest = require('depnest')
33 var ref = require('ssb-ref')
4+var AnchorHook = require('../../../../lib/anchor-hook')
45
56 exports.needs = nest({
67 'keys.sync.id': 'first',
78 'feed.obs.thread': 'first',
@@ -10,9 +11,9 @@
1011 render: 'first',
1112 compose: 'first'
1213 },
1314 'sbot.async.get': 'first',
14- 'intl.sync.i18n':'first',
15+ 'intl.sync.i18n': 'first'
1516 })
1617
1718 exports.gives = nest('page.html.render')
1819
@@ -22,8 +23,9 @@
2223 if (!ref.isMsg(id)) return
2324 var loader = h('div', {className: 'Loading -large'})
2425
2526 var result = Proxy(loader)
27+ var anchor = Value()
2628
2729 var meta = Struct({
2830 type: 'post',
2931 root: Proxy(id),
@@ -34,8 +36,11 @@
3436
3537 var compose = api.message.html.compose({
3638 meta,
3739 shrink: false,
40+ hooks: [
41+ AnchorHook('reply', anchor, (el) => el.focus())
42+ ],
3843 placeholder: when(meta.recps, i18n('Write a private reply'), i18n('Write a public reply'))
3944 })
4045
4146 api.sbot.async.get(id, (err, value) => {
@@ -68,12 +73,20 @@
6873 meta.branch.set(isReply ? thread.branchId : thread.lastId)
6974
7075 var container = h('Thread', [
7176 h('div.messages', [
72- when(thread.branchId, h('a.full', {href: thread.rootId}, [i18n('View full thread')])),
77+ when(thread.branchId, h('a.full', {href: thread.rootId, anchor: id}, [i18n('View full thread')])),
7378 map(thread.messages, (msg) => {
7479 return computed([msg, thread.previousKey(msg)], (msg, previousId) => {
75- return api.message.html.render(msg, {pageId: id, previousId, includeReferences: true})
80+ return h('div', {
81+ hooks: [AnchorHook(msg.key, anchor, showContext)]
82+ }, [
83+ api.message.html.render(msg, {
84+ pageId: id,
85+ previousId,
86+ includeReferences: true
87+ })
88+ ])
7689 })
7790 }, {
7891 maxTime: 5,
7992 idle: true
@@ -83,11 +96,35 @@
8396 ])
8497 result.set(when(thread.sync, container, loader))
8598 })
8699
87- return h('div', {className: 'SplitView'}, [
100+ var view = h('div', {className: 'SplitView'}, [
88101 h('div.main', [
89102 result
90103 ])
91104 ])
105+
106+ view.setAnchor = function (value) {
107+ anchor.set(value)
108+ }
109+
110+ return view
92111 })
93112 }
113+
114+function showContext (element) {
115+ var scrollParent = getScrollParent(element)
116+ if (scrollParent) {
117+ // ensure context is visible
118+ scrollParent.scrollTop = Math.max(0, scrollParent.scrollTop - 100)
119+ }
120+}
121+
122+function getScrollParent (element) {
123+ while (element.parentNode) {
124+ if (element.parentNode.scrollTop) {
125+ return element.parentNode
126+ } else {
127+ element = element.parentNode
128+ }
129+ }
130+}
overrides/patchcore/message/html/action/reply.jsView
@@ -1,0 +1,10 @@
1+var h = require('mutant/h')
2+var nest = require('depnest')
3+
4+exports.gives = nest('message.html.action')
5+
6+exports.create = (api) => {
7+ return nest('message.html.action', function reply (msg) {
8+ return h('a', { href: msg.key, anchor: 'reply' }, 'Reply')
9+ })
10+}

Built with git-ssb-web