mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 03:23:09 +03:00
Merge branch 'master' into lf/profile-overlay
This commit is contained in:
commit
8e028ad255
39
.github/ISSUE_TEMPLATE/kernel-or-runtime-bug-report.md
vendored
Normal file
39
.github/ISSUE_TEMPLATE/kernel-or-runtime-bug-report.md
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
name: Kernel or runtime bug report
|
||||
about: Use this template to file a bug for low-level system components, e.g. Hoon,
|
||||
Arvo, Zuse, the vanes, Vere, etc.
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- A good bug report, description of a crash, etc., should ideally be *reproducible*, with clear steps as to how another developer can replicate and examine your problem. That said, this isn't always possible; some bugs depend on having created a complicated or unusual state, or can otherwise simply be difficult to trigger again (say, you encountered it in the last continuity era).
|
||||
|
||||
Your issue should thus at a minimum be *informative*. The best advice here is probably "don't write bad issues," where "bad" is a matter of judgment and taste. Issues that the maintainers don't judge to be sufficiently useful or informative may be closed. -->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour:
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
**Expected behaviour**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**System (please supply the following information, if relevant):**
|
||||
- OS: [e.g. macOS, linux64, FreeBSD]
|
||||
- Vere and Urbit OS versions
|
||||
- Your ship's `%base` hash (use `.^(@uv %cz /=base=)` to check)
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
**Notify maintainers**
|
||||
If you happen to know who the appropriate maintainers are, consider mentioning them with an @ here. You may want to use `git blame` to see who has last touched any relevant code.
|
@ -119,6 +119,9 @@ this:
|
||||
```
|
||||
urbit-vx.y.z
|
||||
|
||||
Note that this Vere release will by default boot fresh ships using an Urbit OS
|
||||
va.b.c pill.
|
||||
|
||||
Release binaries:
|
||||
|
||||
(linux64)
|
||||
@ -138,9 +141,11 @@ Contributions:
|
||||
|
||||
The same schpeel re: release candidates applies here.
|
||||
|
||||
Do not include implicit Urbit OS changes in Vere releases. This used to be
|
||||
done, historically, but shouldn't be any longer. If there are Urbit OS and
|
||||
Vere changes to be released, make two releases.
|
||||
Note that the release notes indicate which version of Urbit OS the Vere release
|
||||
will use by default when booting fresh ships. Do not include implicit Urbit OS
|
||||
changes in Vere releases; this used to be done, historically, but shouldn't be
|
||||
any longer. If there are Urbit OS and Vere changes to be released, make two
|
||||
separate releases.
|
||||
|
||||
### Deploy the update
|
||||
|
||||
|
BIN
pkg/arvo/app/chat/img/CodeEval.png
Normal file
BIN
pkg/arvo/app/chat/img/CodeEval.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 498 B |
@ -1938,8 +1938,6 @@
|
||||
=/ old-comment (~(get by comments.u.note) comment-date.del)
|
||||
?~ old-comment
|
||||
[~ sty]
|
||||
?: =(our.bol author.u.old-comment)
|
||||
[~ sty]
|
||||
=. comments.u.note (~(put by comments.u.note) comment-date.del data.del)
|
||||
=. notes.u.book (~(put by notes.u.book) note.del u.note)
|
||||
(emit-updates-and-state host.del book.del u.book del sty)
|
||||
|
@ -83,7 +83,7 @@
|
||||
:~ who+(su fed:ag)
|
||||
book+so
|
||||
note+so
|
||||
comment+(su ;~(pfix sig (cook year when:^so)))
|
||||
comment+so
|
||||
body+so
|
||||
==
|
||||
::
|
||||
@ -96,7 +96,7 @@
|
||||
:~ who+(su fed:ag)
|
||||
book+so
|
||||
note+so
|
||||
comment+(su ;~(pfix sig (cook year when:^so)))
|
||||
comment+so
|
||||
==
|
||||
++ subscribe
|
||||
%- ot
|
||||
|
@ -23,7 +23,7 @@ import qualified Urbit.Time as Time
|
||||
data AmesDrv = AmesDrv
|
||||
{ aTurfs :: TVar (Maybe [Turf])
|
||||
, aGalaxies :: IORef (M.Map Galaxy (Async (), TQueue ByteString))
|
||||
, aSocket :: Maybe Socket
|
||||
, aSocket :: TVar (Maybe Socket)
|
||||
, aListener :: Async ()
|
||||
, aSendingQueue :: TQueue (SockAddr, ByteString)
|
||||
, aSendingThread :: Async ()
|
||||
@ -88,8 +88,6 @@ renderGalaxy = Ob.renderPatp . Ob.patp . fromIntegral . unPatp
|
||||
enqueueEv -- Queue-event action.
|
||||
mPort -- Explicit port override from command line arguments.
|
||||
|
||||
TODO Handle socket exceptions in waitPacket
|
||||
|
||||
4096 is a reasonable number for recvFrom. Packets of that size are
|
||||
not possible on the internet.
|
||||
|
||||
@ -114,7 +112,8 @@ ames inst who isFake enqueueEv stderr =
|
||||
start = do
|
||||
aTurfs <- newTVarIO Nothing
|
||||
aGalaxies <- newIORef mempty
|
||||
aSocket <- bindSock
|
||||
aSocket <- newTVarIO Nothing
|
||||
bindSock aSocket
|
||||
aListener <- async (waitPacket aSocket)
|
||||
aSendingQueue <- newTQueueIO
|
||||
aSendingThread <- async (sendingThread aSendingQueue aSocket)
|
||||
@ -135,11 +134,11 @@ ames inst who isFake enqueueEv stderr =
|
||||
|
||||
cancel aSendingThread
|
||||
cancel aListener
|
||||
io $ maybeM (pure ()) (close') (pure aSocket)
|
||||
-- io $ close' aSocket
|
||||
socket <- atomically $ readTVar aSocket
|
||||
io $ maybeM (pure ()) (close') (pure socket)
|
||||
|
||||
bindSock :: RIO e (Maybe Socket)
|
||||
bindSock = getBindAddr >>= doBindSocket
|
||||
bindSock :: TVar (Maybe Socket) -> RIO e ()
|
||||
bindSock socketVar = getBindAddr >>= doBindSocket
|
||||
where
|
||||
getBindAddr = netMode >>= \case
|
||||
Fake -> pure $ Just localhost
|
||||
@ -147,8 +146,8 @@ ames inst who isFake enqueueEv stderr =
|
||||
Real -> pure $ Just inaddrAny
|
||||
NoNetwork -> pure Nothing
|
||||
|
||||
doBindSocket :: Maybe HostAddress -> RIO e (Maybe Socket)
|
||||
doBindSocket Nothing = pure Nothing
|
||||
doBindSocket :: Maybe HostAddress -> RIO e ()
|
||||
doBindSocket Nothing = atomically $ writeTVar socketVar Nothing
|
||||
doBindSocket (Just bindAddr) = do
|
||||
mode <- netMode
|
||||
mPort <- view (networkConfigL . ncAmesPort)
|
||||
@ -159,16 +158,28 @@ ames inst who isFake enqueueEv stderr =
|
||||
let addr = SockAddrInet ourPort bindAddr
|
||||
() <- io $ bind s addr
|
||||
|
||||
pure $ Just s
|
||||
atomically $ writeTVar socketVar (Just s)
|
||||
|
||||
waitPacket :: TVar (Maybe Socket) -> RIO e ()
|
||||
waitPacket socketVar = do
|
||||
(atomically $ readTVar socketVar) >>= \case
|
||||
Nothing -> pure ()
|
||||
Just s -> do
|
||||
res <- io $ tryIOError $ recvFrom s 4096
|
||||
case res of
|
||||
Left exn -> do
|
||||
-- When we have a socket exception, we need to rebuild the
|
||||
-- socket.
|
||||
logTrace $ displayShow ("(ames) Socket exception. Rebinding.")
|
||||
bindSock socketVar
|
||||
Right (bs, addr) -> do
|
||||
logTrace $ displayShow ("(ames) Received packet from ", addr)
|
||||
case addr of
|
||||
SockAddrInet p a -> atomically (enqueueEv $ hearEv p a bs)
|
||||
_ -> pure ()
|
||||
|
||||
waitPacket socketVar
|
||||
|
||||
waitPacket :: Maybe Socket -> RIO e ()
|
||||
waitPacket Nothing = pure ()
|
||||
waitPacket (Just s) = forever $ do
|
||||
(bs, addr) <- io $ recvFrom s 4096
|
||||
logTrace $ displayShow ("(ames) Received packet from ", addr)
|
||||
case addr of
|
||||
SockAddrInet p a -> atomically (enqueueEv $ hearEv p a bs)
|
||||
_ -> pure ()
|
||||
|
||||
handleEffect :: AmesDrv -> NewtEf -> RIO e ()
|
||||
handleEffect drv@AmesDrv{..} = \case
|
||||
@ -216,18 +227,23 @@ ames inst who isFake enqueueEv stderr =
|
||||
|
||||
-- An outbound queue of messages. We can only write to a socket from one
|
||||
-- thread, so coalesce those writes here.
|
||||
sendingThread :: TQueue (SockAddr, ByteString) -> Maybe Socket -> RIO e ()
|
||||
sendingThread queue Nothing = pure ()
|
||||
sendingThread queue (Just socket) = forever $
|
||||
sendingThread :: TQueue (SockAddr, ByteString)
|
||||
-> TVar (Maybe Socket)
|
||||
-> RIO e ()
|
||||
sendingThread queue socketVar = forever $
|
||||
do
|
||||
(dest, bs) <- atomically $ readTQueue queue
|
||||
logTrace $ displayShow ("(ames) Sending packet to ", socket, dest)
|
||||
logTrace $ displayShow ("(ames) Sending packet to ", dest)
|
||||
sendAll bs dest
|
||||
where
|
||||
sendAll bs dest = do
|
||||
bytesSent <- io $ sendTo socket bs dest
|
||||
when (bytesSent /= BS.length bs) $ do
|
||||
sendAll (drop bytesSent bs) dest
|
||||
mybSocket <- atomically $ readTVar socketVar
|
||||
case mybSocket of
|
||||
Nothing -> pure ()
|
||||
Just socket -> do
|
||||
bytesSent <- io $ sendTo socket bs dest
|
||||
when (bytesSent /= BS.length bs) $ do
|
||||
sendAll (drop bytesSent bs) dest
|
||||
|
||||
-- Asynchronous thread per galaxy which handles domain resolution, and can
|
||||
-- block its own queue of ByteStrings to send.
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: urbit-king
|
||||
version: 0.10.1
|
||||
version: 0.10.4
|
||||
license: MIT
|
||||
license-file: LICENSE
|
||||
|
||||
|
@ -7,6 +7,8 @@ var sucrase = require('@sucrase/gulp-plugin');
|
||||
var minify = require('gulp-minify');
|
||||
var rename = require('gulp-rename');
|
||||
var del = require('del');
|
||||
var json = require('rollup-plugin-json');
|
||||
|
||||
|
||||
var resolve = require('rollup-plugin-node-resolve');
|
||||
var commonjs = require('rollup-plugin-commonjs');
|
||||
@ -69,6 +71,7 @@ gulp.task('js-imports', function(cb) {
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
json(),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
@ -95,6 +98,7 @@ gulp.task('tile-js-imports', function(cb) {
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
json(),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
@ -127,6 +131,7 @@ gulp.task('js-imports-prod', function(cb) {
|
||||
extensions: '.js'
|
||||
}),
|
||||
globals(),
|
||||
json(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
|
350
pkg/interface/chat/package-lock.json
generated
350
pkg/interface/chat/package-lock.json
generated
@ -537,6 +537,11 @@
|
||||
"now-and-later": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"bail": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
|
||||
"integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@ -857,6 +862,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"character-entities": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
|
||||
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="
|
||||
},
|
||||
"character-entities-legacy": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
|
||||
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="
|
||||
},
|
||||
"character-reference-invalid": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
|
||||
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||
@ -1017,8 +1037,7 @@
|
||||
"clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
|
||||
"dev": true
|
||||
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
|
||||
},
|
||||
"clone-buffer": {
|
||||
"version": "1.0.0",
|
||||
@ -1060,6 +1079,16 @@
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
||||
"dev": true
|
||||
},
|
||||
"codemirror": {
|
||||
"version": "5.52.2",
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.52.2.tgz",
|
||||
"integrity": "sha512-WCGCixNUck2HGvY8/ZNI1jYfxPG5cRHv0VjmWuNzbtCLz8qYA5d+je4QhSSCtCaagyeOwMi/HmmPTjBgiTm2lQ=="
|
||||
},
|
||||
"collapse-white-space": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
|
||||
"integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ=="
|
||||
},
|
||||
"collect-stream": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/collect-stream/-/collect-stream-1.2.1.tgz",
|
||||
@ -1542,7 +1571,6 @@
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
|
||||
"integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"entities": "^2.0.0"
|
||||
@ -1551,8 +1579,7 @@
|
||||
"domelementtype": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1562,6 +1589,21 @@
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
|
||||
"dev": true
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
|
||||
"integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"domelementtype": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
|
||||
@ -1621,8 +1663,7 @@
|
||||
"entities": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
|
||||
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
@ -1776,8 +1817,7 @@
|
||||
"extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"extend-shallow": {
|
||||
"version": "1.1.4",
|
||||
@ -3229,6 +3269,45 @@
|
||||
"integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"html-to-react": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.2.tgz",
|
||||
"integrity": "sha512-TdTfxd95sRCo6QL8admCkE7mvNNrXtGoVr1dyS+7uvc8XCqAymnf/6ckclvnVbQNUo2Nh21VPwtfEHd0khiV7g==",
|
||||
"requires": {
|
||||
"domhandler": "^3.0",
|
||||
"htmlparser2": "^4.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"ramda": "^0.26"
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
|
||||
"integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^3.0.0",
|
||||
"domutils": "^2.0.0",
|
||||
"entities": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"domelementtype": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz",
|
||||
"integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==",
|
||||
"requires": {
|
||||
"dom-serializer": "^0.2.1",
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-https": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz",
|
||||
@ -3336,6 +3415,20 @@
|
||||
"kind-of": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"is-alphabetical": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
|
||||
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="
|
||||
},
|
||||
"is-alphanumerical": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
|
||||
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
|
||||
"requires": {
|
||||
"is-alphabetical": "^1.0.0",
|
||||
"is-decimal": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
@ -3391,6 +3484,11 @@
|
||||
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
|
||||
"dev": true
|
||||
},
|
||||
"is-decimal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
|
||||
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="
|
||||
},
|
||||
"is-descriptor": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
|
||||
@ -3444,6 +3542,11 @@
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-hexadecimal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
|
||||
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
|
||||
},
|
||||
"is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
@ -3477,6 +3580,11 @@
|
||||
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz",
|
||||
"integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg=="
|
||||
},
|
||||
"is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
@ -3548,12 +3656,22 @@
|
||||
"integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=",
|
||||
"dev": true
|
||||
},
|
||||
"is-whitespace-character": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
|
||||
"integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w=="
|
||||
},
|
||||
"is-windows": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
|
||||
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
|
||||
"dev": true
|
||||
},
|
||||
"is-word-character": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz",
|
||||
"integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
@ -3702,8 +3820,7 @@
|
||||
"lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
|
||||
"dev": true
|
||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
||||
},
|
||||
"lodash.chunk": {
|
||||
"version": "4.2.0",
|
||||
@ -3828,6 +3945,11 @@
|
||||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"markdown-escapes": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
|
||||
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
|
||||
},
|
||||
"matchdep": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
|
||||
@ -3999,6 +4121,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mdast-add-list-metadata": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz",
|
||||
"integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==",
|
||||
"requires": {
|
||||
"unist-util-visit-parents": "1.1.2"
|
||||
}
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
|
||||
@ -4411,6 +4541,19 @@
|
||||
"aggregate-error": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"parse-entities": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz",
|
||||
"integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==",
|
||||
"requires": {
|
||||
"character-entities": "^1.0.0",
|
||||
"character-entities-legacy": "^1.0.0",
|
||||
"character-reference-invalid": "^1.0.0",
|
||||
"is-alphanumerical": "^1.0.0",
|
||||
"is-decimal": "^1.0.0",
|
||||
"is-hexadecimal": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"parse-filepath": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz",
|
||||
@ -4979,6 +5122,11 @@
|
||||
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
|
||||
"dev": true
|
||||
},
|
||||
"ramda": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
|
||||
"integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ=="
|
||||
},
|
||||
"react": {
|
||||
"version": "16.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.10.1.tgz",
|
||||
@ -4989,6 +5137,11 @@
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-codemirror2": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-6.0.1.tgz",
|
||||
"integrity": "sha512-rutEKVgvFhWcy/GeVA1hFbqrO89qLqgqdhUr7YhYgIzdyICdlRQv+ztuNvOFQMXrO0fLt0VkaYOdMdYdQgsSUA=="
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "16.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.1.tgz",
|
||||
@ -5005,6 +5158,21 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.1.tgz",
|
||||
"integrity": "sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw=="
|
||||
},
|
||||
"react-markdown": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz",
|
||||
"integrity": "sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==",
|
||||
"requires": {
|
||||
"html-to-react": "^1.3.4",
|
||||
"mdast-add-list-metadata": "1.0.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.6",
|
||||
"remark-parse": "^5.0.0",
|
||||
"unified": "^6.1.5",
|
||||
"unist-util-visit": "^1.3.0",
|
||||
"xtend": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"react-router": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz",
|
||||
@ -5287,6 +5455,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"remark-disable-tokenizers": {
|
||||
"version": "1.0.24",
|
||||
"resolved": "https://registry.npmjs.org/remark-disable-tokenizers/-/remark-disable-tokenizers-1.0.24.tgz",
|
||||
"integrity": "sha512-HsAmBY5cNliHYAzba4zuskZzkDdp6sG+tRelDb4AoPo2YHNGHnxYsatShzTIsnRNLgCbsxycW5Ge6KigHn701A==",
|
||||
"requires": {
|
||||
"clone": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"remark-parse": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz",
|
||||
"integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==",
|
||||
"requires": {
|
||||
"collapse-white-space": "^1.0.2",
|
||||
"is-alphabetical": "^1.0.0",
|
||||
"is-decimal": "^1.0.0",
|
||||
"is-whitespace-character": "^1.0.0",
|
||||
"is-word-character": "^1.0.0",
|
||||
"markdown-escapes": "^1.0.0",
|
||||
"parse-entities": "^1.1.0",
|
||||
"repeat-string": "^1.5.4",
|
||||
"state-toggle": "^1.0.0",
|
||||
"trim": "0.0.1",
|
||||
"trim-trailing-lines": "^1.0.0",
|
||||
"unherit": "^1.0.4",
|
||||
"unist-util-remove-position": "^1.0.0",
|
||||
"vfile-location": "^2.0.0",
|
||||
"xtend": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"remove-bom-buffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz",
|
||||
@ -5328,14 +5526,12 @@
|
||||
"repeat-string": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
||||
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
|
||||
"dev": true
|
||||
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
|
||||
},
|
||||
"replace-ext": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
|
||||
"integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=",
|
||||
"dev": true
|
||||
"integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs="
|
||||
},
|
||||
"replace-homedir": {
|
||||
"version": "1.0.0",
|
||||
@ -5459,6 +5655,15 @@
|
||||
"rollup-pluginutils": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-json/-/rollup-plugin-json-4.0.0.tgz",
|
||||
"integrity": "sha512-hgb8N7Cgfw5SZAkb3jf0QXii6QX/FOkiIq2M7BAQIEydjHvTyxXHQiIzZaTFgx1GK0cRCHOCBHIyEkkLdWKxow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"rollup-pluginutils": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-node-globals": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-node-globals/-/rollup-plugin-node-globals-1.4.0.tgz",
|
||||
@ -5859,6 +6064,11 @@
|
||||
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
|
||||
"dev": true
|
||||
},
|
||||
"state-toggle": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz",
|
||||
"integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ=="
|
||||
},
|
||||
"static-extend": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
|
||||
@ -6181,6 +6391,21 @@
|
||||
"resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-1.15.3.tgz",
|
||||
"integrity": "sha512-ThJH58GNFKhCw3gIoOtwf3tNwuYjbyEeiGdeq4mNMYWdJctnI896KUqn6PVt7jmNVepqa1bcKQtnMB1HtjsDMA=="
|
||||
},
|
||||
"trim": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
||||
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0="
|
||||
},
|
||||
"trim-trailing-lines": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz",
|
||||
"integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA=="
|
||||
},
|
||||
"trough": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
|
||||
"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA=="
|
||||
},
|
||||
"type": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||
@ -6222,6 +6447,28 @@
|
||||
"integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=",
|
||||
"dev": true
|
||||
},
|
||||
"unherit": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
|
||||
"integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.0",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"unified": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz",
|
||||
"integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==",
|
||||
"requires": {
|
||||
"bail": "^1.0.0",
|
||||
"extend": "^3.0.0",
|
||||
"is-plain-obj": "^1.1.0",
|
||||
"trough": "^1.0.0",
|
||||
"vfile": "^2.0.0",
|
||||
"x-is-string": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
@ -6264,6 +6511,47 @@
|
||||
"through2-filter": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"unist-util-is": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz",
|
||||
"integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A=="
|
||||
},
|
||||
"unist-util-remove-position": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz",
|
||||
"integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==",
|
||||
"requires": {
|
||||
"unist-util-visit": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"unist-util-stringify-position": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz",
|
||||
"integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ=="
|
||||
},
|
||||
"unist-util-visit": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz",
|
||||
"integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==",
|
||||
"requires": {
|
||||
"unist-util-visit-parents": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"unist-util-visit-parents": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz",
|
||||
"integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==",
|
||||
"requires": {
|
||||
"unist-util-is": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"unist-util-visit-parents": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz",
|
||||
"integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q=="
|
||||
},
|
||||
"unquote": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
|
||||
@ -6369,6 +6657,30 @@
|
||||
"integrity": "sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw==",
|
||||
"dev": true
|
||||
},
|
||||
"vfile": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz",
|
||||
"integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==",
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.4",
|
||||
"replace-ext": "1.0.0",
|
||||
"unist-util-stringify-position": "^1.0.0",
|
||||
"vfile-message": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"vfile-location": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz",
|
||||
"integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA=="
|
||||
},
|
||||
"vfile-message": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz",
|
||||
"integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==",
|
||||
"requires": {
|
||||
"unist-util-stringify-position": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"vinyl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz",
|
||||
@ -6476,6 +6788,11 @@
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"x-is-string": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz",
|
||||
"integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI="
|
||||
},
|
||||
"xml-lexer": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/xml-lexer/-/xml-lexer-0.2.2.tgz",
|
||||
@ -6496,8 +6813,7 @@
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"y18n": {
|
||||
"version": "3.2.1",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"gulp-rename": "^1.4.0",
|
||||
"rollup": "^1.6.0",
|
||||
"rollup-plugin-commonjs": "^9.3.4",
|
||||
"rollup-plugin-json": "^4.0.0",
|
||||
"rollup-plugin-node-globals": "^1.4.0",
|
||||
"rollup-plugin-node-resolve": "^4.0.0",
|
||||
"rollup-plugin-root-import": "^0.2.3",
|
||||
@ -27,13 +28,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"codemirror": "^5.51.2",
|
||||
"del": "^5.1.0",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.20.1",
|
||||
"mousetrap": "^1.6.3",
|
||||
"react": "^16.5.2",
|
||||
"react-codemirror2": "^6.0.0",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"remark-disable-tokenizers": "^1.0.24",
|
||||
"urbit-ob": "^5.0.0",
|
||||
"urbit-sigil-js": "^1.3.2"
|
||||
},
|
||||
|
@ -178,6 +178,14 @@ h2 {
|
||||
}
|
||||
|
||||
|
||||
.green3 {
|
||||
color: #7ea899;
|
||||
}
|
||||
|
||||
.unread-notice {
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
/* responsive */
|
||||
|
||||
@media all and (max-width: 34.375em) {
|
||||
@ -196,6 +204,9 @@ h2 {
|
||||
.embed-container {
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
.unread-notice {
|
||||
top: 96px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 34.375em) and (max-width: 46.875em) {
|
||||
@ -231,6 +242,94 @@ h2 {
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-left: 24px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--dark-gray: #555555;
|
||||
--gray: #7F7F7F;
|
||||
--medium-gray: #CCCCCC;
|
||||
--light-gray: rgba(0,0,0,0.08);
|
||||
}
|
||||
.react-codemirror2 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.CodeMirror * {
|
||||
font-family: 'Inter';
|
||||
}
|
||||
|
||||
.CodeMirror.cm-s-code.cm-s-tlon * {
|
||||
font-family: 'Source Code Pro';
|
||||
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background:#BAE3FE !important; color: black; }
|
||||
pre.CodeMirror-placeholder.CodeMirror-line-like { color: var(--gray); }
|
||||
|
||||
.cm-s-tlon span { font-family: "Inter"}
|
||||
.cm-s-tlon span.cm-meta { color: var(--gray); }
|
||||
.cm-s-tlon span.cm-number { color: var(--gray); }
|
||||
.cm-s-tlon span.cm-keyword { line-height: 1em; font-weight: bold; color: var(--gray); }
|
||||
.cm-s-tlon span.cm-atom { font-weight: bold; color: var(--gray); }
|
||||
.cm-s-tlon span.cm-def { color: black; }
|
||||
.cm-s-tlon span.cm-variable { color: black; }
|
||||
.cm-s-tlon span.cm-variable-2 { color: black; }
|
||||
.cm-s-tlon span.cm-variable-3, .cm-s-tlon span.cm-type { color: black; }
|
||||
.cm-s-tlon span.cm-property { color: black; }
|
||||
.cm-s-tlon span.cm-operator { color: black; }
|
||||
.cm-s-tlon span.cm-comment { font-family: 'Source Code Pro'; color: black; background-color: var(--light-gray); display: inline-block; border-radius: 2px;}
|
||||
.cm-s-tlon span.cm-string { color: var(--dark-gray); }
|
||||
.cm-s-tlon span.cm-string-2 { color: var(--gray); }
|
||||
.cm-s-tlon span.cm-qualifier { color: #555; }
|
||||
.cm-s-tlon span.cm-error { color: #FF0000; }
|
||||
.cm-s-tlon span.cm-attribute { color: var(--gray); }
|
||||
.cm-s-tlon span.cm-tag { color: var(--gray); }
|
||||
.cm-s-tlon span.cm-link { color: var(--dark-gray); text-decoration: none;}
|
||||
.cm-s-tlon .CodeMirror-activeline-background { background: var(--gray); }
|
||||
.cm-s-tlon .CodeMirror-cursor {
|
||||
border-left: 2px solid #3687FF;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-builtin { color: var(--gray); }
|
||||
.cm-s-tlon span.cm-bracket { color: var(--gray); }
|
||||
/* .cm-s-tlon { font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;} */
|
||||
|
||||
|
||||
.cm-s-tlon .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }
|
||||
|
||||
.CodeMirror-hints.tlon {
|
||||
/* font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; */
|
||||
color: #616569;
|
||||
background-color: #ebf3fd !important;
|
||||
}
|
||||
|
||||
.CodeMirror-hints.tlon .CodeMirror-hint-active {
|
||||
background-color: #a2b8c9 !important;
|
||||
color: #5c6065 !important;
|
||||
}
|
||||
|
||||
.title-input[placeholder]:empty:before {
|
||||
content: attr(placeholder);
|
||||
color: #7F7F7F;
|
||||
}
|
||||
|
||||
|
||||
/* dark */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@ -295,4 +394,85 @@ h2 {
|
||||
.hover-bg-gray1-d:hover {
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 1px solid white;
|
||||
}
|
||||
|
||||
.contrast-10-d {
|
||||
filter: contrast(0.1);
|
||||
}
|
||||
|
||||
.bg-none-d {
|
||||
background: none;
|
||||
}
|
||||
|
||||
|
||||
/* codemirror */
|
||||
.cm-s-tlon.CodeMirror {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-def {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-variable {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-variable-2 {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-variable-3,
|
||||
.cm-s-tlon span.cm-type {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-property {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-operator {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.cm-s-tlon span.cm-string {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-string-2 {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-attribute {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-tag {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-link {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
/* set rules w/ both color & bg-color last to preserve legibility */
|
||||
.CodeMirror-selected {
|
||||
background: var(--medium-gray) !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cm-s-tlon span.cm-comment {
|
||||
color: black;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
background-color: rgba(255,255,255, 0.3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* CodeMirror styling */
|
||||
|
@ -1,4 +1,6 @@
|
||||
@import '../node_modules/codemirror/lib/codemirror.css';
|
||||
@import '../node_modules/codemirror/theme/material.css';
|
||||
|
||||
@import "css/indigo-static.css";
|
||||
@import "css/fonts.css";
|
||||
@import "css/custom.css";
|
||||
|
||||
|
@ -120,7 +120,9 @@ class UrbitApi {
|
||||
}
|
||||
};
|
||||
|
||||
this.action("chat-hook", "json", data);
|
||||
this.action("chat-hook", "json", data).then(() => {
|
||||
this.chatRead(path);
|
||||
})
|
||||
data.message.envelope.author = data.message.envelope.author.substr(1);
|
||||
this.addPendingMessage(data.message);
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { Route, Link } from "react-router-dom";
|
||||
import { store } from "/store";
|
||||
|
||||
import { ResubscribeElement } from '/components/lib/resubscribe-element';
|
||||
import { BacklogElement } from '/components/lib/backlog-element';
|
||||
import { Message } from '/components/lib/message';
|
||||
import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
|
||||
import { ChatTabBar } from '/components/lib/chat-tabbar';
|
||||
import { ChatInput } from '/components/lib/chat-input';
|
||||
import { UnreadNotice } from '/components/lib/unread-notice';
|
||||
import { deSig } from '/lib/util';
|
||||
|
||||
function getNumPending(props) {
|
||||
@ -37,24 +40,26 @@ export class ChatScreen extends Component {
|
||||
this.scrollContainer = null;
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
|
||||
this.updateReadInterval = setInterval(
|
||||
this.updateReadNumber.bind(this),
|
||||
1000
|
||||
);
|
||||
this.unreadMarker = null;
|
||||
|
||||
moment.updateLocale('en', {
|
||||
calendar: {
|
||||
sameDay: '[Today]',
|
||||
nextDay: '[Tomorrow]',
|
||||
nextWeek: 'dddd',
|
||||
lastDay: '[Yesterday]',
|
||||
lastWeek: '[Last] dddd',
|
||||
sameElse: 'DD/MM/YYYY'
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.scrollToBottom();
|
||||
this.updateReadNumber();
|
||||
this.askForMessages();
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.updateReadInterval) {
|
||||
clearInterval(this.updateReadInterval);
|
||||
this.updateReadInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { props, state } = this;
|
||||
@ -69,17 +74,10 @@ export class ChatScreen extends Component {
|
||||
this.askForMessages();
|
||||
}
|
||||
|
||||
clearInterval(this.updateReadInterval);
|
||||
|
||||
this.setState(
|
||||
{ scrollLocked: false },
|
||||
() => {
|
||||
this.scrollToBottom();
|
||||
this.updateReadInterval = setInterval(
|
||||
this.updateReadNumber.bind(this),
|
||||
1000
|
||||
);
|
||||
this.updateReadNumber();
|
||||
}
|
||||
);
|
||||
} else if (props.chatInitialized &&
|
||||
@ -113,13 +111,6 @@ export class ChatScreen extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
updateReadNumber() {
|
||||
const { props, state } = this;
|
||||
if (props.read < props.length) {
|
||||
props.api.chat.read(props.station);
|
||||
}
|
||||
}
|
||||
|
||||
askForMessages() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -230,9 +221,23 @@ export class ChatScreen extends Component {
|
||||
} else {
|
||||
console.log("Your browser is not supported.");
|
||||
}
|
||||
if(!!this.unreadMarker) {
|
||||
if(
|
||||
!navigator.userAgent.includes('Firefox') &&
|
||||
e.target.scrollHeight - e.target.scrollTop - (e.target.clientHeight * 1.5) + this.unreadMarker.offsetTop > 50
|
||||
) {
|
||||
this.props.api.chat.read(this.props.station);
|
||||
} else if(navigator.userAgent.includes('Firefox') &&
|
||||
this.unreadMarker.offsetTop - e.target.scrollTop - (e.target.clientHeight / 2) > 0
|
||||
) {
|
||||
this.props.api.chat.read(this.props.station);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
chatWindow() {
|
||||
chatWindow(unread) {
|
||||
|
||||
// Replace with just the "not Firefox" implementation
|
||||
// when Firefox #1042151 is patched.
|
||||
@ -255,6 +260,7 @@ export class ChatScreen extends Component {
|
||||
return (value.pending = true);
|
||||
});
|
||||
|
||||
|
||||
messages = pendingMessages.concat(messages);
|
||||
|
||||
let messageElements = messages.map((msg, i) => {
|
||||
@ -268,7 +274,12 @@ export class ChatScreen extends Component {
|
||||
_.get(messages[i - 1], aut) !==
|
||||
_.get(msg, aut, msg.author);
|
||||
|
||||
return (
|
||||
let when = ['when'];
|
||||
let dayBreak =
|
||||
moment(_.get(messages[i+1], when)).format('YYYY.MM.DD') !==
|
||||
moment(_.get(messages[i], when)).format('YYYY.MM.DD');
|
||||
|
||||
const messageElem = (
|
||||
<Message
|
||||
key={msg.uid}
|
||||
msg={msg}
|
||||
@ -280,6 +291,39 @@ export class ChatScreen extends Component {
|
||||
group={props.association}
|
||||
/>
|
||||
);
|
||||
if(unread > 0 && i === unread) {
|
||||
return (
|
||||
<>
|
||||
{messageElem}
|
||||
<div key={'unreads'+ msg.uid} ref={ref => (this.unreadMarker = ref)} className="mv2 green2 flex items-center f9">
|
||||
<hr className="ma0 w2 b--green2 bt-0" />
|
||||
<p className="mh4">
|
||||
New messages below
|
||||
</p>
|
||||
<hr className="ma0 flex-grow-1 b--green2 bt-0" />
|
||||
{ dayBreak && (
|
||||
<p className="gray2 mh4">
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
)}
|
||||
<hr style={{ width: 'calc(50% - 48px)' }} className="b--green2 ma0 bt-0"/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else if(dayBreak) {
|
||||
return (
|
||||
<>
|
||||
{messageElem}
|
||||
<div key={'daybreak' + msg.uid} className="pv3 gray2 b--gray2 flex items-center justify-center f9 ">
|
||||
<p>
|
||||
{moment(_.get(messages[i], when)).calendar()}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return messageElem;
|
||||
}
|
||||
});
|
||||
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
@ -293,6 +337,10 @@ export class ChatScreen extends Component {
|
||||
ref={el => {
|
||||
this.scrollElement = el;
|
||||
}}></div>
|
||||
{(props.chatInitialized &&
|
||||
!(props.station in props.inbox)) && (
|
||||
<BacklogElement />
|
||||
)}
|
||||
{(
|
||||
props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
@ -319,6 +367,10 @@ export class ChatScreen extends Component {
|
||||
ref={el => {
|
||||
this.scrollElement = el;
|
||||
}}></div>
|
||||
{(props.chatInitialized &&
|
||||
!(props.station in props.inbox)) && (
|
||||
<BacklogElement />
|
||||
)}
|
||||
{(
|
||||
props.chatSynced &&
|
||||
!(props.station in props.chatSynced) &&
|
||||
@ -358,10 +410,14 @@ export class ChatScreen extends Component {
|
||||
: props.station.substr(1);
|
||||
}
|
||||
|
||||
const unread = props.length - props.read;
|
||||
|
||||
const unreadMsg = unread > 0 && messages[unread - 1];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={props.station}
|
||||
className="h-100 w-100 overflow-hidden flex flex-column">
|
||||
className="h-100 w-100 overflow-hidden flex flex-column relative">
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
|
||||
style={{ height: "1rem" }}>
|
||||
@ -393,7 +449,14 @@ export class ChatScreen extends Component {
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
{this.chatWindow()}
|
||||
{ !!unreadMsg && (
|
||||
<UnreadNotice
|
||||
unread={unread}
|
||||
unreadMsg={unreadMsg}
|
||||
onRead={() => props.api.chat.read(props.station)}
|
||||
/>
|
||||
) }
|
||||
{this.chatWindow(unread)}
|
||||
<ChatInput
|
||||
api={props.api}
|
||||
numMsgs={lastMsgNum}
|
||||
|
27
pkg/interface/chat/src/js/components/lib/backlog-element.js
Normal file
27
pkg/interface/chat/src/js/components/lib/backlog-element.js
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
|
||||
export class BacklogElement extends Component {
|
||||
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
|
||||
return (
|
||||
<div className="center mw6">
|
||||
<div className="db pa3 ma3 ba b--gray4 bg-gray5 b--gray2-d bg-gray1-d white-d flex items-center">
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~chat/img/Spinner.png"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
<p className="lh-copy db ml3">
|
||||
Past messages are being restored
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
@ -2,12 +2,37 @@ import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import cn from 'classnames';
|
||||
import { UnControlled as CodeEditor } from 'react-codemirror2'
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
|
||||
import { Sigil } from '/components/lib/icons/sigil';
|
||||
import { ShipSearch } from '/components/lib/ship-search';
|
||||
|
||||
import { uuid, uxToHex, hexToRgba } from '/lib/util';
|
||||
|
||||
const MARKDOWN_CONFIG = {
|
||||
name: "markdown",
|
||||
tokenTypeOverrides: {
|
||||
header: "presentation",
|
||||
quote: "presentation",
|
||||
list1: "presentation",
|
||||
list2: "presentation",
|
||||
list3: "presentation",
|
||||
hr: "presentation",
|
||||
image: "presentation",
|
||||
imageAltText: "presentation",
|
||||
imageMarker: "presentation",
|
||||
formatting: "presentation",
|
||||
linkInline: "presentation",
|
||||
linkEmail: "presentation",
|
||||
linkText: "presentation",
|
||||
linkHref: "presentation",
|
||||
}
|
||||
}
|
||||
|
||||
// line height
|
||||
const INPUT_LINE_HEIGHT = 28;
|
||||
@ -36,8 +61,7 @@ export class ChatInput extends Component {
|
||||
|
||||
this.state = {
|
||||
message: '',
|
||||
textareaHeight: INPUT_LINE_HEIGHT + INPUT_TOP_PADDING + 1,
|
||||
patpSearch: ''
|
||||
patpSearch: null
|
||||
};
|
||||
|
||||
this.textareaRef = React.createRef();
|
||||
@ -45,13 +69,15 @@ export class ChatInput extends Component {
|
||||
this.messageSubmit = this.messageSubmit.bind(this);
|
||||
this.messageChange = this.messageChange.bind(this);
|
||||
|
||||
|
||||
this.patpAutocomplete = this.patpAutocomplete.bind(this);
|
||||
this.completePatp = this.completePatp.bind(this);
|
||||
this.clearSearch = this.clearSearch.bind(this);
|
||||
|
||||
|
||||
// Call once per frame @ 60hz
|
||||
this.textareaInput = _.debounce(this.textareaInput.bind(this), 16);
|
||||
this.toggleCode = this.toggleCode.bind(this);
|
||||
|
||||
this.editor = null;
|
||||
|
||||
|
||||
// perf testing:
|
||||
/*let closure = () => {
|
||||
@ -95,8 +121,18 @@ export class ChatInput extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.bindShortcuts();
|
||||
|
||||
nextAutocompleteSuggestion(backward = false) {
|
||||
const { patpSuggestions } = this.state;
|
||||
let idx = patpSuggestions.findIndex(s => s === this.state.selectedSuggestion);
|
||||
|
||||
idx = backward ? idx - 1 : idx + 1;
|
||||
idx = idx % patpSuggestions.length;
|
||||
if(idx < 0) {
|
||||
idx = patpSuggestions.length - 1;
|
||||
}
|
||||
|
||||
this.setState({ selectedSuggestion: patpSuggestions[idx] });
|
||||
}
|
||||
|
||||
|
||||
@ -104,103 +140,48 @@ export class ChatInput extends Component {
|
||||
const match = /~([a-zA-Z\-]*)$/.exec(message);
|
||||
|
||||
if (!match ) {
|
||||
this.bindShortcuts();
|
||||
this.setState({ patpSearch: '' })
|
||||
this.setState({ patpSearch: null })
|
||||
return;
|
||||
}
|
||||
this.unbindShortcuts();
|
||||
this.setState({ patpSearch: match[1].toLowerCase() });
|
||||
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
this.setState({
|
||||
patpSearch: ''
|
||||
patpSearch: null
|
||||
})
|
||||
}
|
||||
|
||||
completePatp(suggestion) {
|
||||
this.bindShortcuts();
|
||||
this.setState({
|
||||
message: this.state.message.replace(
|
||||
/[a-zA-Z\-]*$/,
|
||||
suggestion
|
||||
),
|
||||
patpSearch: ''
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
bindShortcuts() {
|
||||
if(!this.mousetrap) {
|
||||
this.mousetrap = new Mousetrap(this.textareaRef.current);
|
||||
}
|
||||
this.mousetrap.bind('enter', e => {
|
||||
e.preventDefault();
|
||||
|
||||
if(this.state.patpSearch.length === 0) {
|
||||
this.messageSubmit();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.mousetrap.bind('tab', e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if(this.state.patpSearch.length === 0) {
|
||||
this.patpAutocomplete(this.state.message, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unbindShortcuts() {
|
||||
if(!this.mousetrap) {
|
||||
if(!this.editor) {
|
||||
return;
|
||||
}
|
||||
this.mousetrap.unbind('enter')
|
||||
this.mousetrap.unbind('tab')
|
||||
const newMessage = this.editor.getValue().replace(
|
||||
/[a-zA-Z\-]*$/,
|
||||
suggestion
|
||||
);
|
||||
this.editor.setValue(newMessage);
|
||||
const lastRow = this.editor.lastLine();
|
||||
const lastCol = this.editor.getLineHandle(lastRow).text.length;
|
||||
this.editor.setCursor(lastRow, lastCol);
|
||||
this.setState({
|
||||
patpSearch: null
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
messageChange(event) {
|
||||
const message = event.target.value;
|
||||
this.setState({
|
||||
message
|
||||
});
|
||||
messageChange(editor, data, value) {
|
||||
|
||||
const { patpSearch } = this.state;
|
||||
if(patpSearch.length !== 0) {
|
||||
this.patpAutocomplete(message, false);
|
||||
if(patpSearch !== null) {
|
||||
this.patpAutocomplete(value, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
textareaInput() {
|
||||
const maxHeight = INPUT_LINE_HEIGHT * 8 + INPUT_TOP_PADDING;
|
||||
const newHeight = `${Math.min(maxHeight, this.textareaRef.current.scrollHeight)}px`;
|
||||
|
||||
this.setState({
|
||||
textareaHeight: newHeight
|
||||
});
|
||||
}
|
||||
|
||||
getLetterType(letter) {
|
||||
if (letter[0] === '#') {
|
||||
letter = letter.slice(1);
|
||||
// remove insignificant leading whitespace.
|
||||
// aces might be relevant to style.
|
||||
while (letter[0] === '\n') {
|
||||
letter = letter.slice(1);
|
||||
}
|
||||
|
||||
return {
|
||||
code: {
|
||||
expression: letter,
|
||||
output: undefined
|
||||
}
|
||||
}
|
||||
} else if (letter[0] === '@') {
|
||||
letter = letter.slice(1);
|
||||
if (letter.startsWith('/me')) {
|
||||
letter = letter.slice(3);
|
||||
// remove insignificant leading whitespace.
|
||||
// aces might be relevant to style.
|
||||
while (letter[0] === '\n') {
|
||||
@ -233,14 +214,28 @@ export class ChatInput extends Component {
|
||||
}
|
||||
|
||||
messageSubmit() {
|
||||
if(!this.editor) {
|
||||
return;
|
||||
}
|
||||
const { props, state } = this;
|
||||
const editorMessage = this.editor.getValue();
|
||||
|
||||
if (state.message === '') {
|
||||
if (editorMessage === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if(state.code) {
|
||||
props.api.chat.message(props.station, `~${window.ship}`, Date.now(), {
|
||||
code: {
|
||||
expression: editorMessage,
|
||||
output: undefined
|
||||
}
|
||||
});
|
||||
this.editor.setValue('');
|
||||
return;
|
||||
}
|
||||
let message = [];
|
||||
state.message.split(" ").map((each) => {
|
||||
editorMessage.split(" ").map((each) => {
|
||||
if (this.isUrl(each)) {
|
||||
if (message.length > 0) {
|
||||
message = message.join(" ");
|
||||
@ -282,10 +277,28 @@ export class ChatInput extends Component {
|
||||
// perf:
|
||||
//setTimeout(this.closure, 2000);
|
||||
|
||||
this.setState({
|
||||
message: '',
|
||||
textareaHeight: INPUT_LINE_HEIGHT + INPUT_TOP_PADDING + 1
|
||||
});
|
||||
this.editor.setValue('');
|
||||
|
||||
}
|
||||
|
||||
toggleCode() {
|
||||
if(this.state.code) {
|
||||
this.setState({ code: false });
|
||||
this.editor.setOption('mode', MARKDOWN_CONFIG);
|
||||
this.editor.setOption('placeholder', this.props.placeholder);
|
||||
} else {
|
||||
this.setState({ code: true });
|
||||
this.editor.setOption('mode', null);
|
||||
this.editor.setOption('placeholder', "Code...");
|
||||
}
|
||||
const value = this.editor.getValue();
|
||||
|
||||
// Force redraw of placeholder
|
||||
if(value.length === 0) {
|
||||
this.editor.setValue(' ');
|
||||
this.editor.setValue('');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -304,22 +317,45 @@ export class ChatInput extends Component {
|
||||
.reverse()
|
||||
.value();
|
||||
|
||||
const codeTheme = state.code ? ' code' : '';
|
||||
|
||||
const completeActive = state.patpSearch !== null;
|
||||
|
||||
const options = {
|
||||
mode: MARKDOWN_CONFIG,
|
||||
theme: 'tlon' + codeTheme,
|
||||
lineNumbers: false,
|
||||
lineWrapping: true,
|
||||
scrollbarStyle: 'native',
|
||||
cursorHeight: 0.85,
|
||||
placeholder: state.code ? "Code..." : props.placeholder,
|
||||
extraKeys: {
|
||||
Tab: (cm) =>
|
||||
this.patpAutocomplete(cm.getValue(), true),
|
||||
'Enter': (cm) =>
|
||||
this.messageSubmit(),
|
||||
'Shift-3': (cm) =>
|
||||
this.toggleCode()
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="pa3 cf flex black white-d bt b--gray4 b--gray1-d bg-white bg-gray0-d relative"
|
||||
style={{ flexGrow: 1 }}>
|
||||
<ShipSearch
|
||||
popover
|
||||
onSelect={this.completePatp}
|
||||
onClear={this.clearSearch}
|
||||
contacts={props.contacts}
|
||||
candidates={candidates}
|
||||
searchTerm={this.state.patpSearch}
|
||||
inputRef={this.textareaRef.current}
|
||||
cm={this.editor}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="fl"
|
||||
style={{
|
||||
marginTop: 4,
|
||||
marginTop: 6,
|
||||
flexBasis: 24,
|
||||
height: 24
|
||||
}}>
|
||||
@ -330,22 +366,25 @@ export class ChatInput extends Component {
|
||||
classes={sigilClass}
|
||||
/>
|
||||
</div>
|
||||
<div className="fr h-100 flex bg-gray0-d" style={{ flexGrow: 1 }}>
|
||||
<textarea
|
||||
className={"pl3 bn bg-gray0-d white-d lh-copy"}
|
||||
style={{ flexGrow: 1, height: state.textareaHeight, paddingTop: INPUT_TOP_PADDING, resize: "none" }}
|
||||
autoCapitalize="none"
|
||||
autoFocus={(
|
||||
/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(
|
||||
navigator.userAgent
|
||||
)) ? false : true}
|
||||
ref={this.textareaRef}
|
||||
placeholder={props.placeholder}
|
||||
value={state.message}
|
||||
onChange={this.messageChange}
|
||||
onInput={this.textareaInput}
|
||||
<div
|
||||
className="fr h-100 flex bg-gray0-d lh-copy pl2 w-100 items-center"
|
||||
style={{ flexGrow: 1, maxHeight: '224px', width: 'calc(100% - 48px)' }}>
|
||||
<CodeEditor
|
||||
options={options}
|
||||
editorDidMount={editor => { this.editor = editor; }}
|
||||
onChange={(e, d, v) => this.messageChange(e, d, v)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ height: '24px', width: '24px', flexBasis: 24, marginTop: 6 }}>
|
||||
<img
|
||||
style={{ filter: state.code && 'invert(100%)', height: '100%', width: '100%' }}
|
||||
onClick={this.toggleCode}
|
||||
src="/~chat/img/CodeEval.png"
|
||||
className="contrast-10-d bg-white bg-none-d"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,7 +8,34 @@ import { uxToHex, cite, writeText } from '/lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import RemarkDisableTokenizers from 'remark-disable-tokenizers';
|
||||
|
||||
const DISABLED_BLOCK_TOKENS = [
|
||||
'indentedCode',
|
||||
'blockquote',
|
||||
'atxHeading',
|
||||
'thematicBreak',
|
||||
'list',
|
||||
'setextHeading',
|
||||
'html',
|
||||
'definition',
|
||||
'table',
|
||||
];
|
||||
|
||||
const DISABLED_INLINE_TOKENS = [
|
||||
'autoLink',
|
||||
'url',
|
||||
'email',
|
||||
'link',
|
||||
'reference'
|
||||
];
|
||||
|
||||
const MessageMarkdown = React.memo(
|
||||
props => (<ReactMarkdown
|
||||
{...props}
|
||||
plugins={[[RemarkDisableTokenizers, { block: DISABLED_BLOCK_TOKENS, inline: DISABLED_INLINE_TOKENS }]]}
|
||||
/>));
|
||||
|
||||
export class Message extends Component {
|
||||
constructor() {
|
||||
@ -128,10 +155,11 @@ export class Message extends Component {
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
let text = letter.text.split ('\n').map ((item, i) => <p className='f7 lh-copy v-top' key={i}>{item}</p>);
|
||||
return (
|
||||
<section>
|
||||
{text}
|
||||
<MessageMarkdown
|
||||
source={letter.text}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -167,7 +195,7 @@ export class Message extends Component {
|
||||
<div
|
||||
ref={this.containerRef}
|
||||
className={
|
||||
"w-100 f8 pl3 pt4 pr3 cf flex lh-copy " + " " + pending
|
||||
"w-100 f7 pl3 pt4 pr3 cf flex lh-copy " + " " + pending
|
||||
}
|
||||
style={{
|
||||
minHeight: "min-content"
|
||||
@ -214,7 +242,7 @@ export class Message extends Component {
|
||||
minHeight: "min-content"
|
||||
}}>
|
||||
<p className="child pt2 pl2 pr1 mono f9 gray2 dib">{timestamp}</p>
|
||||
<div className="fr f7 clamp-message white-d pr3" style={{ flexGrow: 1 }}>
|
||||
<div className="fr f7 clamp-message white-d pr3 lh-copy" style={{ flexGrow: 1 }}>
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,18 +56,52 @@ export class ShipSearch extends Component {
|
||||
suggestions: [],
|
||||
bound: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.bindShortcuts();
|
||||
if (this.props.suggestEmpty) {
|
||||
this.updateSuggestions();
|
||||
this.keymap = {
|
||||
Tab: (cm) =>
|
||||
this.nextAutocompleteSuggestion(),
|
||||
'Shift-Tab': (cm) =>
|
||||
this.nextAutocompleteSuggestion(true),
|
||||
'Up': (cm) =>
|
||||
this.nextAutocompleteSuggestion(true),
|
||||
'Escape': (cm) =>
|
||||
this.props.onClear(),
|
||||
'Down': (cm) =>
|
||||
this.nextAutocompleteSuggestion(),
|
||||
'Enter': (cm) => {
|
||||
if(this.props.searchTerm !== null) {
|
||||
this.props.onSelect(this.state.selected);
|
||||
}
|
||||
},
|
||||
'Shift-3': (cm) =>
|
||||
this.toggleCode()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(this.props.searchTerm !== null) {
|
||||
this.updateSuggestions(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props } = this;
|
||||
const { props, state } = this;
|
||||
|
||||
if(!state.bound && props.inputRef) {
|
||||
this.bindShortcuts();
|
||||
}
|
||||
|
||||
if(props.searchTerm === null) {
|
||||
if(state.suggestions.length > 0) {
|
||||
this.setState({ suggestions: [] });
|
||||
}
|
||||
this.unbindShortcuts();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
props.searchTerm === null &&
|
||||
props.searchTerm !== prevProps.searchTerm &&
|
||||
props.searchTerm.startsWith(prevProps.searchTerm)
|
||||
) {
|
||||
@ -76,18 +110,10 @@ export class ShipSearch extends Component {
|
||||
this.updateSuggestions(true);
|
||||
}
|
||||
|
||||
if (prevProps.inputRef !== props.inputRef) {
|
||||
this.bindShortcuts();
|
||||
}
|
||||
}
|
||||
|
||||
updateSuggestions(isStale = false) {
|
||||
const needle = this.props.searchTerm;
|
||||
if (needle.length === 0 && !this.props.suggestEmpty) {
|
||||
this.unbindShortcuts();
|
||||
this.setState({ suggestions: [] });
|
||||
return;
|
||||
}
|
||||
const matchString = hay => {
|
||||
hay = hay.toLowerCase();
|
||||
|
||||
@ -127,10 +153,27 @@ export class ShipSearch extends Component {
|
||||
this.setState({ suggestions, selected: suggestions[0] });
|
||||
}
|
||||
|
||||
bindShortcuts() {
|
||||
if (!this.props.inputRef || this.state.bound) {
|
||||
bindCmShortcuts() {
|
||||
if(!this.props.cm) {
|
||||
return;
|
||||
}
|
||||
this.props.cm.addKeyMap(this.keymap);
|
||||
}
|
||||
|
||||
unbindCmShortcuts() {
|
||||
if(!this.props.cm) {
|
||||
return;
|
||||
}
|
||||
this.props.cm.removeKeyMap(this.keymap);
|
||||
}
|
||||
|
||||
bindShortcuts() {
|
||||
if (this.state.bound) {
|
||||
return;
|
||||
}
|
||||
if (!this.props.inputRef) {
|
||||
return this.bindCmShortcuts();
|
||||
}
|
||||
this.setState({ bound: true });
|
||||
if (!this.mousetrap) {
|
||||
this.mousetrap = new Mousetrap(this.props.inputRef);
|
||||
@ -164,14 +207,19 @@ export class ShipSearch extends Component {
|
||||
this.mousetrap.bind("esc", e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onDismiss();
|
||||
this.props.onClear();
|
||||
});
|
||||
}
|
||||
|
||||
unbindShortcuts() {
|
||||
if(!this.props.inputRef) {
|
||||
this.unbindCmShortcuts()
|
||||
}
|
||||
|
||||
if (!this.state.bound) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ bound: false });
|
||||
this.mousetrap.unbind("enter");
|
||||
this.mousetrap.unbind("tab");
|
||||
@ -240,28 +288,28 @@ export class ShipSearchInput extends Component {
|
||||
searchTerm: ""
|
||||
};
|
||||
|
||||
this.inputRef = React.createRef();
|
||||
this.popoverRef = React.createRef();
|
||||
this.inputRef = null;
|
||||
this.popoverRef = null;
|
||||
|
||||
this.search = this.search.bind(this);
|
||||
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.setInputRef = this.setInputRef.bind(this);
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
const { popoverRef } = this;
|
||||
// Do nothing if clicking ref's element or descendent elements
|
||||
if (!popoverRef.current || popoverRef.current.contains(event.target)) {
|
||||
if (!popoverRef || popoverRef.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onDismiss();
|
||||
this.props.onClear();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("mousedown", this.onClick);
|
||||
document.addEventListener("touchstart", this.onClick);
|
||||
this.inputRef.current.focus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -269,6 +317,15 @@ export class ShipSearchInput extends Component {
|
||||
document.removeEventListener("touchstart", this.onClick);
|
||||
}
|
||||
|
||||
setInputRef(ref) {
|
||||
this.inputRef = ref;
|
||||
if(ref) {
|
||||
ref.focus();
|
||||
}
|
||||
// update this.inputRef prop
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
search(e) {
|
||||
const searchTerm = e.target.value;
|
||||
this.setState({ searchTerm });
|
||||
@ -279,7 +336,7 @@ export class ShipSearchInput extends Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.popoverRef}
|
||||
ref={ref => (this.popoverRef = ref)}
|
||||
style={{ top: "150%", left: "-80px" }}
|
||||
className="b--gray2 b--solid ba absolute bg-white bg-gray0-d shadow-5"
|
||||
>
|
||||
@ -298,16 +355,15 @@ export class ShipSearchInput extends Component {
|
||||
placeholder="Search for a ship"
|
||||
value={state.searchTerm}
|
||||
onChange={this.search}
|
||||
ref={this.inputRef}
|
||||
ref={this.setInputRef}
|
||||
/>
|
||||
<ShipSearch
|
||||
contacts={props.contacts}
|
||||
candidates={props.candidates}
|
||||
searchTerm={deSig(state.searchTerm)}
|
||||
inputRef={this.inputRef.current}
|
||||
inputRef={this.inputRef}
|
||||
onSelect={props.onSelect}
|
||||
onDismiss={props.onDismiss}
|
||||
suggestEmpty
|
||||
onClear={props.onClear}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
40
pkg/interface/chat/src/js/components/lib/unread-notice.js
Normal file
40
pkg/interface/chat/src/js/components/lib/unread-notice.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { Component } from "react";
|
||||
import classnames from "classnames";
|
||||
import moment from "moment";
|
||||
|
||||
export class UnreadNotice extends Component {
|
||||
render() {
|
||||
let { unread, unreadMsg, onRead } = this.props;
|
||||
|
||||
let when = moment.unix(unreadMsg.when / 10000);
|
||||
|
||||
let datestamp = moment.unix(unreadMsg.when / 1000).format("YYYY.M.D");
|
||||
let timestamp = moment.unix(unreadMsg.when / 1000).format("HH:mm");
|
||||
|
||||
if (datestamp === moment().format("YYYY.M.D")) {
|
||||
datestamp = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ left: "0px" }}
|
||||
className="pa4 w-100 absolute z-1 unread-notice"
|
||||
>
|
||||
<div className="ba b--green2 green2 bg-white bg-gray0-d flex items-center pa2 f9 justify-between br1">
|
||||
<p className="lh-copy db">
|
||||
{unread} new messages since{" "}
|
||||
{datestamp && (
|
||||
<>
|
||||
<span className="green3">~{datestamp}</span> at{" "}
|
||||
</>
|
||||
)}
|
||||
<span className="green3">{timestamp}</span>
|
||||
</p>
|
||||
<div onClick={onRead} className="ml4 inter b--green2 pointer tr lh-copy">
|
||||
Mark as Read
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -142,7 +142,7 @@ export class Sidebar extends Component {
|
||||
contacts={{}}
|
||||
candidates={candidates}
|
||||
onSelect={this.goDm.bind(this)}
|
||||
onDismiss={this.onClickDm.bind(this)}
|
||||
onClear={this.onClickDm.bind(this)}
|
||||
|
||||
/>
|
||||
)}
|
||||
|
@ -20,8 +20,11 @@ export class ContactSidebar extends Component {
|
||||
let responsiveClasses =
|
||||
props.activeDrawer === "contacts" ? "db" : "dn db-ns";
|
||||
|
||||
let me = (window.ship in props.defaultContacts) ?
|
||||
props.defaultContacts[window.ship] : { color: '0x0', nickname: null};
|
||||
let me = (window.ship in props.contacts)
|
||||
? props.contacts[window.ship]
|
||||
: (window.ship in props.defaultContacts)
|
||||
? props.defaultContacts[window.ship]
|
||||
: { color: '0x0', nickname: null };
|
||||
|
||||
let shareSheet =
|
||||
!(window.ship in props.contacts) ?
|
||||
@ -32,11 +35,23 @@ export class ContactSidebar extends Component {
|
||||
path={props.path}
|
||||
selected={props.path + "/" + window.ship === props.selectedContact}
|
||||
/>
|
||||
) : (<div></div>);
|
||||
) : (
|
||||
<>
|
||||
<h2 className="f9 pt4 pr4 pb2 pl4 gray2 c-default">You</h2>
|
||||
<ContactItem
|
||||
ship={window.ship}
|
||||
nickname={me.nickname}
|
||||
color={me.color}
|
||||
path={props.path}
|
||||
selected={props.path + "/" + window.ship === props.selectedContact}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
group.delete(window.ship);
|
||||
|
||||
let contactItems =
|
||||
Object.keys(props.contacts)
|
||||
.filter(c => c !== window.ship)
|
||||
.map((contact) => {
|
||||
group.delete(contact);
|
||||
let path = props.path + "/" + contact;
|
||||
|
26
pkg/interface/publish/src/js/components/lib/comment-input.js
Normal file
26
pkg/interface/publish/src/js/components/lib/comment-input.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
|
||||
export const CommentInput = React.forwardRef((props, ref) => (
|
||||
<textarea
|
||||
{...props}
|
||||
ref={ref}
|
||||
style={{ resize: "vertical" }}
|
||||
id="comment"
|
||||
name="comment"
|
||||
placeholder="Leave a comment here"
|
||||
className={
|
||||
"f9 db border-box w-100 ba b--gray3 pt3 ph3 br1 " +
|
||||
"b--gray2-d mb2 focus-b--black focus-b--white-d white-d bg-gray0-d"
|
||||
}
|
||||
aria-describedby="comment-desc"
|
||||
style={{ height: "4rem" }}
|
||||
onKeyDown={e => {
|
||||
if (
|
||||
(e.getModifierState("Control") || event.metaKey) &&
|
||||
e.key === "Enter"
|
||||
) {
|
||||
props.onSubmit();
|
||||
}
|
||||
}}
|
||||
></textarea>
|
||||
));
|
@ -1,11 +1,20 @@
|
||||
import React, { Component } from 'react';
|
||||
import moment from 'moment';
|
||||
import { Sigil } from './icons/sigil';
|
||||
import { CommentInput } from './comment-input';
|
||||
import { uxToHex, cite } from '../../lib/util';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
export class CommentItem extends Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
commentBody: ''
|
||||
};
|
||||
|
||||
this.commentChange = this.commentChange.bind(this);
|
||||
this.commentEdit = this.commentEdit.bind(this);
|
||||
moment.updateLocale('en', {
|
||||
relativeTime: {
|
||||
past: function(input) {
|
||||
@ -28,6 +37,29 @@ export class CommentItem extends Component {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
commentEdit() {
|
||||
let commentPath = Object.keys(this.props.comment)[0];
|
||||
let commentBody = this.props.comment[commentPath].content;
|
||||
this.setState({ commentBody });
|
||||
this.props.onEdit();
|
||||
}
|
||||
|
||||
focusTextArea(text) {
|
||||
text && text.focus();
|
||||
}
|
||||
|
||||
commentChange(e) {
|
||||
this.setState({
|
||||
commentBody: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
onUpdate() {
|
||||
this.props.onUpdate(this.state.commentBody);
|
||||
}
|
||||
|
||||
render() {
|
||||
let pending = !!this.props.pending ? "o-60" : "";
|
||||
let commentData = this.props.comment[Object.keys(this.props.comment)[0]];
|
||||
@ -55,8 +87,13 @@ export class CommentItem extends Component {
|
||||
name = cite(commentData.author);
|
||||
}
|
||||
|
||||
const { editing } = this.props;
|
||||
|
||||
const disabled = this.props.pending
|
||||
|| window.ship !== commentData.author.slice(1);
|
||||
|
||||
return (
|
||||
<div className={pending}>
|
||||
<div className={"mb8 " + pending}>
|
||||
<div className="flex mv3 bg-white bg-gray0-d">
|
||||
<Sigil
|
||||
ship={commentData.author}
|
||||
@ -70,10 +107,39 @@ export class CommentItem extends Component {
|
||||
{name}
|
||||
</div>
|
||||
<div className="f9 gray3 pt1">{date}</div>
|
||||
{ !editing && !disabled && (
|
||||
<>
|
||||
<div onClick={this.commentEdit.bind(this)} className="green2 pointer ml2 f9 pt1">
|
||||
Edit
|
||||
</div>
|
||||
<div onClick={this.props.onDelete} className="red2 pointer ml2 f9 pt1">
|
||||
Delete
|
||||
</div>
|
||||
</>
|
||||
) }
|
||||
</div>
|
||||
<div className="f8 lh-solid mb8 mb2">
|
||||
{content}
|
||||
<div className="f8 lh-solid mb2">
|
||||
{ !editing && content }
|
||||
{ editing && (
|
||||
<CommentInput style={{resize:'vertical'}}
|
||||
ref={(el) => {this.focusTextArea(el)}}
|
||||
onChange={this.commentChange}
|
||||
value={this.state.commentBody}
|
||||
onSubmit={this.onUpdate.bind(this)}>
|
||||
</CommentInput>
|
||||
)}
|
||||
</div>
|
||||
{ editing && (
|
||||
<div className="flex">
|
||||
<div onClick={this.onUpdate.bind(this)} className="br1 green2 pointer f9 pt1 b--green2 ba pa2 dib">
|
||||
Submit
|
||||
</div>
|
||||
<div onClick={this.props.onEditCancel} className="br1 black white-d pointer f9 b--gray2 ba pa2 dib ml2">
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react'
|
||||
import { CommentItem } from './comment-item';
|
||||
import { CommentInput } from './comment-input';
|
||||
import { dateToDa } from '/lib/util';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
@ -8,11 +9,13 @@ export class Comments extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
commentBody: '',
|
||||
disabled: false,
|
||||
pending: new Set()
|
||||
pending: new Set(),
|
||||
awaiting: null,
|
||||
editing: null,
|
||||
}
|
||||
this.commentSubmit = this.commentSubmit.bind(this);
|
||||
this.commentChange = this.commentChange.bind(this);
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@ -50,10 +53,10 @@ export class Comments extends Component {
|
||||
this.setState({pending: pendingState});
|
||||
|
||||
this.textArea.value = '';
|
||||
this.setState({commentBody: "", disabled: true});
|
||||
this.setState({commentBody: "", awaiting: 'new'});
|
||||
let submit = window.api.action("publish", "publish-action", comment);
|
||||
submit.then(() => {
|
||||
this.setState({ disabled: false });
|
||||
this.setState({ awaiting: null });
|
||||
})
|
||||
}
|
||||
|
||||
@ -63,11 +66,60 @@ export class Comments extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
commentEdit(idx) {
|
||||
this.setState({ editing: idx });
|
||||
}
|
||||
|
||||
commentEditCancel() {
|
||||
this.setState({ editing: null });
|
||||
}
|
||||
|
||||
|
||||
commentUpdate(idx, body) {
|
||||
|
||||
let path = Object.keys(this.props.comments[idx])[0];
|
||||
let comment = {
|
||||
"edit-comment": {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
body: body,
|
||||
comment: path
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({ awaiting: 'edit' })
|
||||
|
||||
window.api
|
||||
.action('publish', 'publish-action', comment)
|
||||
.then(() => { this.setState({ awaiting: null, editing: null }) })
|
||||
}
|
||||
|
||||
commentDelete(idx) {
|
||||
let path = Object.keys(this.props.comments[idx])[0];
|
||||
let comment = {
|
||||
"del-comment": {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
comment: path
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({ awaiting: { kind: 'del', what: idx }})
|
||||
window.api
|
||||
.action('publish', 'publish-action', comment)
|
||||
.then(() => { this.setState({ awaiting: null }) })
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { editing } = this.state;
|
||||
|
||||
let pendingArray = Array.from(this.state.pending).map((com, i) => {
|
||||
let da = dateToDa(new Date);
|
||||
let comment = {
|
||||
@ -93,43 +145,46 @@ export class Comments extends Component {
|
||||
comment={com}
|
||||
key={i}
|
||||
contacts={this.props.contacts}
|
||||
onUpdate={u => this.commentUpdate(i, u)}
|
||||
onDelete={() => this.commentDelete(i)}
|
||||
onEdit={() => this.commentEdit(i)}
|
||||
onEditCancel={this.commentEditCancel.bind(this)}
|
||||
editing={i === editing}
|
||||
disabled={!!this.state.awaiting || editing}
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
let disableComment = ((this.state.commentBody === '') || (this.state.disabled === true));
|
||||
let disableComment = ((this.state.commentBody === '') || (!!this.state.awaiting));
|
||||
let commentClass = (disableComment)
|
||||
? "bg-transparent f9 pa2 br1 ba b--gray2 gray2"
|
||||
: "bg-transparent f9 pa2 br1 ba b--gray2 black white-d pointer";
|
||||
|
||||
let spinnerText =
|
||||
this.state.awaiting === 'new'
|
||||
? 'Posting commment...'
|
||||
: this.state.awaiting === 'edit'
|
||||
? 'Updating comment...'
|
||||
: 'Deleting comment...';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mv8 relative">
|
||||
<div>
|
||||
<textarea style={{resize:'vertical'}}
|
||||
<CommentInput style={{resize:'vertical'}}
|
||||
ref={(el) => {this.textArea = el}}
|
||||
id="comment"
|
||||
name="comment"
|
||||
placeholder="Leave a comment here"
|
||||
className={"f9 db border-box w-100 ba b--gray3 pt3 ph3 br1 " +
|
||||
"b--gray2-d mb2 focus-b--black focus-b--white-d white-d bg-gray0-d"}
|
||||
aria-describedby="comment-desc"
|
||||
style={{height: "4rem"}}
|
||||
onChange={this.commentChange}
|
||||
onKeyDown={(e) => {
|
||||
if ((e.getModifierState("Control") || event.metaKey)
|
||||
&& e.key === "Enter") {
|
||||
this.commentSubmit();
|
||||
}
|
||||
}}>
|
||||
</textarea>
|
||||
value={this.state.commentBody}
|
||||
disabled={!!this.state.editing}
|
||||
onSubmit={this.commentSubmit}>
|
||||
</CommentInput>
|
||||
</div>
|
||||
<button disabled={disableComment}
|
||||
onClick={this.commentSubmit}
|
||||
className={commentClass}>
|
||||
Add comment
|
||||
</button>
|
||||
<Spinner text="Posting comment..." awaiting={this.state.disabled} classes="absolute bottom-0 right-0 pb2"/>
|
||||
<Spinner text={spinnerText} awaiting={this.state.awaiting} classes="absolute bottom-0 right-0 pb2"/>
|
||||
</div>
|
||||
{pendingArray}
|
||||
{commentArray}
|
||||
|
@ -201,7 +201,7 @@ export class Note extends Component {
|
||||
ref={el => {
|
||||
this.scrollElement = el;
|
||||
}}>
|
||||
<div className="h-100 flex flex-column items-center mt4 ph4 pb4">
|
||||
<div className="h-100 flex flex-column items-center pa4">
|
||||
<div className="w-100 flex justify-center pb6">
|
||||
<SidebarSwitcher
|
||||
popout={props.popout}
|
||||
|
Loading…
Reference in New Issue
Block a user