Commit 7132de8591e3a3e7dde989835cf9c5fb52382791
Merge pull request #134 from ticktackim/feature-external-share
Feature external sharemix 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.js | changed |
app/page/blogShow.js | changed |
app/page/blogShow.mcss | changed |
app/page/settings.js | changed |
app/page/settings.mcss | changed |
app/sync/initialize/clickHandler.js | changed |
app/sync/initialize/styles.js | changed |
app/sync/initialize/suggests.js | changed |
app/sync/initialize/zoomMemory.js | changed |
app/sync/initialize/settings.js | added |
message/html/likes.js | changed |
message/html/likes.mcss | changed |
message/html/subject.js | changed |
message/html/shares.js | added |
message/html/shares.mcss | added |
message/html/webshares.js | added |
message/html/webshares.mcss | added |
message/index.js | changed |
message/obs/shares.js | added |
message/obs/webshares.js | added |
package-lock.json | changed |
package.json | changed |
translations/en.js | changed |
app/index.js | ||
---|---|---|
@@ -20,12 +20,12 @@ | ||
20 | 20 | sideNav: { |
21 | 21 | addressBook: require('./html/sideNav/sideNavAddressBook'), |
22 | 22 | discovery: require('./html/sideNav/sideNavDiscovery') |
23 | 23 | }, |
24 | - warning: require('./html/warning'), | |
24 | + warning: require('./html/warning') | |
25 | 25 | }, |
26 | 26 | obs: { |
27 | - pluginsOk: require('./obs/pluginsOk'), | |
27 | + pluginsOk: require('./obs/pluginsOk') | |
28 | 28 | }, |
29 | 29 | page: { |
30 | 30 | addressBook: require('./page/addressBook'), |
31 | 31 | blogIndex: require('./page/blogIndex'), |
@@ -53,8 +53,9 @@ | ||
53 | 53 | }, |
54 | 54 | sync: { |
55 | 55 | initialize: { |
56 | 56 | clickHandler: require('./sync/initialize/clickHandler'), |
57 | + settings: require('./sync/initialize/settings'), | |
57 | 58 | styles: require('./sync/initialize/styles'), |
58 | 59 | suggests: require('./sync/initialize/suggests'), |
59 | 60 | zoomMemory: require('./sync/initialize/zoomMemory') |
60 | 61 | } |
app/page/blogShow.js | ||
---|---|---|
@@ -11,8 +11,10 @@ | ||
11 | 11 | 'app.html.sideNav': 'first', |
12 | 12 | 'contact.html.follow': 'first', |
13 | 13 | 'message.html.channel': 'first', |
14 | 14 | 'message.html.likes': 'first', |
15 | + 'message.html.webshares': 'first', | |
16 | + 'message.html.shares': 'first', | |
15 | 17 | 'message.html.timeago': 'first', |
16 | 18 | 'feed.obs.thread': 'first', |
17 | 19 | 'blog.html.title': 'first', |
18 | 20 | 'blog.html.content': 'first' |
@@ -20,9 +22,9 @@ | ||
20 | 22 | |
21 | 23 | exports.create = (api) => { |
22 | 24 | return nest('app.page.blogShow', blogShow) |
23 | 25 | |
24 | - function blogShow (blogMsg) { | |
26 | + function blogShow(blogMsg) { | |
25 | 27 | // blogMsg = a thread (message, may be decorated with replies) |
26 | 28 | |
27 | 29 | const { author } = blogMsg.value |
28 | 30 | |
@@ -45,9 +47,11 @@ | ||
45 | 47 | h('div.blog-details', [ |
46 | 48 | h('h1', title), |
47 | 49 | timeago(blogMsg), |
48 | 50 | 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) | |
50 | 54 | ]), |
51 | 55 | h('div.author', [ |
52 | 56 | h('div.leftCol', api.about.html.avatar(author, 'medium')), |
53 | 57 | h('div.rightCol', [ |
app/page/blogShow.mcss | ||
---|---|---|
@@ -46,9 +46,16 @@ | ||
46 | 46 | div.Button.-channel { |
47 | 47 | margin-right: 1rem |
48 | 48 | } |
49 | 49 | div.Likes { |
50 | + margin-right: 2rem | |
50 | 51 | } |
52 | + div.Shares { | |
53 | + margin-right: 2rem | |
54 | + } | |
55 | + div.Likes { | |
56 | + margin-right: 2rem | |
57 | + } | |
51 | 58 | } |
52 | 59 | |
53 | 60 | div.author { |
54 | 61 | display: flex |
app/page/settings.js | ||
---|---|---|
@@ -1,6 +1,6 @@ | ||
1 | 1 | const nest = require('depnest') |
2 | -const { h, computed } = require('mutant') | |
2 | +const { h, computed, when } = require('mutant') | |
3 | 3 | const electron = require('electron') |
4 | 4 | const path = require('path') |
5 | 5 | const { version } = require('../../package.json') |
6 | 6 | |
@@ -21,14 +21,8 @@ | ||
21 | 21 | }) |
22 | 22 | |
23 | 23 | const LANGUAGES = ['zh', 'en'] |
24 | 24 | |
25 | -// TODO - this needs moving somewhere upstream | |
26 | -// const DEFAULT_SETTINGS = { | |
27 | -// onboarded: false, | |
28 | -// language: 'zh' | |
29 | -// } | |
30 | - | |
31 | 25 | exports.create = (api) => { |
32 | 26 | return nest('app.page.settings', settings) |
33 | 27 | |
34 | 28 | function settings (location) { |
@@ -39,11 +33,12 @@ | ||
39 | 33 | // clear history back to start page |
40 | 34 | api.history.obs.store().set([ |
41 | 35 | { page: 'blogIndex' } |
42 | 36 | ]) |
43 | - api.history.sync.push({page: 'settings'}) | |
37 | + api.history.sync.push({ page: 'settings' }) | |
44 | 38 | }) |
45 | 39 | |
40 | + const webSharingMetricsOption = api.settings.obs.get('ticktack.websharemetrics') | |
46 | 41 | const feed = api.keys.sync.id() |
47 | 42 | const strings = api.translations.sync.strings() |
48 | 43 | const currentLanguage = api.settings.sync.get('language') |
49 | 44 | |
@@ -83,10 +78,18 @@ | ||
83 | 78 | h('div.right', LANGUAGES.map(Language)) |
84 | 79 | ]), |
85 | 80 | h('section -zoom', [ |
86 | 81 | 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, '+')]) | |
88 | 83 | ]), |
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 | + ]), | |
89 | 92 | h('section -version', [ |
90 | 93 | h('div.left', strings.settingsPage.section.version), |
91 | 94 | h('div.right', version) |
92 | 95 | ]) |
@@ -112,13 +115,29 @@ | ||
112 | 115 | { |
113 | 116 | 'ev-click': () => { |
114 | 117 | var zoomFactor = api.settings.sync.get('ticktack.electron.zoomFactor', 1) |
115 | 118 | var newZoomFactor = zoomFactor + increment |
116 | - var zoomFactor = api.settings.sync.set('ticktack.electron.zoomFactor', newZoomFactor) | |
119 | + api.settings.sync.set('ticktack.electron.zoomFactor', newZoomFactor) | |
117 | 120 | getCurrentWebContents().setZoomFactor(newZoomFactor) |
118 | 121 | } |
119 | 122 | }, |
120 | 123 | symbol |
121 | 124 | ) |
122 | 125 | } |
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 | + } | |
123 | 142 | } |
124 | 143 | } |
app/page/settings.mcss | ||
---|---|---|
@@ -55,22 +55,37 @@ | ||
55 | 55 | margin-bottom: 2rem |
56 | 56 | } |
57 | 57 | |
58 | 58 | -version { |
59 | - margin-top: 2rem | |
60 | - | |
61 | 59 | div.right { |
62 | 60 | $colorFontSubtle |
63 | 61 | } |
64 | 62 | } |
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 | + } | |
65 | 74 | } |
66 | 75 | } |
67 | 76 | } |
68 | 77 | |
69 | 78 | Button -language { |
70 | 79 | margin-right: 1rem |
71 | 80 | } |
72 | 81 | |
82 | + | |
83 | +Button -websharingmetrics { | |
84 | + margin-right: 1rem | |
85 | + margin-bottom: 1rem | |
86 | +} | |
87 | + | |
73 | 88 | Button -zoom { |
74 | 89 | margin-right: 1rem |
75 | 90 | min-width: .5rem |
76 | 91 | } |
app/sync/initialize/clickHandler.js | ||
---|---|---|
@@ -10,8 +10,9 @@ | ||
10 | 10 | |
11 | 11 | exports.create = (api) => { |
12 | 12 | return nest({ |
13 | 13 | 'app.sync.initialize': function initializeClickHandling () { |
14 | + console.log('> initialise: clickHandler') | |
14 | 15 | const target = document.body |
15 | 16 | |
16 | 17 | api.app.async.catchLinkClick(target, (link, { isExternal }) => { |
17 | 18 | if (isExternal) return openExternal(link) |
app/sync/initialize/styles.js | ||
---|---|---|
@@ -10,8 +10,9 @@ | ||
10 | 10 | |
11 | 11 | exports.create = (api) => { |
12 | 12 | return nest({ |
13 | 13 | 'app.sync.initialize': function initializeStyles () { |
14 | + console.log('> initialise: styles') | |
14 | 15 | const css = values(api.styles.css()).join('\n') |
15 | 16 | insertCss(css) |
16 | 17 | } |
17 | 18 | }) |
app/sync/initialize/suggests.js | ||
---|---|---|
@@ -12,8 +12,9 @@ | ||
12 | 12 | var nav = null |
13 | 13 | |
14 | 14 | return nest({ |
15 | 15 | 'app.sync.initialize': function initializeSuggests () { |
16 | + console.log('> initialise: suggest-mentions') | |
16 | 17 | api.about.async.suggest() |
17 | 18 | api.channel.async.suggest() |
18 | 19 | // api.channel.obs.recent()() TODO - figure out how to initialise this store |
19 | 20 | } |
app/sync/initialize/zoomMemory.js | ||
---|---|---|
@@ -11,8 +11,9 @@ | ||
11 | 11 | exports.create = (api) => { |
12 | 12 | return nest('app.sync.initialize', zoomMemory) |
13 | 13 | |
14 | 14 | function zoomMemory () { |
15 | + console.log('> initialise: window form') | |
15 | 16 | const { getCurrentWebContents, getCurrentWindow } = electron.remote |
16 | 17 | |
17 | 18 | window.addEventListener('resize', () => { |
18 | 19 | var wc = getCurrentWebContents() |
app/sync/initialize/settings.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -9,9 +9,9 @@ | ||
9 | 9 | |
10 | 10 | exports.gives = nest('message.html.likes') |
11 | 11 | |
12 | 12 | exports.create = (api) => { |
13 | - return nest('message.html.likes', function likes (msg) { | |
13 | + return nest('message.html.likes', function likes(msg) { | |
14 | 14 | var id = api.keys.sync.id() |
15 | 15 | var likes = api.message.obs.likes(msg.key) |
16 | 16 | |
17 | 17 | var iLike = computed(likes, likes => likes.includes(id)) |
@@ -22,18 +22,18 @@ | ||
22 | 22 | h('div.count', count) |
23 | 23 | ]) |
24 | 24 | }) |
25 | 25 | |
26 | - function publishLike (msg, status = true) { | |
26 | + function publishLike(msg, status = true) { | |
27 | 27 | var like = status ? { |
28 | 28 | type: 'vote', |
29 | 29 | channel: msg.value.content.channel, |
30 | 30 | vote: { link: msg.key, value: 1, expression: 'Like' } |
31 | 31 | } : { |
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 | + } | |
36 | 36 | if (msg.value.content.recps) { |
37 | 37 | like.recps = msg.value.content.recps.map(function (e) { |
38 | 38 | return e && typeof e !== 'string' ? e.link : e |
39 | 39 | }) |
message/html/likes.mcss | ||
---|---|---|
@@ -2,9 +2,9 @@ | ||
2 | 2 | cursor: pointer |
3 | 3 | min-width: 2.5rem |
4 | 4 | |
5 | 5 | display: flex |
6 | - align-items: center | |
6 | + align-items: baseline | |
7 | 7 | |
8 | 8 | i { |
9 | 9 | margin-right: .4rem |
10 | 10 | } |
message/html/subject.js | ||
---|---|---|
@@ -15,10 +15,9 @@ | ||
15 | 15 | var subjectCache = {} |
16 | 16 | |
17 | 17 | return nest('message.html.subject', subject) |
18 | 18 | |
19 | - function subject (msg) { | |
20 | - if (msg === undefined) debugger | |
19 | + function subject(msg) { | |
21 | 20 | // test if it's a message ref, or a full message object |
22 | 21 | // a message ref is generally passed in if we're fetching the subject of a root message |
23 | 22 | if (isMsg(msg)) { |
24 | 23 | if (subjectCache[msg]) return subjectCache[msg] |
@@ -26,19 +25,23 @@ | ||
26 | 25 | var subject = Value() |
27 | 26 | |
28 | 27 | api.sbot.async.get(msg, (err, value) => { |
29 | 28 | 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 | + } | |
30 | 36 | |
31 | - var _subject = getMsgSubject({ key: msg, value: api.message.sync.unbox(value) }) | |
32 | - subject.set(_subject) | |
33 | - subjectCache[msg] = _subject | |
34 | 37 | }) |
35 | 38 | |
36 | 39 | return subject |
37 | 40 | } else { return getMsgSubject(msg) } |
38 | 41 | } |
39 | 42 | |
40 | - function getMsgSubject (msg) { | |
43 | + function getMsgSubject(msg) { | |
41 | 44 | const { subject, text } = msg.value.content |
42 | 45 | if (!(subject || text)) return |
43 | 46 | |
44 | 47 | return subject |
message/html/shares.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.mcss | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -5,11 +5,17 @@ | ||
5 | 5 | html: { |
6 | 6 | channel: require('./html/channel'), |
7 | 7 | compose: require('./html/compose'), |
8 | 8 | likes: require('./html/likes'), |
9 | + shares: require('./html/shares'), | |
10 | + webshares: require('./html/webshares'), | |
9 | 11 | subject: require('./html/subject'), |
10 | 12 | timeago: require('./html/timeago') |
11 | 13 | }, |
12 | 14 | sync: { |
13 | 15 | getParticipants: require('./sync/getParticipants') |
16 | + }, | |
17 | + obs: { | |
18 | + shares: require('./obs/shares'), | |
19 | + webshares: require('./obs/webshares') | |
14 | 20 | } |
15 | 21 | } |
message/obs/shares.js | ||
---|---|---|
@@ -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.js | ||
---|---|---|
@@ -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.json | ||
---|---|---|
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.json | ||
---|---|---|
@@ -46,9 +46,9 @@ | ||
46 | 46 | "open-external": "^0.1.1", |
47 | 47 | "patch-drafts": "0.0.6", |
48 | 48 | "patch-history": "^1.0.0", |
49 | 49 | "patch-profile": "^1.0.4", |
50 | - "patch-settings": "^1.0.0", | |
50 | + "patch-settings": "^1.1.2", | |
51 | 51 | "patch-suggest": "^1.1.0", |
52 | 52 | "patchcore": "^1.23.3", |
53 | 53 | "pull-next": "^1.0.1", |
54 | 54 | "pull-next-step": "^1.0.0", |
translations/en.js | ||
---|---|---|
@@ -160,13 +160,29 @@ | ||
160 | 160 | state: { |
161 | 161 | noSubscriptions: 'You have no subscriptions yet' |
162 | 162 | } |
163 | 163 | }, |
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 | + }, | |
164 | 180 | statsShow: { |
165 | 181 | title: 'Stats', |
166 | 182 | prevMonth: 'Prev 30 days', |
167 | 183 | nextMonth: 'Next 30 days', |
168 | - thirtyDays: '30 days', | |
184 | + thirtyDays: '30 days' | |
169 | 185 | }, |
170 | 186 | languages: { |
171 | 187 | en: 'English', |
172 | 188 | zh: '中文' |
Built with git-ssb-web