git ssb

0+

alanz / patchwork



forked from Matt McKegg / patchwork

Commit 572440feaf959755763efb726087066a6f5b29db

quick dump

Matt McKegg committed on 10/27/2016, 3:07:41 AM

Files changed

.gitignoreadded
README.mdadded
api/index.jsadded
assets/base.htmladded
assets/img/emojiadded
index.jsadded
lib/context-menu.jsadded
lib/h.jsadded
lib/make-single-instance.jsadded
lib/serve-blobs.jsadded
lib/ssb-server.jsadded
lib/window.jsadded
main-window.jsadded
package.jsonadded
styles/base.mcssadded
styles/index.jsadded
styles/main-window.mcssadded
.gitignoreView
@@ -1,0 +1,1 @@
1+node_modules
README.mdView
@@ -1,0 +1,26 @@
1+patchwork-next
2+===
3+
4+Very early **work-in-progress** attempt at remaking [Patchwork](https://github.com/ssbc/patchwork) using [patchbay](https://github.com/dominictarr/patchbay) and UX/ideas from [ferment](https://github.com/mmckegg/ferment).
5+
6+The goal is to make a standalone, easy to install, "social" view into the ssb world.
7+
8+## Install and run
9+
10+```shell
11+$ git clone https://github.com/mmckegg/patchwork-next
12+$ cd patchwork-next
13+$ npm install
14+$ npm start
15+```
16+
17+## TODO
18+
19+- [ ] Preserve scroll on back button
20+- [ ] Main navigation buttons
21+- [ ] Easy navigation sidebar
22+- [ ] Contacts sidebar
23+- [ ] Lots of styling
24+- [ ] Compressed feed (the algorithm :wink:)
25+ - [ ] Thread previews in feed [figure out how to do this streaming]
26+- [ ] Add more todos!
api/index.jsView
@@ -1,0 +1,124 @@
1+var pull = require('pull-stream')
2+var ssbKeys = require('ssb-keys')
3+var ref = require('ssb-ref')
4+
5+function Hash (onHash) {
6+ var buffers = []
7+ return pull.through(function (data) {
8+ buffers.push('string' === typeof data
9+ ? new Buffer(data, 'utf8')
10+ : data
11+ )
12+ }, function (err) {
13+ if(err && !onHash) throw err
14+ var b = buffers.length > 1 ? Buffer.concat(buffers) : buffers[0]
15+ var h = '&'+ssbKeys.hash(b)
16+ onHash && onHash(err, h)
17+ })
18+}
19+//uncomment this to use from browser...
20+//also depends on having ssb-ws installed.
21+//var createClient = require('ssb-lite')
22+
23+var createFeed = require('ssb-feed')
24+var cache = CACHE = {}
25+
26+module.exports = function (sbot, opts) {
27+ var connection_status = []
28+ var keys = opts.keys
29+
30+ var internal = {
31+ getLatest: function (id, cb) {
32+ sbot.getLatest(id, cb)
33+ },
34+ add: function (msg, cb) {
35+ sbot.add(msg, cb)
36+ }
37+ }
38+
39+ var feed = createFeed(internal, keys, {remote: true})
40+
41+ setImmediate((x) => {
42+ connection_status.forEach(fn => fn())
43+ })
44+
45+ return {
46+ connection_status: connection_status,
47+ sbot_blobs_add: function (cb) {
48+ return pull(
49+ Hash(function (err, id) {
50+ if(err) return cb(err)
51+ //completely UGLY hack to tell when the blob has been sucessfully written...
52+ var start = Date.now(), n = 5
53+ ;(function next () {
54+ setTimeout(function () {
55+ sbot.blobs.has(id, function (err, has) {
56+ if(has) return cb(null, id)
57+ if(n--) next()
58+ else cb(new Error('write failed'))
59+ })
60+ }, Date.now() - start)
61+ })()
62+ }),
63+ sbot.blobs.add()
64+ )
65+ },
66+ sbot_links: function (query) {
67+ return sbot.links(query)
68+ },
69+ sbot_links2: function (query) {
70+ return sbot.links2.read(query)
71+ },
72+ sbot_query: function (query) {
73+ return sbot.query.read(query)
74+ },
75+ sbot_log: function (opts) {
76+ return pull(
77+ sbot.createLogStream(opts),
78+ pull.through(function (e) {
79+ CACHE[e.key] = CACHE[e.key] || e.value
80+ })
81+ )
82+ },
83+ sbot_user_feed: function (opts) {
84+ return sbot.createUserStream(opts)
85+ },
86+ sbot_get: function (key, cb) {
87+ if(CACHE[key]) cb(null, CACHE[key])
88+ else sbot.get(key, function (err, value) {
89+ if(err) return cb(err)
90+ cb(null, CACHE[key] = value)
91+ })
92+ },
93+ sbot_gossip_peers: function (cb) {
94+ sbot.gossip.peers(cb)
95+ },
96+ //liteclient won't have permissions for this
97+ sbot_gossip_connect: function (opts, cb) {
98+ sbot.gossip.connect(opts, cb)
99+ },
100+ sbot_publish: function (content, cb) {
101+ if(content.recps)
102+ content = ssbKeys.box(content, content.recps.map(function (e) {
103+ return ref.isFeed(e) ? e : e.link
104+ }))
105+ else if(content.mentions)
106+ content.mentions.forEach(function (mention) {
107+ if(ref.isBlob(mention.link)) {
108+ sbot.blobs.push(mention.link, function (err) {
109+ if(err) console.error(err)
110+ })
111+ }
112+ })
113+
114+ feed.add(content, function (err, msg) {
115+ if(err) console.error(err)
116+ else if(!cb) console.log(msg)
117+ cb && cb(err, msg)
118+ })
119+ },
120+ sbot_whoami: function (cb) {
121+ sbot.whoami(cb)
122+ }
123+ }
124+}
assets/base.htmlView
@@ -1,0 +1,5 @@
1+<!DOCTYPE html>
2+<html>
3+ <head></head>
4+ <body></body>
5+</html>
assets/img/emojiView
@@ -1,0 +1,1 @@
1+../../node_modules/emoji-named-characters/pngs
index.jsView
@@ -1,0 +1,100 @@
1+process.on('uncaughtException', function (err) {
2+ console.log(err)
3+ process.exit()
4+})
5+
6+var electron = require('electron')
7+var openWindow = require('./lib/window')
8+var createSbot = require('./lib/ssb-server')
9+var serveBlobs = require('./lib/serve-blobs')
10+var makeSingleInstance = require('./lib/make-single-instance')
11+var pull = require('pull-stream')
12+var pullFile = require('pull-file')
13+var Path = require('path')
14+var fs = require('fs')
15+var defaultMenu = require('electron-default-menu')
16+var Menu = electron.Menu
17+var dataUriToBuffer = require('data-uri-to-buffer')
18+var extend = require('xtend')
19+var ssbKeys = require('ssb-keys')
20+
21+var windows = {
22+ dialogs: new Set()
23+}
24+
25+var context = null
26+if (process.argv.includes('--use-global-ssb') || process.argv.includes('-g')) {
27+ context = setupContext('ssb', {
28+ server: false
29+ })
30+} else {
31+ makeSingleInstance(windows, openMainWindow)
32+ context = setupContext('ssb')
33+}
34+
35+electron.ipcMain.on('add-blob', (ev, id, path, cb) => {
36+ pull(
37+ path.startsWith('data:') ? pull.values([dataUriToBuffer(path)]) : pullFile(path),
38+ context.sbot.blobs.add((err, hash) => {
39+ if (err) return ev.sender.send('response', id, err)
40+ ev.sender.send('response', id, null, hash)
41+ })
42+ )
43+})
44+
45+electron.app.on('ready', function () {
46+ Menu.setApplicationMenu(Menu.buildFromTemplate(defaultMenu(electron.app, electron.shell)))
47+ openMainWindow()
48+})
49+
50+electron.app.on('activate', function (e) {
51+ openMainWindow()
52+})
53+
54+function openMainWindow () {
55+ if (!windows.main) {
56+ windows.main = openWindow(context, Path.join(__dirname, 'main-window.js'), {
57+ minWidth: 800,
58+ width: 1024,
59+ height: 768,
60+ titleBarStyle: 'hidden-inset',
61+ title: 'Ferment',
62+ show: true,
63+ backgroundColor: '#444',
64+ webPreferences: {
65+ experimentalFeatures: true
66+ },
67+ icon: './ferment-logo.png'
68+ })
69+ windows.main.setSheetOffset(40)
70+ windows.main.on('closed', function () {
71+ windows.main = null
72+ })
73+ }
74+}
75+
76+function setupContext (appName, opts) {
77+ var ssbConfig = require('ssb-config/inject')(appName, extend({
78+ port: 8008,
79+ blobsPort: 7777
80+ }, opts))
81+
82+ if (opts && opts.server === false) {
83+ return {
84+ config: ssbConfig
85+ }
86+ } else {
87+ ssbConfig.keys = ssbKeys.loadOrCreateSync(Path.join(ssbConfig.path, 'secret'))
88+ var context = {
89+ sbot: createSbot(ssbConfig),
90+ config: ssbConfig
91+ }
92+ ssbConfig.manifest = context.sbot.getManifest()
93+ serveBlobs(context)
94+ fs.writeFileSync(Path.join(ssbConfig.path, 'manifest.json'), JSON.stringify(ssbConfig.manifest))
95+ console.log(`Address: ${context.sbot.getAddress()}`)
96+ return context
97+ }
98+
99+ return ssbConfig
100+}
lib/context-menu.jsView
@@ -1,0 +1,44 @@
1+var electron = require('electron')
2+var Menu = electron.remote.Menu
3+var MenuItem = electron.remote.MenuItem
4+var BrowserWindow = electron.remote.BrowserWindow
5+
6+window.addEventListener('contextmenu', function (e) {
7+ module.exports(null, null, e)
8+}, false)
9+
10+module.exports = function (context, item, ev) {
11+ ev.preventDefault()
12+ ev.stopPropagation()
13+ var menu = new Menu()
14+ menu.append(new MenuItem({
15+ label: 'Reload',
16+ click: function (item, focusedWindow) {
17+ if (focusedWindow) {
18+ focusedWindow.reload()
19+ }
20+ }
21+ }))
22+ menu.append(new MenuItem({
23+ type: 'separator'
24+ }))
25+ menu.append(new MenuItem({
26+ label: 'Inspect Element',
27+ click: function () {
28+ var x = ev.clientX
29+ var y = ev.clientY
30+ BrowserWindow.getFocusedWindow().inspectElement(x, y)
31+ }
32+ }))
33+
34+ if (item && item.id) {
35+ menu.append(new MenuItem({
36+ label: 'Copy SSB ID',
37+ click: function () {
38+ electron.clipboard.writeText(item.id)
39+ }
40+ }))
41+ }
42+
43+ menu.popup(electron.remote.getCurrentWindow())
44+}
lib/h.jsView
@@ -1,0 +1,1 @@
1+module.exports = require('micro-css/h')(require('@mmckegg/mutant/html-element'))
lib/make-single-instance.jsView
@@ -1,0 +1,13 @@
1+var electron = require('electron')
2+module.exports = function (windows, openMainWindow) {
3+ if (electron.app.makeSingleInstance((commandLine, workingDirectory) => {
4+ if (windows.main) {
5+ if (windows.main.isMinimized()) windows.main.restore()
6+ windows.main.focus()
7+ } else {
8+ openMainWindow()
9+ }
10+ })) {
11+ electron.app.quit()
12+ }
13+}
lib/serve-blobs.jsView
@@ -1,0 +1,60 @@
1+var pull = require('pull-stream')
2+var cat = require('pull-cat')
3+var toPull = require('stream-to-pull-stream')
4+var ident = require('pull-identify-filetype')
5+var mime = require('mime-types')
6+var URL = require('url')
7+var http = require('http')
8+
9+module.exports = function (context, cb) {
10+ return http.createServer(ServeBlobs(context.sbot)).listen(context.config.blobsPort, cb)
11+}
12+
13+function ServeBlobs (sbot) {
14+ return function (req, res, next) {
15+ var parsed = URL.parse(req.url, true)
16+ var hash = decodeURIComponent(parsed.pathname.slice(1))
17+ sbot.blobs.want(hash, function (_, has) {
18+ if (!has) return respond(res, 404, 'File not found')
19+ // optional name override
20+ if (parsed.query.name) {
21+ res.setHeader('Content-Disposition', 'inline; filename=' + encodeURIComponent(parsed.query.name))
22+ }
23+
24+ // serve
25+ res.setHeader('Content-Security-Policy', BlobCSP())
26+ respondSource(res, sbot.blobs.get(hash), false)
27+ })
28+ }
29+}
30+
31+function respondSource (res, source, wrap) {
32+ if (wrap) {
33+ res.writeHead(200, {'Content-Type': 'text/html'})
34+ pull(
35+ cat([
36+ pull.once('<html><body><script>'),
37+ source,
38+ pull.once('</script></body></html>')
39+ ]),
40+ toPull.sink(res)
41+ )
42+ } else {
43+ pull(
44+ source,
45+ ident(function (type) {
46+ if (type) res.writeHead(200, {'Content-Type': mime.lookup(type)})
47+ }),
48+ toPull.sink(res)
49+ )
50+ }
51+}
52+
53+function respond (res, status, message) {
54+ res.writeHead(status)
55+ res.end(message)
56+}
57+
58+function BlobCSP () {
59+ return 'default-src none; sandbox'
60+}
lib/ssb-server.jsView
@@ -1,0 +1,13 @@
1+module.exports = require('scuttlebot')
2+ .use(require('scuttlebot/plugins/master'))
3+ .use(require('scuttlebot/plugins/gossip'))
4+ .use(require('scuttlebot/plugins/friends'))
5+ .use(require('scuttlebot/plugins/replicate'))
6+ .use(require('ssb-blobs'))
7+ .use(require('scuttlebot/plugins/invite'))
8+ .use(require('scuttlebot/plugins/block'))
9+ .use(require('scuttlebot/plugins/local'))
10+ .use(require('scuttlebot/plugins/logging'))
11+ .use(require('scuttlebot/plugins/private'))
12+ .use(require('ssb-links'))
13+ .use(require('ssb-query'))
lib/window.jsView
@@ -1,0 +1,92 @@
1+var Path = require('path')
2+var electron = require('electron')
3+var extend = require('xtend/mutable')
4+
5+module.exports = function Window (context, path, opts) {
6+ var window = new electron.BrowserWindow(extend({
7+ show: false
8+ }, opts))
9+
10+ window.setMenu(null)
11+ electron.ipcMain.on('ready-to-show', handleReadyToShow)
12+
13+ window.webContents.on('dom-ready', function () {
14+ window.webContents.executeJavaScript(`
15+ var electron = require('electron')
16+ var rootView = require(${JSON.stringify(path)})
17+ var insertCss = require('insert-css')
18+ var h = require('../lib/h')
19+ var createClient = require('ssb-client')
20+
21+ require('../lib/context-menu')
22+ insertCss(require('../styles'))
23+ electron.webFrame.setZoomLevelLimits(1, 1)
24+
25+ var config = ${JSON.stringify(context.config)}
26+ var data = ${JSON.stringify(opts.data)}
27+ var title = ${JSON.stringify(opts.title || 'Patchwork')}
28+
29+ document.documentElement.querySelector('head').appendChild(
30+ h('title', title)
31+ )
32+
33+ var shouldShow = ${opts.show !== false}
34+ var shouldConnect = ${opts.connect !== false}
35+
36+ if (shouldConnect) {
37+ createClient(config.keys, config, (err, client) => {
38+ if (err) {
39+ electron.remote.getGlobal('console').log(err)
40+ throw err
41+ } else {
42+ render(client)
43+ }
44+ })
45+ } else {
46+ render()
47+ }
48+
49+ function render (client) {
50+ try {
51+ document.documentElement.replaceChild(h('body', [
52+ rootView(client, config, data)
53+ ]), document.body)
54+ } catch (ex) {
55+ electron.ipcRenderer.send('ready-to-show')
56+ throw ex
57+ }
58+ shouldShow && electron.ipcRenderer.send('ready-to-show')
59+ }
60+ `)
61+ })
62+
63+ // setTimeout(function () {
64+ // window.show()
65+ // }, 3000)
66+
67+ window.webContents.on('will-navigate', function (e, url) {
68+ e.preventDefault()
69+ electron.shell.openExternal(url)
70+ })
71+
72+ window.webContents.on('new-window', function (e, url) {
73+ e.preventDefault()
74+ electron.shell.openExternal(url)
75+ })
76+
77+ window.on('closed', function () {
78+ electron.ipcMain.removeListener('ready-to-show', handleReadyToShow)
79+ })
80+
81+ window.loadURL('file://' + Path.join(__dirname, '..', 'assets', 'base.html'))
82+ return window
83+
84+ // scoped
85+
86+ function handleReadyToShow (ev) {
87+ if (ev.sender === window) {
88+ window.show()
89+ electron.ipcMain.removeListener('ready-to-show', handleReadyToShow)
90+ }
91+ }
92+}
main-window.jsView
@@ -1,0 +1,116 @@
1+var combine = require('depject')
2+var Modules = require('patchbay/modules')
3+var SbotApi = require('./api')
4+var extend = require('xtend')
5+var h = require('./lib/h')
6+var plugs = require('patchbay/plugs')
7+var Value = require('@mmckegg/mutant/value')
8+var when = require('@mmckegg/mutant/when')
9+var computed = require('@mmckegg/mutant/computed')
10+
11+module.exports = function (ssbClient, config) {
12+ var api = SbotApi(ssbClient, config)
13+ var modules = combine(extend(Modules, {
14+ 'sbot-api.js': api,
15+ 'blob-url.js': {
16+ blob_url: function (link) {
17+ var prefix = config.blobsPrefix != null ? config.blobsPrefix : `http://localhost:${config.blobsPort}`
18+ if (typeof link.link === 'string') {
19+ link = link.link
20+ }
21+ return `${prefix}/${encodeURIComponent(link)}`
22+ }
23+ }
24+ }))
25+
26+ var screenView = plugs.first(modules.plugs.screen_view)
27+ var forwardHistory = []
28+ var backHistory = []
29+ var views = {
30+ '/public': screenView('/public')
31+ }
32+ var canGoForward = Value(false)
33+ var canGoBack = Value(false)
34+ var currentView = Value(['/public'])
35+ var rootElement = computed(currentView, (data) => {
36+ if (Array.isArray(data)) {
37+ return views[data[0]]
38+ }
39+ })
40+
41+ window.onhashchange = function (ev) {
42+ setView(window.location.hash.substring(1))
43+ }
44+
45+ var mainElement = h('div.main', [
46+ rootElement
47+ ])
48+
49+ return h('MainWindow', {
50+ classList: [ '-' + process.platform ]
51+ }, [
52+ h('div.top', [
53+ h('span.history', [
54+ h('a', {
55+ 'ev-click': goBack,
56+ classList: [ when(canGoBack, '-active') ]
57+ }, '<'),
58+ h('a', {
59+ 'ev-click': goForward,
60+ classList: [ when(canGoForward, '-active') ]
61+ }, '>')
62+ ]),
63+ h('span.appTitle', ['Patchwork'])
64+ ]),
65+ mainElement
66+ ])
67+
68+ // scoped
69+
70+ function goBack () {
71+ if (backHistory.length) {
72+ canGoForward.set(true)
73+ forwardHistory.push(currentView())
74+ currentView.set(backHistory.pop())
75+ canGoBack.set(backHistory.length > 0)
76+ }
77+ }
78+
79+ function goForward () {
80+ if (forwardHistory.length) {
81+ backHistory.push(currentView())
82+ currentView.set(forwardHistory.pop())
83+ canGoForward.set(forwardHistory.length > 0)
84+ canGoBack.set(true)
85+ }
86+ }
87+
88+ function setView (view, ...args) {
89+ var newView = [view, ...args]
90+ if (!isSame(newView, currentView())) {
91+ if (!views[view]) {
92+ views[view] = screenView(view, ...args)
93+ }
94+
95+ canGoForward.set(false)
96+ canGoBack.set(true)
97+ forwardHistory.length = 0
98+ backHistory.push(currentView())
99+ currentView.set(newView)
100+ currentView().scrollTop = 0
101+ }
102+ }
103+}
104+
105+function isSame (a, b) {
106+ if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
107+ for (var i = 0; i < a.length; i++) {
108+ if (a[i] !== b[i]) {
109+ return false
110+ }
111+ }
112+ return true
113+ } else if (a === b) {
114+ return true
115+ }
116+}
package.jsonView
@@ -1,0 +1,33 @@
1+{
2+ "name": "patchwork-next",
3+ "version": "0.0.0",
4+ "description": "",
5+ "main": "index.js",
6+ "scripts": {
7+ "test": "echo \"Error: no test specified\" && exit 1",
8+ "start": "electron index.js",
9+ "postinstall": "npm run rebuild",
10+ "rebuild": "npm rebuild --runtime=electron --target=1.4.3 --abi=50 --disturl=https://atom.io/download/atom-shell"
11+ },
12+ "author": "",
13+ "license": "ISC",
14+ "dependencies": {
15+ "@mmckegg/mutant": "^3.6.1",
16+ "data-uri-to-buffer": "0.0.4",
17+ "electron": "^1.4.4",
18+ "electron-default-menu": "^1.0.0",
19+ "insert-css": "^1.0.0",
20+ "micro-css": "^0.6.2",
21+ "patchbay": "^3.5.0",
22+ "pull-file": "^1.0.0",
23+ "pull-identify-filetype": "^1.1.0",
24+ "pull-stream": "^3.4.5",
25+ "scuttlebot": "^9.2.0",
26+ "ssb-blobs": "^0.1.7",
27+ "ssb-keys": "^7.0.0",
28+ "ssb-links": "^2.0.0",
29+ "ssb-query": "^0.1.1",
30+ "ssb-ref": "^2.6.2",
31+ "ssb-ws": "^0.6.2"
32+ }
33+}
styles/base.mcssView
@@ -1,0 +1,101 @@
1+html, body {
2+ background: #ccc
3+ margin: 0
4+ font-family: caption, sans-serif
5+ overflow: hidden
6+ height: 100%
7+ font-size: 12px
8+ -webkit-user-select: none
9+ color: #2b2b2b
10+}
11+
12+body {
13+ display: flex
14+ flex-direction: column
15+ line-height: 1.2
16+}
17+
18+h1 {
19+ color: #C0C0C0
20+ font-size: 200%
21+ margin: 4px 0
22+ font-weight: normal
23+ text-shadow: 0px 0px 2px black
24+ flex: 1
25+}
26+
27+select {
28+ font-size: 80%
29+ display: inline-block
30+ padding: 2px 4px
31+ height: 18px
32+ border: 1px solid #A9A9A9
33+ background: #666 svg(dropArrow) no-repeat right
34+ -webkit-appearance: none
35+ color: #FFF
36+ padding-right: 12px
37+ border-radius: 0
38+
39+ :hover {
40+ background-image: svg(dropArrow -active)
41+ }
42+
43+ @svg dropArrow {
44+ width: 12px
45+ height: 6px
46+ content: "<path d='M2,0 L10,0 L6,6 Z' />"
47+
48+ path {
49+ fill: #888
50+ }
51+
52+ -active {
53+ path {
54+ fill: #DDD
55+ }
56+ }
57+ }
58+}
59+
60+input {
61+ [type='text'] {
62+ font-size: 80%
63+ display: inline-block
64+ padding: 2px 4px
65+ height: 18px
66+ border: 1px solid #A9A9A9
67+ background: #333
68+ color: #FFF
69+ padding-right: 12px
70+ border-radius: 0
71+ }
72+}
73+
74+::-webkit-file-upload-button {
75+ font-family: inherit
76+}
77+
78+a {
79+ color: #ebf7b1
80+ text-decoration: none
81+
82+ code {
83+ color: #8EC1FC
84+ }
85+
86+ :hover {
87+ text-decoration: underline
88+ }
89+}
90+
91+* + h1 {
92+ margin-top: 16px
93+}
94+
95+* {
96+ box-sizing:border-box
97+}
98+
99+input, textarea, keygen, select, button {
100+ font-family: '.SFNSText-Regular', sans-serif
101+}
styles/index.jsView
@@ -1,0 +1,19 @@
1+var fs = require('fs')
2+var path = require('path')
3+var compile = require('micro-css')
4+var result = ''
5+var additional = ''
6+
7+fs.readdirSync(__dirname).forEach(function (file) {
8+ if (/\.mcss$/i.test(file)) {
9+ result += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n'
10+ }
11+
12+ if (/\.css$/i.test(file)) {
13+ additional += fs.readFileSync(path.resolve(__dirname, file), 'utf8') + '\n'
14+ }
15+})
16+
17+additional += fs.readFileSync(require.resolve('patchbay/style.css'), 'utf8')
18+
19+module.exports = compile(result) + additional
styles/main-window.mcssView
@@ -1,0 +1,158 @@
1+MainWindow {
2+ height: 100%
3+ display: flex
4+ flex-direction: column
5+
6+ -darwin {
7+ div.top {
8+ padding-left: 70px
9+ }
10+ div.top {
11+ span.appTitle {
12+ margin-right: 40px;
13+ }
14+ }
15+ }
16+
17+ -win32 {
18+ div.top {
19+ padding-right: 70px
20+ }
21+ }
22+
23+ div.top {
24+ -webkit-app-region: drag;
25+ display: flex;
26+ background: #fff;
27+ padding: 10px;
28+ border-bottom: 2px solid #e2e2e2;
29+ box-shadow: 0 0 3px #7f7f7f;
30+ position: relative;
31+
32+ span {
33+ a {
34+ padding: 2px 8px;
35+ border: 2px solid #bbbbbb;
36+ border-radius: 4px;
37+ background: #a7a6a6;
38+ color: #000000;
39+ font-size: 120%;
40+ cursor: pointer;
41+ margin-left: 5px;
42+
43+ :hover {
44+ text-decoration: none
45+ color: black
46+ border-color: #888
47+ }
48+
49+ -selected {
50+ border-color: #444
51+ background: #CCC
52+ color: black
53+ }
54+
55+ -add {
56+ border-color: #498849
57+ background-color: #255D24
58+ text-shadow: 1px 1px 1px #000
59+ color: white
60+
61+ :active {
62+ background-color: #1F331F !important
63+ }
64+
65+ :hover {
66+ background-color: #356D34
67+ border-color: #4CB54C
68+ }
69+ }
70+ }
71+ }
72+
73+ span.history {
74+ a {
75+ opacity: 0.3
76+
77+ -active {
78+ opacity: 1
79+ }
80+ }
81+
82+ a + a {
83+ margin-left: 0
84+ }
85+ }
86+
87+ span.appTitle {
88+ flex: 1;
89+ text-align: center;
90+ font-size: 130%;
91+ color: #757575;
92+ letter-spacing: 0.1em;
93+ font-weight: bold;
94+ font-weight: normal;
95+ }
96+ }
97+
98+ div.info {
99+ a.message {
100+ display: block
101+ padding: 10px
102+ background: #314427
103+ transition: color 0.2s, background-color 0.2s
104+
105+ :hover {
106+ text-decoration: none
107+ color: white
108+ background: #315427
109+ }
110+ }
111+ box-shadow: 0 0 3px black
112+ overflow: hidden
113+ animation: 0.5s slide-in
114+ position: relative
115+ }
116+
117+ div.main {
118+ flex: 1
119+ overflow: auto
120+ display: flex
121+
122+ div {
123+ flex: 1
124+ }
125+ }
126+
127+ div.bottom {
128+ position: relative
129+ box-shadow: 0 0 3px #222
130+ background: #222
131+ align-items: center
132+ display: flex
133+ padding: 5px
134+
135+ audio {
136+ color: #EEE
137+
138+ ::-webkit-media-controls-panel {
139+ background: transparent
140+ }
141+
142+ ::-webkit-media-controls-current-time-display {
143+ color: inherit
144+ }
145+
146+ width: 100%
147+ }
148+ }
149+}
150+
151+@keyframes slide-in {
152+ 0% {
153+ max-height: 0
154+ }
155+ 100% {
156+ max-height: 100px
157+ }
158+}

Built with git-ssb-web