Commit c9ba3630ee9b32a904d47de7b87c1e101e5d59c5
Integrating with patch-tag
Josiah Witt committed on 3/15/2018, 12:42:20 AMParent: 011ddc7e8f1977900832da37876ff81783bc7b9f
Files changed
main-window.js | changed |
modules/message/html/tag.js | added |
modules/page/html/render/tag.js | added |
modules/sheet/display.js | changed |
modules/sheet/editTags.js | added |
modules/sheet/tags.js | added |
modules/tag/html/tag.js | added |
package-lock.json | changed |
package.json | changed |
plugs/message/html/meta/tags.js | added |
server-process.js | changed |
styles/light/message.mcss | changed |
styles/light/editTags.mcss | added |
styles/light/tag-list.mcss | added |
styles/light/tag.mcss | added |
styles/light/tags.mcss | added |
main-window.js | ||
---|---|---|
@@ -20,8 +20,9 @@ | ||
20 | 20 … | require('./modules'), |
21 | 21 … | require('./plugs'), |
22 | 22 … | require('patch-settings'), |
23 | 23 … | require('patchcore'), |
24 … | + require('patch-tag'), | |
24 | 25 … | require('./overrides') |
25 | 26 … | ) |
26 | 27 … | |
27 | 28 … | var api = entry(sockets, nest({ |
@@ -110,8 +111,9 @@ | ||
110 | 111 … | tab(i18n('Private'), '/private'), |
111 | 112 … | dropTab(i18n('More'), [ |
112 | 113 … | getSubscribedChannelMenu, |
113 | 114 … | [i18n('Gatherings'), '/gatherings'], |
115 … | + ['Tags', '/tags'], | |
114 | 116 … | [i18n('Extended Network'), '/all'], |
115 | 117 … | {separator: true}, |
116 | 118 … | [i18n('Settings'), '/settings'] |
117 | 119 … | ]) |
modules/message/html/tag.js | ||
---|---|---|
@@ -1,0 +1,17 @@ | ||
1 … | +var { h, computed, when } = require('mutant') | |
2 … | +var nest = require('depnest') | |
3 … | + | |
4 … | +exports.needs = nest({ | |
5 … | + 'sheet.editTags': 'first' | |
6 … | +}) | |
7 … | + | |
8 … | +exports.gives = nest('message.html.action') | |
9 … | + | |
10 … | +exports.create = (api) => { | |
11 … | + return nest('message.html.action', msg => { | |
12 … | + return h('a.tag', { | |
13 … | + href: '#', | |
14 … | + 'ev-click': () => api.sheet.editTags({ msgId: msg.key }, console.log) | |
15 … | + }, 'Tag') | |
16 … | + }) | |
17 … | +} |
modules/page/html/render/tag.js | |||
---|---|---|---|
@@ -1,0 +1,78 @@ | |||
1 … | +const { Value, computed, map, when, h } = require('mutant') | ||
2 … | +var nest = require('depnest') | ||
3 … | + | ||
4 … | +exports.needs = nest({ | ||
5 … | + 'about.obs.valueFrom': 'first', | ||
6 … | + 'keys.sync.id': 'first', | ||
7 … | + 'message.html.render': 'first', | ||
8 … | + 'message.obs.get': 'first', | ||
9 … | + 'tag.html.tag': 'first', | ||
10 … | + 'tag.obs.allTagsFrom': 'first', | ||
11 … | + 'tag.obs.taggedMessages': 'first' | ||
12 … | +}) | ||
13 … | + | ||
14 … | +exports.gives = nest('page.html.render') | ||
15 … | + | ||
16 … | +exports.create = function (api) { | ||
17 … | + return nest('page.html.render', function channel (path) { | ||
18 … | + if (!path.startsWith('/tags')) return | ||
19 … | + | ||
20 … | + const myId = api.keys.sync.id() | ||
21 … | + const tags = map( | ||
22 … | + api.tag.obs.allTagsFrom(myId), | ||
23 … | + tagId => { | ||
24 … | + return { tagId, tagName: api.about.obs.valueFrom(tagId, 'name', myId) } | ||
25 … | + } | ||
26 … | + ) | ||
27 … | + const selectedTagId = Value(path.split('/')[2]) | ||
28 … | + const name = computed( | ||
29 … | + selectedTagId, | ||
30 … | + tagId => tagId | ||
31 … | + ? api.about.obs.valueFrom(tagId, 'name', myId) | ||
32 … | + : Value('Select A Tag') | ||
33 … | + ) | ||
34 … | + const tagMessages = computed( | ||
35 … | + selectedTagId, | ||
36 … | + tagId => tagId | ||
37 … | + ? map( | ||
38 … | + api.tag.obs.taggedMessages(myId, tagId), | ||
39 … | + msgId => api.message.obs.get(msgId) | ||
40 … | + ) | ||
41 … | + : [] | ||
42 … | + ) | ||
43 … | + | ||
44 … | + var prepend = [ | ||
45 … | + h('PageHeading', [ | ||
46 … | + h('h1', [ h('strong', 'Tags'), ' from you' ]), | ||
47 … | + ]) | ||
48 … | + ] | ||
49 … | + | ||
50 … | + return h('div.SplitView', [ | ||
51 … | + h('div.side', map( | ||
52 … | + tags, | ||
53 … | + tag => computed( | ||
54 … | + tag, | ||
55 … | + ({ tagId, tagName }) => | ||
56 … | + h('a', { | ||
57 … | + 'ev-click': () => selectedTagId.set(tagId) | ||
58 … | + }, api.tag.html.tag({ tagName, tagId }, null)) | ||
59 … | + ) | ||
60 … | + )), | ||
61 … | + h('div.main', [ | ||
62 … | + h('Scroller',[ | ||
63 … | + h('h2', name), | ||
64 … | + h('section.messages', [ | ||
65 … | + map( | ||
66 … | + tagMessages, | ||
67 … | + msg => computed(msg, msg => { | ||
68 … | + if (msg && !msg.value.missing) { | ||
69 … | + return h('div.messagewrapper', api.message.html.render(msg)) | ||
70 … | + } | ||
71 … | + }) | ||
72 … | + ) | ||
73 … | + ]) | ||
74 … | + ]) | ||
75 … | + ]) | ||
76 … | + ]) | ||
77 … | + }) | ||
78 … | +} |
modules/sheet/display.js | ||
---|---|---|
@@ -4,17 +4,19 @@ | ||
4 | 4 … | exports.gives = nest('sheet.display') |
5 | 5 … | |
6 | 6 … | exports.create = function () { |
7 | 7 … | return nest('sheet.display', function (handler) { |
8 | - var {content, footer} = handler(done) | |
8 … | + var {content, footer, mounted} = handler(done) | |
9 | 9 … | |
10 | 10 … | var container = h('div', {className: 'Sheet'}, [ |
11 | 11 … | h('section', [content]), |
12 | 12 … | h('footer', [footer]) |
13 | 13 … | ]) |
14 | 14 … | |
15 | 15 … | document.body.appendChild(container) |
16 | 16 … | |
17 … | + if (mounted) mounted() | |
18 … | + | |
17 | 19 … | function done () { |
18 | 20 … | document.body.removeChild(container) |
19 | 21 … | } |
20 | 22 … | }) |
modules/sheet/editTags.js | ||
---|---|---|
@@ -1,0 +1,183 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | +const { h, Value, Struct, map, computed } = require('mutant') | |
3 … | +const MutantArray = require('mutant/Array') | |
4 … | +const concat = require('lodash/concat') | |
5 … | +const filter = require('lodash/filter') | |
6 … | +const zip = require('lodash/zip') | |
7 … | +const forEach = require('lodash/forEach') | |
8 … | +const addSuggest = require('suggest-box') | |
9 … | + | |
10 … | +exports.gives = nest('sheet.editTags') | |
11 … | + | |
12 … | +exports.needs = nest({ | |
13 … | + 'about.obs.valueFrom': 'first', | |
14 … | + 'keys.sync.id': 'first', | |
15 … | + 'sheet.display': 'first', | |
16 … | + 'tag': { | |
17 … | + 'async': { | |
18 … | + 'apply': 'first', | |
19 … | + 'create': 'first', | |
20 … | + 'name': 'first' | |
21 … | + }, | |
22 … | + 'html': { | |
23 … | + 'tag': 'first' | |
24 … | + }, | |
25 … | + 'obs': { | |
26 … | + 'messageTags': 'first', | |
27 … | + 'allTags': 'first' | |
28 … | + } | |
29 … | + } | |
30 … | +}) | |
31 … | + | |
32 … | +exports.create = function(api) { | |
33 … | + return nest({ 'sheet.editTags': editTags }) | |
34 … | + | |
35 … | + function editTags({ msgId }, cb) { | |
36 … | + cb = cb || function() {} | |
37 … | + api.sheet.display(function (close) { | |
38 … | + const tagsToCreate = MutantArray([]) | |
39 … | + const tagsToApply = MutantArray([]) | |
40 … | + const tagsToRemove = MutantArray([]) | |
41 … | + const tagsInput = Value('') | |
42 … | + | |
43 … | + const myId = api.keys.sync.id() | |
44 … | + const messageTags = map( | |
45 … | + api.tag.obs.messageTags(msgId), | |
46 … | + tagId => Struct({ | |
47 … | + tagId: Value(tagId), | |
48 … | + tagName: api.about.obs.valueFrom(tagId, 'name', myId) | |
49 … | + }) | |
50 … | + ) | |
51 … | + const filteredMessages = computed( | |
52 … | + [ messageTags, tagsToRemove ], | |
53 … | + (tags, removedIds) => filter(tags, tag => !removedIds.includes(tag.tagId)) | |
54 … | + ) | |
55 … | + | |
56 … | + const messageTagsView = map( | |
57 … | + filteredMessages, | |
58 … | + tag => computed(tag, t => api.tag.html.tag(t, () => tagsToRemove.push(t.tagId))) | |
59 … | + ) | |
60 … | + const tagsToApplyView = map( | |
61 … | + tagsToApply, | |
62 … | + tag => api.tag.html.tag(tag, () => tagsToApply.delete(tag)) | |
63 … | + ) | |
64 … | + const tagsToCreateView = map( | |
65 … | + tagsToCreate, | |
66 … | + tag => api.tag.html.tag({ tagName: tag, tagId: 'new' }, () => tagsToCreate.delete(tag)) | |
67 … | + ) | |
68 … | + const stagedTags = computed( | |
69 … | + [messageTagsView, tagsToApplyView, tagsToCreateView], | |
70 … | + (a, b, c) => h('StagedTags', concat(a, [b, c])) | |
71 … | + ) | |
72 … | + | |
73 … | + const input = h('input.tags', { | |
74 … | + placeholder: 'Add tags here', | |
75 … | + 'ev-keyup': onInput, | |
76 … | + value: tagsInput() | |
77 … | + }) | |
78 … | + | |
79 … | + input.addEventListener('suggestselect', onSuggestSelect) | |
80 … | + | |
81 … | + return { | |
82 … | + content: [ | |
83 … | + stagedTags, | |
84 … | + h('EditTags', input) | |
85 … | + ], | |
86 … | + footer: [ | |
87 … | + h('button.save', { | |
88 … | + 'ev-click': () => { | |
89 … | + onSave() | |
90 … | + close() | |
91 … | + } | |
92 … | + }, | |
93 … | + 'Save' | |
94 … | + ), | |
95 … | + h('button.cancel', { | |
96 … | + 'ev-click': () => close() | |
97 … | + }, | |
98 … | + 'Cancel' | |
99 … | + ) | |
100 … | + ], | |
101 … | + mounted: () => { | |
102 … | + input.focus() | |
103 … | + addSuggest(input, (inputText, cb) => { | |
104 … | + cb(null, getTagSuggestions(inputText)) | |
105 … | + }, { cls: 'ConfirmSuggest' }) | |
106 … | + } | |
107 … | + } | |
108 … | + | |
109 … | + function publish () { | |
110 … | + close() | |
111 … | + onSave() | |
112 … | + } | |
113 … | + | |
114 … | + function onInput(e) { | |
115 … | + const input = e.target.value; | |
116 … | + if (!input.endsWith(",")) { | |
117 … | + tagsInput.set(input) | |
118 … | + return | |
119 … | + } | |
120 … | + const tag = input.substring(0, input.length - 1) | |
121 … | + tagsToCreate.push(tag) | |
122 … | + e.target.value = "" | |
123 … | + } | |
124 … | + | |
125 … | + function onSuggestSelect(e) { | |
126 … | + e.target.value = "" | |
127 … | + const { value, tagId } = e.detail | |
128 … | + const index = tagsToRemove().indexOf(tagId) | |
129 … | + if (index >= 0) { | |
130 … | + tagsToRemove.deleteAt(index) | |
131 … | + } else { | |
132 … | + tagsToApply.push({ tagId, tagName: value }) | |
133 … | + } | |
134 … | + } | |
135 … | + | |
136 … | + function getTagSuggestions(word) { | |
137 … | + const suggestions = map( | |
138 … | + api.tag.obs.allTags(), | |
139 … | + tagId => { | |
140 … | + const tagName = api.about.obs.valueFrom(tagId, 'name', myId)() | |
141 … | + return { | |
142 … | + title: tagName, | |
143 … | + value: tagName, | |
144 … | + tagId | |
145 … | + } | |
146 … | + } | |
147 … | + )() | |
148 … | + const appliedTagIds = map(filteredMessages, tag => tag.tagId) | |
149 … | + const applyTagIds = map(tagsToApply, tag => tag.tagId) | |
150 … | + const stagedTagIds = computed([ appliedTagIds, applyTagIds ], (a, b) => concat(a, b))() | |
151 … | + const filteredSuggestions = filter(suggestions, tag => !stagedTagIds.includes(tag.tagId)) | |
152 … | + filteredSuggestions.push({ title: "Press , to create a new tag" }) | |
153 … | + return filteredSuggestions | |
154 … | + } | |
155 … | + | |
156 … | + function onSave() { | |
157 … | + // tagsToCreate | |
158 … | + forEach( | |
159 … | + tagsToCreate(), | |
160 … | + tag => { | |
161 … | + api.tag.async.create(null, (err, msg) => { | |
162 … | + if (err) return | |
163 … | + api.tag.async.name({ tag: msg.key, name: tag }, cb) | |
164 … | + api.tag.async.apply({ tagged: true, message: msgId, tag: msg.key }, cb) | |
165 … | + }) | |
166 … | + } | |
167 … | + ) | |
168 … | + | |
169 … | + // tagsToApply | |
170 … | + forEach( | |
171 … | + tagsToApply(), | |
172 … | + tag => api.tag.async.apply({ tagged: true, message: msgId, tag: tag.tagId }, cb) | |
173 … | + ) | |
174 … | + | |
175 … | + // tagsToRemove | |
176 … | + forEach( | |
177 … | + tagsToRemove(), | |
178 … | + tagId => api.tag.async.apply({ tagged: false, message: msgId, tag: tagId }, cb) | |
179 … | + ) | |
180 … | + } | |
181 … | + }) | |
182 … | + } | |
183 … | +} |
modules/sheet/tags.js | ||
---|---|---|
@@ -1,0 +1,217 @@ | ||
1 … | +var {h, when, map, computed, Value, lookup} = require('mutant') | |
2 … | +var nest = require('depnest') | |
3 … | +var catchLinks = require('../../lib/catch-links') | |
4 … | + | |
5 … | +exports.needs = nest({ | |
6 … | + 'sheet.display': 'first', | |
7 … | + 'keys.sync.id': 'first', | |
8 … | + 'contact.obs.following': 'first', | |
9 … | + 'contact.html.followToggle': 'first', | |
10 … | + 'profile.obs.rank': 'first', | |
11 … | + 'about.html.image': 'first', | |
12 … | + 'about.obs.name': 'first', | |
13 … | + 'app.navigate': 'first', | |
14 … | + 'intl.sync.i18n': 'first', | |
15 … | + 'tag.obs.messageTaggers': 'first' | |
16 … | +}) | |
17 … | + | |
18 … | +exports.gives = nest('sheet.tags') | |
19 … | + | |
20 … | +exports.create = function (api) { | |
21 … | + const i18n = api.intl.sync.i18n | |
22 … | + const displayTags = Value(true) | |
23 … | + const selectedTag = Value() | |
24 … | + return nest('sheet.tags', function (msgId, ids) { | |
25 … | + api.sheet.display(close => { | |
26 … | + const content = computed([displayTags, selectedTag], (displayTags, selectedTag) => { | |
27 … | + if (displayTags) { | |
28 … | + return renderTags(ids) | |
29 … | + } else { | |
30 … | + return renderTaggers(msgId, selectedTag) | |
31 … | + } | |
32 … | + }) | |
33 … | + const back = computed(displayTags, (display) => { | |
34 … | + if (display) { | |
35 … | + return | |
36 … | + } else { | |
37 … | + return h('button -close', { | |
38 … | + 'ev-click': () => { | |
39 … | + displayTags.set(true) | |
40 … | + selectedTag.set() | |
41 … | + } | |
42 … | + }, i18n('Back')) | |
43 … | + } | |
44 … | + }) | |
45 … | + return { | |
46 … | + content, | |
47 … | + footer: [ | |
48 … | + back, | |
49 … | + h('button -close', { | |
50 … | + 'ev-click': () => { | |
51 … | + close() | |
52 … | + displayTags.set(true) | |
53 … | + selectedTag.set() | |
54 … | + } | |
55 … | + }, i18n('Close')) | |
56 … | + ] | |
57 … | + } | |
58 … | + }) | |
59 … | + }) | |
60 … | + | |
61 … | + function renderTags (ids) { | |
62 … | + var currentFilter = Value() | |
63 … | + var tagLookup = lookup(ids, (id) => { | |
64 … | + return [id, api.about.obs.name(id)] | |
65 … | + }) | |
66 … | + var filteredIds = computed([ids, tagLookup, currentFilter], (ids, tagLookup, filter) => { | |
67 … | + if (filter) { | |
68 … | + var result = [] | |
69 … | + for (var k in tagLookup) { | |
70 … | + if ((tagLookup[k] && tagLookup[k].toLowerCase().includes(filter.toLowerCase())) || k === filter) { | |
71 … | + result.push(k) | |
72 … | + } | |
73 … | + } | |
74 … | + return result | |
75 … | + } else { | |
76 … | + return ids | |
77 … | + } | |
78 … | + }) | |
79 … | + var content = h('div', { | |
80 … | + style: { padding: '20px' } | |
81 … | + }, [ | |
82 … | + h('h2', { | |
83 … | + style: { 'font-weight': 'normal' } | |
84 … | + }, [ | |
85 … | + i18n('Applied Tags'), | |
86 … | + h('input', { | |
87 … | + type: 'search', | |
88 … | + placeholder: 'filter tags', | |
89 … | + 'ev-input': function (ev) { | |
90 … | + currentFilter.set(ev.target.value) | |
91 … | + }, | |
92 … | + hooks: [FocusHook()], | |
93 … | + style: { | |
94 … | + 'float': 'right', | |
95 … | + 'font-size': '100%' | |
96 … | + } | |
97 … | + }) | |
98 … | + ]), | |
99 … | + renderTagBlock(filteredIds) | |
100 … | + ]) | |
101 … | + | |
102 … | + catchLinks(content, (href, external, anchor) => { | |
103 … | + if (!external) { | |
104 … | + api.app.navigate(href, anchor) | |
105 … | + close() | |
106 … | + } | |
107 … | + }) | |
108 … | + | |
109 … | + return content | |
110 … | + } | |
111 … | + | |
112 … | + function renderTagBlock (tags) { | |
113 … | + var yourId = api.keys.sync.id() | |
114 … | + return [ | |
115 … | + h('div', { | |
116 … | + classList: 'TagList' | |
117 … | + }, [ | |
118 … | + map(tags, (id) => { | |
119 … | + return h('a.tag', { | |
120 … | + href: `/tags/${id}`, | |
121 … | + title: id | |
122 … | + }, [ | |
123 … | + h('div.main', [ | |
124 … | + h('div.name', [ api.about.obs.name(id) ]) | |
125 … | + ]), | |
126 … | + h('div.buttons', [ | |
127 … | + h('a.ToggleButton', { | |
128 … | + 'ev-click': () => { | |
129 … | + selectedTag.set(id) | |
130 … | + displayTags.set(false) | |
131 … | + } | |
132 … | + }, i18n('View Taggers')) | |
133 … | + ]) | |
134 … | + ]) | |
135 … | + }, { idle: true, maxTime: 2 }) | |
136 … | + ]) | |
137 … | + ] | |
138 … | + } | |
139 … | + | |
140 … | + function renderTaggers (msgId, tagId) { | |
141 … | + var taggerIds = api.tag.obs.messageTaggers(msgId, tagId) | |
142 … | + var currentFilter = Value() | |
143 … | + var taggerLookup = lookup(taggerIds, (id) => { | |
144 … | + return [id, api.about.obs.name(id)] | |
145 … | + }) | |
146 … | + var filteredIds = computed([taggerIds, taggerLookup, currentFilter], (ids, taggerLookup, filter) => { | |
147 … | + if (filter) { | |
148 … | + var result = [] | |
149 … | + for (var k in taggerLookup) { | |
150 … | + if ((taggerLookup[k] && taggerLookup[k].toLowerCase().includes(filter.toLowerCase())) || k === filter) { | |
151 … | + result.push(k) | |
152 … | + } | |
153 … | + } | |
154 … | + return result | |
155 … | + } else { | |
156 … | + return ids | |
157 … | + } | |
158 … | + }) | |
159 … | + return h('div', { | |
160 … | + style: { padding: '20px' } | |
161 … | + }, [ | |
162 … | + h('h2', { | |
163 … | + style: { 'font-weight': 'normal' } | |
164 … | + }, [ | |
165 … | + api.about.obs.name(tagId), | |
166 … | + i18n(' Taggers'), | |
167 … | + h('input', { | |
168 … | + type: 'search', | |
169 … | + placeholder: 'filter names', | |
170 … | + 'ev-input': function (ev) { | |
171 … | + currentFilter.set(ev.target.value) | |
172 … | + }, | |
173 … | + hooks: [FocusHook()], | |
174 … | + style: { | |
175 … | + 'float': 'right', | |
176 … | + 'font-size': '100%' | |
177 … | + } | |
178 … | + }) | |
179 … | + ]), | |
180 … | + renderTaggersBlock(filteredIds) | |
181 … | + ]) | |
182 … | + } | |
183 … | + | |
184 … | + function renderTaggersBlock (profiles) { | |
185 … | + var yourId = api.keys.sync.id() | |
186 … | + profiles = api.profile.obs.rank(profiles) | |
187 … | + return [ | |
188 … | + h('div', { | |
189 … | + classList: 'ProfileList' | |
190 … | + }, [ | |
191 … | + map(profiles, (id) => { | |
192 … | + return h('a.profile', { | |
193 … | + href: id, | |
194 … | + title: id | |
195 … | + }, [ | |
196 … | + h('div.avatar', [api.about.html.image(id)]), | |
197 … | + h('div.main', [ | |
198 … | + h('div.name', [ api.about.obs.name(id) ]) | |
199 … | + ]), | |
200 … | + h('div.buttons', [ | |
201 … | + api.contact.html.followToggle(id, {block: false}) | |
202 … | + ]) | |
203 … | + ]) | |
204 … | + }, { idle: true, maxTime: 2 }) | |
205 … | + ]) | |
206 … | + ] | |
207 … | + } | |
208 … | +} | |
209 … | + | |
210 … | +function FocusHook () { | |
211 … | + return function (element) { | |
212 … | + setTimeout(() => { | |
213 … | + element.focus() | |
214 … | + element.select() | |
215 … | + }, 5) | |
216 … | + } | |
217 … | +} |
modules/tag/html/tag.js | ||
---|---|---|
@@ -1,0 +1,57 @@ | ||
1 … | +const nest = require('depnest') | |
2 … | +const { h, computed } = require('mutant') | |
3 … | +const hexrgb = require('hex-rgb') | |
4 … | + | |
5 … | +exports.gives = nest('tag.html.tag') | |
6 … | + | |
7 … | +exports.needs = nest({ | |
8 … | + 'about.obs.color': 'first' | |
9 … | +}) | |
10 … | + | |
11 … | +exports.create = function(api) { | |
12 … | + return nest({ 'tag.html.tag': function({ tagName, tagId }, handleRemove) { | |
13 … | + var removeTag | |
14 … | + if (handleRemove) { | |
15 … | + removeTag = h('a', { | |
16 … | + 'ev-click': handleRemove | |
17 … | + }, 'x') | |
18 … | + } else { | |
19 … | + removeTag = ''; | |
20 … | + } | |
21 … | + | |
22 … | + const backgroundColor = api.about.obs.color(tagId) | |
23 … | + const fontColor = computed(backgroundColor, contrast) | |
24 … | + | |
25 … | + return h( | |
26 … | + 'Tag', | |
27 … | + { | |
28 … | + style: { | |
29 … | + 'background-color': backgroundColor, | |
30 … | + 'color': fontColor | |
31 … | + } | |
32 … | + }, | |
33 … | + [ | |
34 … | + h('span', tagName), | |
35 … | + removeTag | |
36 … | + ] | |
37 … | + ) | |
38 … | + }}) | |
39 … | +} | |
40 … | + | |
41 … | +function contrast(backgroundColor) { | |
42 … | + const { red, green, blue } = hexrgb(backgroundColor) | |
43 … | + const C = [ red/255, green/255, blue/255 ] | |
44 … | + for ( var i = 0; i < C.length; ++i ) { | |
45 … | + if ( C[i] <= 0.03928 ) { | |
46 … | + C[i] = C[i] / 12.92 | |
47 … | + } else { | |
48 … | + C[i] = Math.pow( ( C[i] + 0.055 ) / 1.055, 2.4); | |
49 … | + } | |
50 … | + } | |
51 … | + const L = 0.2126 * C[0] + 0.7152 * C[1] + 0.0722 * C[2] | |
52 … | + if (L > 0.179) { | |
53 … | + return '#000' | |
54 … | + } else { | |
55 … | + return '#fff' | |
56 … | + } | |
57 … | +} |
package-lock.json | ||
---|---|---|
The diff is too large to show. Use a local git client to view these changes. Old file size: 245431 bytes New file size: 245774 bytes |
package.json | ||
---|---|---|
@@ -31,8 +31,9 @@ | ||
31 | 31 … | "flatpickr": "^3.0.5-1", |
32 | 32 … | "flumeview-level": "^2.0.3", |
33 | 33 … | "flumeview-reduce": "^1.3.12", |
34 | 34 … | "hashlru": "^2.2.0", |
35 … | + "hex-rgb": "^2.0.0", | |
35 | 36 … | "human-time": "0.0.1", |
36 | 37 … | "i18n": "^0.8.3", |
37 | 38 … | "level": "~1.7.0", |
38 | 39 … | "lrucache": "^1.0.2", |
plugs/message/html/meta/tags.js | ||
---|---|---|
@@ -1,0 +1,38 @@ | ||
1 … | +var nest = require('depnest') | |
2 … | +var { h, computed, map, send } = require('mutant') | |
3 … | + | |
4 … | +exports.gives = nest('message.html.meta') | |
5 … | +exports.needs = nest({ | |
6 … | + 'about.obs.name': 'first', | |
7 … | + 'sheet.tags': 'first', | |
8 … | + 'tag.obs.messageTags': 'first' | |
9 … | +}) | |
10 … | + | |
11 … | +exports.create = function (api) { | |
12 … | + return nest('message.html.meta', function tags (msg) { | |
13 … | + if (msg.key) { | |
14 … | + return computed(api.tag.obs.messageTags(msg.key), (tags) => tagCount(msg.key, tags)) | |
15 … | + } | |
16 … | + }) | |
17 … | + | |
18 … | + function tagCount (msgId, tags) { | |
19 … | + if (tags.length) { | |
20 … | + return [' ', h('a.tags', { | |
21 … | + title: tagList('Tags', tags), | |
22 … | + href: '#', | |
23 … | + 'ev-click': send(displayTags, { msgId, tags }) | |
24 … | + }, [`${tags.length} ${tags.length === 1 ? 'tag' : 'tags'}`])] | |
25 … | + } | |
26 … | + } | |
27 … | + | |
28 … | + function tagList (prefix, ids) { | |
29 … | + const items = map(ids, api.about.obs.name) | |
30 … | + return computed([prefix, items], (prefix, names) => { | |
31 … | + return (prefix ? (prefix + '\n') : '') + names.map((n) => `- ${n}`).join('\n') | |
32 … | + }) | |
33 … | + } | |
34 … | + | |
35 … | + function displayTags ({ msgId, tags }) { | |
36 … | + api.sheet.tags(msgId, tags) | |
37 … | + } | |
38 … | +} |
server-process.js | ||
---|---|---|
@@ -17,8 +17,9 @@ | ||
17 | 17 … | .use(require('scuttlebot/plugins/local')) |
18 | 18 … | .use(require('scuttlebot/plugins/logging')) |
19 | 19 … | .use(require('ssb-query')) |
20 | 20 … | .use(require('ssb-about')) |
21 … | + .use(require('ssb-tags')) | |
21 | 22 … | // .use(require('ssb-ebt')) // enable at your own risk! |
22 | 23 … | .use(require('./sbot')) |
23 | 24 … | |
24 | 25 … | fixPath() |
styles/light/message.mcss | ||
---|---|---|
@@ -204,8 +204,13 @@ | ||
204 | 204 … | color: #286bc3; |
205 | 205 … | font-size:90%; |
206 | 206 … | } |
207 | 207 … | |
208 … | + a.tags { | |
209 … | + color: #286bc3; | |
210 … | + font-size:90%; | |
211 … | + } | |
212 … | + | |
208 | 213 … | span.private { |
209 | 214 … | display: inline-block; |
210 | 215 … | margin: -3px -3px 3px 4px; |
211 | 216 … | border: 4px solid #525050; |
styles/light/editTags.mcss | ||
---|---|---|
@@ -1,0 +1,42 @@ | ||
1 … | +StagedTags { | |
2 … | + margin: 1rem 0 1rem 1rem | |
3 … | + display: inline-block | |
4 … | +} | |
5 … | + | |
6 … | +EditTags { | |
7 … | + margin: 1rem 1rem 1rem 0 | |
8 … | + display: inline-block | |
9 … | + input.tags { | |
10 … | + font-size: 1rem | |
11 … | + padding: .4rem | |
12 … | + } | |
13 … | +} | |
14 … | + | |
15 … | +ConfirmSuggest { | |
16 … | + position: fixed | |
17 … | + border: 1px solid #ddd | |
18 … | + z-index: 100 | |
19 … | + background: white | |
20 … | + | |
21 … | + ul { | |
22 … | + margin: 0 | |
23 … | + padding: 0 | |
24 … | + list-style: none | |
25 … | + li { | |
26 … | + padding: 4px 8px | |
27 … | + font-size: 85% | |
28 … | + border-bottom: 1px solid #ddd | |
29 … | + selected { | |
30 … | + color: #fff | |
31 … | + background-color: #428bca | |
32 … | + border-color: darken(#428bca, 5%) | |
33 … | + } | |
34 … | + :last-child { | |
35 … | + border: 0 | |
36 … | + } | |
37 … | + img { | |
38 … | + height: 20px | |
39 … | + } | |
40 … | + } | |
41 … | + } | |
42 … | +} |
styles/light/tag-list.mcss | ||
---|---|---|
@@ -1,0 +1,126 @@ | ||
1 … | +TagList { | |
2 … | + a.tag { | |
3 … | + display: flex; | |
4 … | + padding: 4px; | |
5 … | + font-size: 110%; | |
6 … | + margin: 4px 0; | |
7 … | + background: rgba(255, 255, 255, 0.74); | |
8 … | + border-radius: 5px; | |
9 … | + position: relative | |
10 … | + text-decoration: none | |
11 … | + transition: background-color 0.2s | |
12 … | + | |
13 … | + background-repeat: no-repeat | |
14 … | + background-position: right | |
15 … | + | |
16 … | + -more { | |
17 … | + padding-top: 10px | |
18 … | + padding-bottom: 10px | |
19 … | + | |
20 … | + div.main { | |
21 … | + div.name { | |
22 … | + font-weight: normal | |
23 … | + } | |
24 … | + } | |
25 … | + } | |
26 … | + | |
27 … | + -following { | |
28 … | + background-image: svg(following) | |
29 … | + } | |
30 … | + | |
31 … | + -connected { | |
32 … | + background-image: svg(connected) | |
33 … | + } | |
34 … | + | |
35 … | + @svg connected { | |
36 … | + width: 20px | |
37 … | + height: 12px | |
38 … | + content: "<circle cx='6' stroke='none' fill='green' cy='6' r='5' />" | |
39 … | + } | |
40 … | + | |
41 … | + @svg following { | |
42 … | + width: 20px | |
43 … | + height: 12px | |
44 … | + content: "<circle cx='6' stroke='#888' fill='none' cy='6' r='5' /> <circle cx='6' cy='6' r='3' fill='#888'/>" | |
45 … | + } | |
46 … | + | |
47 … | + div.avatar { | |
48 … | + img { | |
49 … | + width: 40px | |
50 … | + height: 40px | |
51 … | + display: block | |
52 … | + } | |
53 … | + } | |
54 … | + | |
55 … | + div.main { | |
56 … | + display: flex | |
57 … | + flex-direction: column | |
58 … | + flex: 1 | |
59 … | + margin-left: 10px | |
60 … | + justify-content: center | |
61 … | + min-width: 0px | |
62 … | + div.name { | |
63 … | + white-space: nowrap | |
64 … | + font-weight: bold | |
65 … | + font-size: 110% | |
66 … | + color: #636363 | |
67 … | + -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,1) 90%, rgba(0,0,0,0)) | |
68 … | + } | |
69 … | + } | |
70 … | + | |
71 … | + div.progress { | |
72 … | + display: flex | |
73 … | + flex-direction: column | |
74 … | + svg { | |
75 … | + transition: opacity 0.2s | |
76 … | + opacity: 0 | |
77 … | + -pending { | |
78 … | + opacity: 1 | |
79 … | + } | |
80 … | + width: 20px | |
81 … | + flex: 1 | |
82 … | + } | |
83 … | + } | |
84 … | + | |
85 … | + div.controls { | |
86 … | + opacity: 0 | |
87 … | + position: absolute | |
88 … | + right: 7px | |
89 … | + top: 0 | |
90 … | + bottom: 0 | |
91 … | + display: flex; | |
92 … | + justify-content: center; | |
93 … | + flex-direction: column; | |
94 … | + width: 15px; | |
95 … | + transition: opacity 0.2s | |
96 … | + a.disconnect { | |
97 … | + color: white; | |
98 … | + border-radius: 10px; | |
99 … | + height: 16px; | |
100 … | + width: 16px; | |
101 … | + background-color: #777 | |
102 … | + text-align: center | |
103 … | + vertical-align: middle | |
104 … | + font-size: 14px; | |
105 … | + overflow: hidden; | |
106 … | + :hover { | |
107 … | + text-decoration: none | |
108 … | + background-color: #F77 | |
109 … | + } | |
110 … | + } | |
111 … | + } | |
112 … | + | |
113 … | + div.buttons { | |
114 … | + display: flex; | |
115 … | + align-items: center; | |
116 … | + padding-right: 10px; | |
117 … | + } | |
118 … | + | |
119 … | + :hover { | |
120 … | + background-color: rgba(255, 255, 255, 0.4); | |
121 … | + div.controls { | |
122 … | + opacity: 1 | |
123 … | + } | |
124 … | + } | |
125 … | + } | |
126 … | +} |
Built with git-ssb-web