git ssb

2+

mixmix / ticktack



Commit 7132de8591e3a3e7dde989835cf9c5fb52382791

Merge pull request #134 from ticktackim/feature-external-share

Feature external share
mix irving authored on 5/8/2018, 8:06:04 AM
GitHub committed on 5/8/2018, 8:06:04 AM
Parent: 1b46fe809a87f906b19ab7dad059971d13730b4f
Parent: 0b16e011754d8816fc45b0d388ab692c97826bf1

Files changed

app/index.jschanged
app/page/blogShow.jschanged
app/page/blogShow.mcsschanged
app/page/settings.jschanged
app/page/settings.mcsschanged
app/sync/initialize/clickHandler.jschanged
app/sync/initialize/styles.jschanged
app/sync/initialize/suggests.jschanged
app/sync/initialize/zoomMemory.jschanged
app/sync/initialize/settings.jsadded
message/html/likes.jschanged
message/html/likes.mcsschanged
message/html/subject.jschanged
message/html/shares.jsadded
message/html/shares.mcssadded
message/html/webshares.jsadded
message/html/webshares.mcssadded
message/index.jschanged
message/obs/shares.jsadded
message/obs/webshares.jsadded
package-lock.jsonchanged
package.jsonchanged
translations/en.jschanged
app/index.jsView
@@ -20,12 +20,12 @@
2020 sideNav: {
2121 addressBook: require('./html/sideNav/sideNavAddressBook'),
2222 discovery: require('./html/sideNav/sideNavDiscovery')
2323 },
24- warning: require('./html/warning'),
24+ warning: require('./html/warning')
2525 },
2626 obs: {
27- pluginsOk: require('./obs/pluginsOk'),
27+ pluginsOk: require('./obs/pluginsOk')
2828 },
2929 page: {
3030 addressBook: require('./page/addressBook'),
3131 blogIndex: require('./page/blogIndex'),
@@ -53,8 +53,9 @@
5353 },
5454 sync: {
5555 initialize: {
5656 clickHandler: require('./sync/initialize/clickHandler'),
57+ settings: require('./sync/initialize/settings'),
5758 styles: require('./sync/initialize/styles'),
5859 suggests: require('./sync/initialize/suggests'),
5960 zoomMemory: require('./sync/initialize/zoomMemory')
6061 }
app/page/blogShow.jsView
@@ -11,8 +11,10 @@
1111 'app.html.sideNav': 'first',
1212 'contact.html.follow': 'first',
1313 'message.html.channel': 'first',
1414 'message.html.likes': 'first',
15+ 'message.html.webshares': 'first',
16+ 'message.html.shares': 'first',
1517 'message.html.timeago': 'first',
1618 'feed.obs.thread': 'first',
1719 'blog.html.title': 'first',
1820 'blog.html.content': 'first'
@@ -20,9 +22,9 @@
2022
2123 exports.create = (api) => {
2224 return nest('app.page.blogShow', blogShow)
2325
24- function blogShow (blogMsg) {
26+ function blogShow(blogMsg) {
2527 // blogMsg = a thread (message, may be decorated with replies)
2628
2729 const { author } = blogMsg.value
2830
@@ -45,9 +47,11 @@
4547 h('div.blog-details', [
4648 h('h1', title),
4749 timeago(blogMsg),
4850 channel(blogMsg),
49- api.message.html.likes(blogMsg)
51+ api.message.html.likes(blogMsg),
52+ api.message.html.shares(blogMsg),
53+ api.message.html.webshares(blogMsg)
5054 ]),
5155 h('div.author', [
5256 h('div.leftCol', api.about.html.avatar(author, 'medium')),
5357 h('div.rightCol', [
app/page/blogShow.mcssView
@@ -46,9 +46,16 @@
4646 div.Button.-channel {
4747 margin-right: 1rem
4848 }
4949 div.Likes {
50+ margin-right: 2rem
5051 }
52+ div.Shares {
53+ margin-right: 2rem
54+ }
55+ div.Likes {
56+ margin-right: 2rem
57+ }
5158 }
5259
5360 div.author {
5461 display: flex
app/page/settings.jsView
@@ -1,6 +1,6 @@
11 const nest = require('depnest')
2-const { h, computed } = require('mutant')
2+const { h, computed, when } = require('mutant')
33 const electron = require('electron')
44 const path = require('path')
55 const { version } = require('../../package.json')
66
@@ -21,14 +21,8 @@
2121 })
2222
2323 const LANGUAGES = ['zh', 'en']
2424
25-// TODO - this needs moving somewhere upstream
26-// const DEFAULT_SETTINGS = {
27-// onboarded: false,
28-// language: 'zh'
29-// }
30-
3125 exports.create = (api) => {
3226 return nest('app.page.settings', settings)
3327
3428 function settings (location) {
@@ -39,11 +33,12 @@
3933 // clear history back to start page
4034 api.history.obs.store().set([
4135 { page: 'blogIndex' }
4236 ])
43- api.history.sync.push({page: 'settings'})
37+ api.history.sync.push({ page: 'settings' })
4438 })
4539
40+ const webSharingMetricsOption = api.settings.obs.get('ticktack.websharemetrics')
4641 const feed = api.keys.sync.id()
4742 const strings = api.translations.sync.strings()
4843 const currentLanguage = api.settings.sync.get('language')
4944
@@ -83,10 +78,18 @@
8378 h('div.right', LANGUAGES.map(Language))
8479 ]),
8580 h('section -zoom', [
8681 h('div.left', strings.settingsPage.section.zoom),
87- h('div.right', [ zoomButton(-0.1, '-'), zoomButton(+0.1, '+') ])
82+ h('div.right', [zoomButton(-0.1, '-'), zoomButton(+0.1, '+')])
8883 ]),
84+ h('section -sharing', [
85+ h('div.left', strings.share.settings.caption),
86+ h('div.right', [].concat(
87+ webSharingOption('public', strings.share.settings.publicOption),
88+ webSharingOption('author', strings.share.settings.authorAndYouOption),
89+ webSharingOption('private', strings.share.settings.justYouOption)
90+ ))
91+ ]),
8992 h('section -version', [
9093 h('div.left', strings.settingsPage.section.version),
9194 h('div.right', version)
9295 ])
@@ -112,13 +115,29 @@
112115 {
113116 'ev-click': () => {
114117 var zoomFactor = api.settings.sync.get('ticktack.electron.zoomFactor', 1)
115118 var newZoomFactor = zoomFactor + increment
116- var zoomFactor = api.settings.sync.set('ticktack.electron.zoomFactor', newZoomFactor)
119+ api.settings.sync.set('ticktack.electron.zoomFactor', newZoomFactor)
117120 getCurrentWebContents().setZoomFactor(newZoomFactor)
118121 }
119122 },
120123 symbol
121124 )
122125 }
126+
127+ function webSharingOption (v, label) {
128+ let myOption = computed(webSharingMetricsOption, opt => opt === v)
129+
130+ const selectWebSharingOption = () => {
131+ webSharingMetricsOption.set(v)
132+ }
133+
134+ return h('Button -websharingmetrics',
135+ {
136+ 'ev-click': () => selectWebSharingOption(v),
137+ className: when(myOption, '-strong', '')
138+ },
139+ label
140+ )
141+ }
123142 }
124143 }
app/page/settings.mcssView
@@ -55,22 +55,37 @@
5555 margin-bottom: 2rem
5656 }
5757
5858 -version {
59- margin-top: 2rem
60-
6159 div.right {
6260 $colorFontSubtle
6361 }
6462 }
63+
64+ -sharing {
65+ div.left {
66+ align-items: flex-start
67+ padding-top: .8rem
68+ }
69+ div.right {
70+ flex-direction: column
71+ align-items: flex-start
72+ }
73+ }
6574 }
6675 }
6776 }
6877
6978 Button -language {
7079 margin-right: 1rem
7180 }
7281
82+
83+Button -websharingmetrics {
84+ margin-right: 1rem
85+ margin-bottom: 1rem
86+}
87+
7388 Button -zoom {
7489 margin-right: 1rem
7590 min-width: .5rem
7691 }
app/sync/initialize/clickHandler.jsView
@@ -10,8 +10,9 @@
1010
1111 exports.create = (api) => {
1212 return nest({
1313 'app.sync.initialize': function initializeClickHandling () {
14+ console.log('> initialise: clickHandler')
1415 const target = document.body
1516
1617 api.app.async.catchLinkClick(target, (link, { isExternal }) => {
1718 if (isExternal) return openExternal(link)
app/sync/initialize/styles.jsView
@@ -10,8 +10,9 @@
1010
1111 exports.create = (api) => {
1212 return nest({
1313 'app.sync.initialize': function initializeStyles () {
14+ console.log('> initialise: styles')
1415 const css = values(api.styles.css()).join('\n')
1516 insertCss(css)
1617 }
1718 })
app/sync/initialize/suggests.jsView
@@ -12,8 +12,9 @@
1212 var nav = null
1313
1414 return nest({
1515 'app.sync.initialize': function initializeSuggests () {
16+ console.log('> initialise: suggest-mentions')
1617 api.about.async.suggest()
1718 api.channel.async.suggest()
1819 // api.channel.obs.recent()() TODO - figure out how to initialise this store
1920 }
app/sync/initialize/zoomMemory.jsView
@@ -11,8 +11,9 @@
1111 exports.create = (api) => {
1212 return nest('app.sync.initialize', zoomMemory)
1313
1414 function zoomMemory () {
15+ console.log('> initialise: window form')
1516 const { getCurrentWebContents, getCurrentWindow } = electron.remote
1617
1718 window.addEventListener('resize', () => {
1819 var wc = getCurrentWebContents()
app/sync/initialize/settings.jsView
@@ -1,0 +1,33 @@
1+const nest = require('depnest')
2+const merge = require('lodash/merge')
3+
4+exports.gives = nest('app.sync.initialize')
5+
6+exports.needs = nest({
7+ 'settings.sync.set': 'first',
8+ 'settings.sync.get': 'first'
9+})
10+
11+const defaults = {
12+ onboarded: false,
13+ language: 'en',
14+ ticktack: {
15+ websharemetrics: 'public',
16+ electron: {
17+ zoomFactor: 1
18+ }
19+ }
20+}
21+
22+exports.create = function (api) {
23+ return nest('app.sync.initialize', initialiseSettings)
24+
25+ function initialiseSettings () {
26+ console.log('> initialise: default settings')
27+ const { get, set } = api.settings.sync
28+ const settings = merge({}, defaults, get())
29+
30+ console.log('dog', settings)
31+ set(settings)
32+ }
33+}
message/html/likes.jsView
@@ -9,9 +9,9 @@
99
1010 exports.gives = nest('message.html.likes')
1111
1212 exports.create = (api) => {
13- return nest('message.html.likes', function likes (msg) {
13+ return nest('message.html.likes', function likes(msg) {
1414 var id = api.keys.sync.id()
1515 var likes = api.message.obs.likes(msg.key)
1616
1717 var iLike = computed(likes, likes => likes.includes(id))
@@ -22,18 +22,18 @@
2222 h('div.count', count)
2323 ])
2424 })
2525
26- function publishLike (msg, status = true) {
26+ function publishLike(msg, status = true) {
2727 var like = status ? {
2828 type: 'vote',
2929 channel: msg.value.content.channel,
3030 vote: { link: msg.key, value: 1, expression: 'Like' }
3131 } : {
32- type: 'vote',
33- channel: msg.value.content.channel,
34- vote: { link: msg.key, value: 0, expression: 'Unlike' }
35- }
32+ type: 'vote',
33+ channel: msg.value.content.channel,
34+ vote: { link: msg.key, value: 0, expression: 'Unlike' }
35+ }
3636 if (msg.value.content.recps) {
3737 like.recps = msg.value.content.recps.map(function (e) {
3838 return e && typeof e !== 'string' ? e.link : e
3939 })
message/html/likes.mcssView
@@ -2,9 +2,9 @@
22 cursor: pointer
33 min-width: 2.5rem
44
55 display: flex
6- align-items: center
6+ align-items: baseline
77
88 i {
99 margin-right: .4rem
1010 }
message/html/subject.jsView
@@ -15,10 +15,9 @@
1515 var subjectCache = {}
1616
1717 return nest('message.html.subject', subject)
1818
19- function subject (msg) {
20- if (msg === undefined) debugger
19+ function subject(msg) {
2120 // test if it's a message ref, or a full message object
2221 // a message ref is generally passed in if we're fetching the subject of a root message
2322 if (isMsg(msg)) {
2423 if (subjectCache[msg]) return subjectCache[msg]
@@ -26,19 +25,23 @@
2625 var subject = Value()
2726
2827 api.sbot.async.get(msg, (err, value) => {
2928 if (err) throw err
29+ try {
30+ var _subject = getMsgSubject({ key: msg, value: api.message.sync.unbox(value) })
31+ subject.set(_subject)
32+ subjectCache[msg] = _subject
33+ } catch (n) {
34+ subject.set("broken message")
35+ }
3036
31- var _subject = getMsgSubject({ key: msg, value: api.message.sync.unbox(value) })
32- subject.set(_subject)
33- subjectCache[msg] = _subject
3437 })
3538
3639 return subject
3740 } else { return getMsgSubject(msg) }
3841 }
3942
40- function getMsgSubject (msg) {
43+ function getMsgSubject(msg) {
4144 const { subject, text } = msg.value.content
4245 if (!(subject || text)) return
4346
4447 return subject
message/html/shares.jsView
@@ -1,0 +1,71 @@
1+var { h, computed, when, Value, resolve } = require('mutant')
2+var nest = require('depnest')
3+
4+
5+exports.needs = nest({
6+ 'keys.sync.id': 'first',
7+ 'message.obs.shares': 'first',
8+ 'sbot.async.publish': 'first',
9+ 'translations.sync.strings': 'first',
10+ 'app.html.lightbox': 'first'
11+})
12+
13+exports.gives = nest('message.html.shares')
14+
15+exports.create = (api) => {
16+ return nest('message.html.shares', function shares(msg) {
17+ var id = api.keys.sync.id()
18+ var shares = api.message.obs.shares(msg.key)
19+
20+ var iShared = computed(shares, shares => shares.includes(id))
21+ var count = computed(shares, shares => shares.length ? shares.length : '')
22+ var isOpen = Value(false)
23+ var strings = api.translations.sync.strings()
24+ var captionRaw = Value('')
25+ var captionInput = h('textarea#caption', {
26+ style: {
27+ width: '90%'
28+ },
29+ placeholder: strings.share.captionPlaceholder,
30+ value: captionRaw,
31+ 'ev-input': () => captionRaw.set(captionInput.value),
32+ })
33+
34+ var confirmationDialog = h('div.dialog', [
35+ h('div.message', [
36+ h('p', strings.share.shareLabel),
37+ ]),
38+ h('div.form', [
39+ captionInput
40+ ]),
41+ h('div.actions', [
42+ h('Button', { 'ev-click': () => isOpen.set(false) }, strings.userShow.action.cancel),
43+ h('Button -primary', { 'ev-click': () => publishShare(msg, resolve(captionRaw), () => isOpen.set(false)) }, strings.share.action.share)
44+ ])
45+ ])
46+
47+ var lb = api.app.html.lightbox(confirmationDialog, isOpen)
48+
49+
50+ return h('Shares', { 'ev-click': () => isOpen.set(!iShared()) }, [
51+ h('i.fa.fa-retweet', { className: when(iShared, '', 'faint') }),
52+ h('div.count', count),
53+ lb
54+ ])
55+ })
56+
57+ function publishShare(msg, text, cb) {
58+ var share = {
59+ type: 'share',
60+ share: { link: msg.key, content: "blog", text: text }
61+ }
62+ if (msg.value.content.recps) {
63+ share.recps = msg.value.content.recps.map(function (e) {
64+ return e && typeof e !== 'string' ? e.link : e
65+ })
66+ share.private = true
67+ }
68+ console.log("publishing share", share)
69+ api.sbot.async.publish(share, cb)
70+ }
71+}
message/html/shares.mcssView
@@ -1,0 +1,31 @@
1+Shares {
2+ cursor: pointer
3+ min-width: 2.5rem
4+
5+ display: flex
6+ align-items: baseline
7+
8+ i {
9+ margin-right: .4rem
10+ }
11+ i.fa-retweet {
12+ $colorFontPrimary
13+ }
14+
15+ i.faint {
16+ color: #b9b9b9
17+ }
18+
19+}
20+
21+textarea#caption {
22+ $fontBasic
23+
24+ padding: 1rem
25+ border-radius: 1rem
26+ $borderSubtle
27+ margin-bottom: .5rem
28+
29+ outline: none
30+}
31+
message/html/webshares.jsView
@@ -1,0 +1,91 @@
1+var { h, computed, when, Value, resolve } = require('mutant')
2+var nest = require('depnest')
3+var { clipboard, shell } = require('electron')
4+
5+exports.needs = nest({
6+ 'keys.sync.id': 'first',
7+ 'message.obs.webshares': 'first',
8+ 'sbot.async.publish': 'first',
9+ 'translations.sync.strings': 'first',
10+ 'app.html.lightbox': 'first',
11+ 'settings.sync.get': 'first'
12+})
13+
14+exports.gives = nest('message.html.webshares')
15+
16+exports.create = (api) => {
17+ return nest('message.html.webshares', function shares (msg) {
18+ var id = api.keys.sync.id()
19+ var shares = api.message.obs.webshares(msg.key)
20+
21+ var iShared = computed(shares, shares => shares.includes(id))
22+ var count = computed(shares, shares => shares.length ? shares.length : '')
23+ var isOpen = Value(false)
24+ var strings = api.translations.sync.strings()
25+ var publishAndClose = (msg, action) => {
26+ publishShare(msg, action)
27+ isOpen.set(false)
28+ }
29+ var confirmationDialog = h('div.dialog', [
30+ h('div.message', [
31+ h('p', strings.share.externalShareLabel)
32+ ]),
33+ h('div.actions', [
34+ h('Button', { 'ev-click': () => isOpen.set(false) }, strings.userShow.action.cancel),
35+ h('Button', { style: { 'margin-left': '10px', 'margin-right': '10px' }, 'ev-click': () => publishAndClose(msg, 'copy') }, strings.share.action.copy),
36+ h('Button -primary', { 'ev-click': () => publishAndClose(msg, 'open') }, strings.share.action.open)
37+
38+ ])
39+ ])
40+
41+ var lb = api.app.html.lightbox(confirmationDialog, isOpen)
42+
43+ return h('WebShares', { 'ev-click': () => isOpen.set(true) }, [
44+ h('i.fa.fa-share-alt', { className: when(iShared, '', 'faint') }),
45+ h('div.count', count),
46+ lb
47+ ])
48+ })
49+
50+ function publishShare (msg, action) {
51+ var sharingScope = api.settings.sync.get('websharingmetrics')
52+ var url = `http://share2.ticktack.im:8807/${encodeURIComponent(msg.key)}`
53+ var share = {
54+ type: 'share',
55+ share: { link: msg.key, content: 'blog', url: url }
56+ }
57+ // if it was a private message, then share as private ...
58+ if (msg.value.content.recps) {
59+ share.recps = msg.value.content.recps.map(function (e) {
60+ return e && typeof e !== 'string' ? e.link : e
61+ })
62+ share.private = true
63+ } else {
64+ // ... else obey configuration settings.
65+ switch (sharingScope) {
66+ case 'private':
67+ share.private = true
68+ break
69+ case 'author':
70+ share.recps = [
71+ id,
72+ msg.value.author
73+ ]
74+ share.private = true
75+ break
76+ case 'public':
77+ default:
78+ break
79+ }
80+ }
81+ api.sbot.async.publish(share)
82+
83+ if (action == 'copy') {
84+ console.log('copying to clipboard')
85+ clipboard.writeText(url)
86+ } else {
87+ console.log('opening external')
88+ shell.openExternal(url, err => console.log('error', err))
89+ }
90+ }
91+}
message/html/webshares.mcssView
@@ -1,0 +1,20 @@
1+WebShares {
2+ cursor: pointer
3+ min-width: 2.5rem
4+
5+ display: flex
6+ align-items: baseline
7+
8+ i {
9+ margin-right: .4rem
10+ }
11+ i.fa-share-alt {
12+ $colorFontPrimary
13+ }
14+
15+ i.faint {
16+ color: #b9b9b9
17+ }
18+
19+}
20+
message/index.jsView
@@ -5,11 +5,17 @@
55 html: {
66 channel: require('./html/channel'),
77 compose: require('./html/compose'),
88 likes: require('./html/likes'),
9+ shares: require('./html/shares'),
10+ webshares: require('./html/webshares'),
911 subject: require('./html/subject'),
1012 timeago: require('./html/timeago')
1113 },
1214 sync: {
1315 getParticipants: require('./sync/getParticipants')
16+ },
17+ obs: {
18+ shares: require('./obs/shares'),
19+ webshares: require('./obs/webshares')
1420 }
1521 }
message/obs/shares.jsView
@@ -1,0 +1,96 @@
1+var nest = require('depnest')
2+var ref = require('ssb-ref')
3+var MutantArray = require('mutant/array')
4+var concat = require('mutant/concat')
5+
6+var { computed } = require('mutant')
7+
8+exports.needs = nest({
9+ 'message.sync.unbox': 'first',
10+ 'backlinks.obs.for': 'first'
11+})
12+
13+exports.gives = nest({
14+ 'sbot.hook.publish': true,
15+ 'message.obs.shares': true
16+})
17+
18+function isShare(c) {
19+ if (c.type !== 'share') return false
20+
21+ if (!c.share || !c.share.link || !ref.isMsg(c.share.link) || !c.share.hasOwnProperty('text')) {
22+ return false
23+ }
24+
25+ return true
26+}
27+
28+exports.create = function (api) {
29+ var activeShares = new Set()
30+ return nest({
31+ 'sbot.hook.publish': (msg) => {
32+ if (!(msg && msg.value && msg.value.content)) return
33+ if (typeof msg.value.content === 'string') {
34+ msg = api.message.sync.unbox(msg)
35+ if (!msg) return
36+ }
37+
38+ var c = msg.value.content
39+ if (c.type !== 'share') return
40+ if (!isShare(c)) return
41+
42+ activeShares.forEach((shares) => {
43+ if (shares.id === c.share.link) {
44+ shares.push(msg)
45+ }
46+ })
47+ },
48+ 'message.obs.shares': (id) => {
49+ if (!ref.isLink(id)) throw new Error('an id must be specified')
50+ var obs = get(id)
51+ obs.id = id
52+ var result = computed(obs, getShares, {
53+ // allow manual append for simulated realtime
54+ onListen: () => activeShares.add(obs),
55+ onUnlisten: () => activeShares.delete(obs)
56+ })
57+ result.sync = obs.sync
58+ return result
59+ }
60+ })
61+
62+ function get(id) {
63+ var backlinks = api.backlinks.obs.for(id)
64+ var merge = MutantArray()
65+
66+ var shares = computed([backlinks.sync, concat([backlinks, merge])], (sync, backlinks) => {
67+ if (sync) {
68+ return backlinks.reduce((result, msg) => {
69+ var c = msg.value.content
70+ if (isShare(c) && c.share.link === id) {
71+ var value = result[msg.value.author]
72+ if (!value || value[0] < msg.value.timestamp) {
73+ result[msg.value.author] = [msg.value.timestamp, c.share.text]
74+ }
75+ }
76+ return result
77+ }, {})
78+ } else {
79+ return {}
80+ }
81+ })
82+
83+ shares.push = merge.push
84+ shares.sync = backlinks.sync
85+ return shares
86+ }
87+}
88+
89+function getShares(shares) {
90+ return Object.keys(shares).reduce((result, id) => {
91+ if (shares[id].length >= 1) {
92+ result.push(id)
93+ }
94+ return result
95+ }, [])
96+}
message/obs/webshares.jsView
@@ -1,0 +1,98 @@
1+var nest = require('depnest')
2+var ref = require('ssb-ref')
3+var MutantArray = require('mutant/array')
4+var concat = require('mutant/concat')
5+
6+var { computed } = require('mutant')
7+
8+exports.needs = nest({
9+ 'message.sync.unbox': 'first',
10+ 'backlinks.obs.for': 'first'
11+})
12+
13+exports.gives = nest({
14+ 'sbot.hook.publish': true,
15+ 'message.obs.webshares': true
16+})
17+
18+
19+function isShare(c) {
20+ if (c.type !== 'share') return false
21+
22+ if (!c.share || !c.share.link || !ref.isMsg(c.share.link) || !c.share.hasOwnProperty('url')) {
23+ return false
24+ }
25+
26+ return true
27+}
28+
29+
30+exports.create = function (api) {
31+ var activeShares = new Set()
32+ return nest({
33+ 'sbot.hook.publish': (msg) => {
34+ if (!(msg && msg.value && msg.value.content)) return
35+ if (typeof msg.value.content === 'string') {
36+ msg = api.message.sync.unbox(msg)
37+ if (!msg) return
38+ }
39+
40+ var c = msg.value.content
41+
42+ if (!isShare(c)) return
43+
44+ activeShares.forEach((shares) => {
45+ if (shares.id === c.share.link) {
46+ shares.push(msg)
47+ }
48+ })
49+ },
50+ 'message.obs.webshares': (id) => {
51+ if (!ref.isLink(id)) throw new Error('an id must be specified')
52+ var obs = get(id)
53+ obs.id = id
54+ var result = computed(obs, getShares, {
55+ // allow manual append for simulated realtime
56+ onListen: () => activeShares.add(obs),
57+ onUnlisten: () => activeShares.delete(obs)
58+ })
59+ result.sync = obs.sync
60+ return result
61+ }
62+ })
63+
64+ function get(id) {
65+ var backlinks = api.backlinks.obs.for(id)
66+ var merge = MutantArray()
67+
68+ var shares = computed([backlinks.sync, concat([backlinks, merge])], (sync, backlinks) => {
69+ if (sync) {
70+ return backlinks.reduce((result, msg) => {
71+ var c = msg.value.content
72+ if (isShare(c) && c.share.link === id) {
73+ var value = result[msg.value.author]
74+ if (!value || value[0] < msg.value.timestamp) {
75+ result[msg.value.author] = [msg.value.timestamp, c.share.url]
76+ }
77+ }
78+ return result
79+ }, {})
80+ } else {
81+ return {}
82+ }
83+ })
84+
85+ shares.push = merge.push
86+ shares.sync = backlinks.sync
87+ return shares
88+ }
89+}
90+
91+function getShares(shares) {
92+ return Object.keys(shares).reduce((result, id) => {
93+ if (shares[id].length >= 1) {
94+ result.push(id)
95+ }
96+ return result
97+ }, [])
98+}
package-lock.jsonView
The diff is too large to show. Use a local git client to view these changes.
Old file size: 250843 bytes
New file size: 283856 bytes
package.jsonView
@@ -46,9 +46,9 @@
4646 "open-external": "^0.1.1",
4747 "patch-drafts": "0.0.6",
4848 "patch-history": "^1.0.0",
4949 "patch-profile": "^1.0.4",
50- "patch-settings": "^1.0.0",
50+ "patch-settings": "^1.1.2",
5151 "patch-suggest": "^1.1.0",
5252 "patchcore": "^1.23.3",
5353 "pull-next": "^1.0.1",
5454 "pull-next-step": "^1.0.0",
translations/en.jsView
@@ -160,13 +160,29 @@
160160 state: {
161161 noSubscriptions: 'You have no subscriptions yet'
162162 }
163163 },
164+ share: {
165+ captionPlaceholder: 'Type an optional caption here',
166+ shareLabel: 'Share this blog with your followers',
167+ externalShareLabel: 'Share this blog with on the Web',
168+ action: {
169+ share: 'Share',
170+ copy: 'Copy external URL',
171+ open: 'Open external URL'
172+ },
173+ settings: {
174+ caption: 'Web Sharing Metrics',
175+ publicOption: 'publish metrics openly',
176+ authorAndYouOption: 'publish for you and author',
177+ justYouOption: 'publish just for you'
178+ }
179+ },
164180 statsShow: {
165181 title: 'Stats',
166182 prevMonth: 'Prev 30 days',
167183 nextMonth: 'Next 30 days',
168- thirtyDays: '30 days',
184+ thirtyDays: '30 days'
169185 },
170186 languages: {
171187 en: 'English',
172188 zh: '中文'

Built with git-ssb-web