git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Commit 741ac0a148c2a18f2bc5d54ad7582e7c82430d2b

Integrating with patch-tag

Josiah Witt committed on 3/15/2018, 2:38:25 AM
Parent: 011ddc7e8f1977900832da37876ff81783bc7b9f

Files changed

main-window.jschanged
modules/message/html/tag.jsadded
modules/page/html/render/tag.jsadded
modules/sheet/display.jschanged
modules/sheet/editTags.jsadded
modules/sheet/tags.jsadded
modules/tag/html/tag.jsadded
package-lock.jsonchanged
package.jsonchanged
plugs/message/html/meta/tags.jsadded
server-process.jschanged
styles/light/message.mcsschanged
styles/light/editTags.mcssadded
styles/light/tag-list.mcssadded
styles/light/tag.mcssadded
styles/light/tags.mcssadded
main-window.jsView
@@ -20,8 +20,9 @@
2020 require('./modules'),
2121 require('./plugs'),
2222 require('patch-settings'),
2323 require('patchcore'),
24 + require('patch-tag'),
2425 require('./overrides')
2526 )
2627
2728 var api = entry(sockets, nest({
@@ -110,8 +111,9 @@
110111 tab(i18n('Private'), '/private'),
111112 dropTab(i18n('More'), [
112113 getSubscribedChannelMenu,
113114 [i18n('Gatherings'), '/gatherings'],
115 + ['Tags', '/tags'],
114116 [i18n('Extended Network'), '/all'],
115117 {separator: true},
116118 [i18n('Settings'), '/settings']
117119 ])
modules/message/html/tag.jsView
@@ -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.jsView
@@ -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.jsView
@@ -4,17 +4,19 @@
44 exports.gives = nest('sheet.display')
55
66 exports.create = function () {
77 return nest('sheet.display', function (handler) {
8- var {content, footer} = handler(done)
8 + var {content, footer, mounted} = handler(done)
99
1010 var container = h('div', {className: 'Sheet'}, [
1111 h('section', [content]),
1212 h('footer', [footer])
1313 ])
1414
1515 document.body.appendChild(container)
1616
17 + if (mounted) mounted()
18 +
1719 function done () {
1820 document.body.removeChild(container)
1921 }
2022 })
modules/sheet/editTags.jsView
@@ -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.jsView
@@ -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.jsView
@@ -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.jsonView
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.jsonView
@@ -31,8 +31,9 @@
3131 "flatpickr": "^3.0.5-1",
3232 "flumeview-level": "^2.0.3",
3333 "flumeview-reduce": "^1.3.12",
3434 "hashlru": "^2.2.0",
35 + "hex-rgb": "^2.0.0",
3536 "human-time": "0.0.1",
3637 "i18n": "^0.8.3",
3738 "level": "~1.7.0",
3839 "lrucache": "^1.0.2",
plugs/message/html/meta/tags.jsView
@@ -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.jsView
@@ -17,8 +17,9 @@
1717 .use(require('scuttlebot/plugins/local'))
1818 .use(require('scuttlebot/plugins/logging'))
1919 .use(require('ssb-query'))
2020 .use(require('ssb-about'))
21 + .use(require('ssb-tags'))
2122 // .use(require('ssb-ebt')) // enable at your own risk!
2223 .use(require('./sbot'))
2324
2425 fixPath()
styles/light/message.mcssView
@@ -204,8 +204,13 @@
204204 color: #286bc3;
205205 font-size:90%;
206206 }
207207
208 + a.tags {
209 + color: #286bc3;
210 + font-size:90%;
211 + }
212 +
208213 span.private {
209214 display: inline-block;
210215 margin: -3px -3px 3px 4px;
211216 border: 4px solid #525050;
styles/light/editTags.mcssView
@@ -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.mcssView
@@ -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 +}
styles/light/tag.mcssView
@@ -1,0 +1,14 @@
1 +Tag {
2 + display: inline-block
3 + padding: .4rem .6rem
4 + background-color: lightgray
5 + border-radius: 2px
6 + margin-right: .3rem
7 +
8 + a {
9 + margin-left: .3rem
10 + cursor: pointer
11 + font-weight: 700
12 + color: inherit
13 + }
14 +}
styles/light/tags.mcssView
@@ -1,0 +1,3 @@
1 +div.messagewrapper {
2 + margin-bottom: 1rem
3 +}

Built with git-ssb-web