Merge branch 'release/next-userspace' into lf/hark-redux
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d39205183f34081949acf9fa4abb9017a8764ee36526009cd3a26a4f121faf01
|
||||
size 6278799
|
||||
oid sha256:b5d3225b222544eae6f78d18a4a2343ff4c746fbcb72914760b79598ff90813d
|
||||
size 6278785
|
||||
|
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 453 B |
Before Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 951 B |
Before Width: | Height: | Size: 1010 B |
Before Width: | Height: | Size: 679 B |
222
pkg/arvo/app/observe-hook.hoon
Normal file
@ -0,0 +1,222 @@
|
||||
:: observe-hook:
|
||||
::
|
||||
:: helper that observes an app at a particular path and forwards all facts
|
||||
:: to a particular thread. kills the subscription if the thread crashes
|
||||
::
|
||||
/- sur=observe-hook
|
||||
/+ default-agent, dbug
|
||||
::
|
||||
|%
|
||||
+$ card card:agent:gall
|
||||
+$ versioned-state
|
||||
$% state-0
|
||||
==
|
||||
::
|
||||
+$ serial @uv
|
||||
+$ state-0 [%0 observers=(map serial observer:sur)]
|
||||
++ got-by-val
|
||||
|= [a=(map serial observer:sur) b=observer:sur]
|
||||
^- serial
|
||||
%- need
|
||||
%+ roll ~(tap by a)
|
||||
|= [[key=serial val=observer:sur] output=(unit serial)]
|
||||
?:(=(val b) `key output)
|
||||
--
|
||||
::
|
||||
%- agent:dbug
|
||||
=| state-0
|
||||
=* state -
|
||||
::
|
||||
^- agent:gall
|
||||
|_ =bowl:gall
|
||||
+* this .
|
||||
def ~(. (default-agent this %|) bowl)
|
||||
::
|
||||
++ on-init
|
||||
|^ ^- (quip card _this)
|
||||
:_ this
|
||||
:_ ~
|
||||
(act /inv-gra [%watch %invite-store /invitatory/graph %invite-accepted-graph])
|
||||
::
|
||||
++ act
|
||||
|= [=wire =action:sur]
|
||||
^- card
|
||||
:* %pass
|
||||
wire
|
||||
%agent
|
||||
[our.bowl %observe-hook]
|
||||
%poke
|
||||
%observe-action
|
||||
!> ^- action:sur
|
||||
action
|
||||
==
|
||||
--
|
||||
::
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
^- (quip card _this)
|
||||
`this(state !<(state-0 old-vase))
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card _this)
|
||||
?> (team:title our.bowl src.bowl)
|
||||
?. ?=(%observe-action mark)
|
||||
(on-poke:def mark vase)
|
||||
=/ =action:sur !<(action:sur vase)
|
||||
=* observer observer.action
|
||||
=/ vals (silt ~(val by observers))
|
||||
?- -.action
|
||||
%watch
|
||||
?: ?|(=(app.observer %spider) =(app.observer %observe-hook))
|
||||
~|('we avoid infinite loops' !!)
|
||||
?: (~(has in vals) observer)
|
||||
~|('duplicate observer' !!)
|
||||
:_ this(observers (~(put by observers) (sham eny.bowl) observer))
|
||||
:_ ~
|
||||
:* %pass
|
||||
/observer/(scot %uv (sham eny.bowl))
|
||||
%agent
|
||||
[our.bowl app.observer]
|
||||
%watch
|
||||
path.observer
|
||||
==
|
||||
::
|
||||
%ignore
|
||||
?. (~(has in vals) observer)
|
||||
~|('cannot remove nonexistent observer' !!)
|
||||
=/ key (got-by-val observers observer)
|
||||
:_ this(observers (~(del by observers) key))
|
||||
:_ ~
|
||||
:* %pass
|
||||
/observer/(scot %uv key)
|
||||
%agent
|
||||
[our.bowl app.observer]
|
||||
%leave
|
||||
~
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card _this)
|
||||
|^
|
||||
?+ wire (on-agent:def wire sign)
|
||||
[%observer @ ~] on-observer
|
||||
[%thread-result @ ~] on-thread-result
|
||||
[%thread-start @ @ ~] on-thread-start
|
||||
==
|
||||
::
|
||||
++ on-observer
|
||||
?> ?=([%observer @ ~] wire)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%watch-ack
|
||||
?~ p.sign [~ this]
|
||||
=/ =serial (slav %uv i.t.wire)
|
||||
~& watch-ack-deleting-observer+(~(got by observers) serial)
|
||||
[~ this(observers (~(del by observers) serial))]
|
||||
::
|
||||
%kick
|
||||
=/ =serial (slav %uv i.t.wire)
|
||||
=/ =observer:sur (~(got by observers) serial)
|
||||
:_ this
|
||||
:_ ~
|
||||
:* %pass
|
||||
wire
|
||||
%agent
|
||||
[our.bowl app.observer]
|
||||
%watch
|
||||
path.observer
|
||||
==
|
||||
::
|
||||
%fact
|
||||
=/ =serial (slav %uv i.t.wire)
|
||||
=/ =observer:sur (~(got by observers) serial)
|
||||
=/ tid (scot %uv (sham eny.bowl))
|
||||
:_ this
|
||||
:~ :* %pass
|
||||
[%thread-result i.t.wire ~]
|
||||
%agent
|
||||
[our.bowl %spider]
|
||||
%watch
|
||||
[%thread-result tid ~]
|
||||
==
|
||||
:* %pass
|
||||
[%thread-start i.t.wire tid ~]
|
||||
%agent
|
||||
[our.bowl %spider]
|
||||
%poke
|
||||
%spider-start
|
||||
!>([~ `tid thread.observer (slop q.cage.sign !>(~))])
|
||||
== ==
|
||||
==
|
||||
::
|
||||
++ on-thread-result
|
||||
?> ?=([%thread-result @ ~] wire)
|
||||
?+ -.sign (on-agent:def wire sign)
|
||||
%kick [~ this]
|
||||
%watch-ack [~ this]
|
||||
::
|
||||
%fact
|
||||
?. =(p.cage.sign %thread-fail)
|
||||
:_ this
|
||||
:_ ~
|
||||
:* %pass
|
||||
wire
|
||||
%agent
|
||||
[our.bowl %spider]
|
||||
%leave
|
||||
~
|
||||
==
|
||||
=/ =serial (slav %uv i.t.wire)
|
||||
=/ =observer:sur (~(got by observers) serial)
|
||||
~& observer-failed+observer
|
||||
:_ this(observers (~(del by observers) serial))
|
||||
:~ :* %pass
|
||||
[%observer i.t.wire ~]
|
||||
%agent
|
||||
[our.bowl app.observer]
|
||||
%leave
|
||||
~
|
||||
==
|
||||
:* %pass
|
||||
wire
|
||||
%agent
|
||||
[our.bowl %spider]
|
||||
%leave
|
||||
~
|
||||
==
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-thread-start
|
||||
?> ?=([%thread-start @ @ ~] wire)
|
||||
?. ?=(%poke-ack -.sign) (on-agent:def wire sign)
|
||||
?~ p.sign [~ this]
|
||||
=/ =serial (slav %uv i.t.wire)
|
||||
=/ =observer:sur (~(got by observers) serial)
|
||||
~& added-invalid-observer+observer
|
||||
:_ this(observers (~(del by observers) serial))
|
||||
:~ :* %pass
|
||||
[%observer i.t.wire ~]
|
||||
%agent
|
||||
[our.bowl app.observer]
|
||||
%leave
|
||||
~
|
||||
==
|
||||
:* %pass
|
||||
wire
|
||||
%agent
|
||||
[our.bowl app.observer]
|
||||
%leave
|
||||
~
|
||||
== ==
|
||||
--
|
||||
::
|
||||
++ on-watch on-watch:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-peek on-peek:def
|
||||
++ on-arvo on-arvo:def
|
||||
++ on-fail on-fail:def
|
||||
--
|
@ -111,6 +111,7 @@
|
||||
%hark-graph-hook
|
||||
%hark-group-hook
|
||||
%hark-chat-hook
|
||||
%observe-hook
|
||||
==
|
||||
::
|
||||
++ deft-fish :: default connects
|
||||
@ -250,6 +251,7 @@
|
||||
=> (se-born | %home %hark-group-hook)
|
||||
=> (se-born | %home %hark-chat-hook)
|
||||
(se-born | %home %hark-store)
|
||||
(se-born | %home %observe-hook)
|
||||
..on-load
|
||||
::
|
||||
++ reap-phat :: ack connect
|
||||
|
@ -12,26 +12,26 @@
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-init")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-init"))
|
||||
=^ cards agent on-init:ag
|
||||
[[(emit-event %on-init ~) cards] this]
|
||||
::
|
||||
++ on-save
|
||||
^- vase
|
||||
%- (print bowl "{<dap.bowl>}: on-save")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-save"))
|
||||
on-save:ag
|
||||
::
|
||||
++ on-load
|
||||
|= old-state=vase
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-load")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-load"))
|
||||
=^ cards agent (on-load:ag old-state)
|
||||
[[(emit-event %on-load ~) cards] this]
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-poke with mark {<mark>}")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-poke with mark {<mark>}"))
|
||||
?: ?=(%verb mark)
|
||||
?- !<(?(%loud %bowl) vase)
|
||||
%loud `this(loud !loud)
|
||||
@ -43,7 +43,7 @@
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-watch on path {<path>}")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-watch on path {<path>}"))
|
||||
=^ cards agent
|
||||
?: ?=([%verb %events ~] path)
|
||||
[~ agent]
|
||||
@ -53,7 +53,7 @@
|
||||
++ on-leave
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-leave on path {<path>}")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-leave on path {<path>}"))
|
||||
?: ?=([%verb %event ~] path)
|
||||
[~ this]
|
||||
=^ cards agent (on-leave:ag path)
|
||||
@ -62,39 +62,40 @@
|
||||
++ on-peek
|
||||
|= =path
|
||||
^- (unit (unit cage))
|
||||
%- (print bowl "{<dap.bowl>}: on-peek on path {<path>}")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-peek on path {<path>}"))
|
||||
(on-peek:ag path)
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-agent on wire {<wire>}, {<-.sign>}")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-agent on wire {<wire>}, {<-.sign>}"))
|
||||
=^ cards agent (on-agent:ag wire sign)
|
||||
[[(emit-event %on-agent wire -.sign) cards] this]
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-arvo on wire {<wire>}, {<[- +<]:sign-arvo>}")
|
||||
%- %+ print bowl |.
|
||||
"{<dap.bowl>}: on-arvo on wire {<wire>}, {<[- +<]:sign-arvo>}"
|
||||
=^ cards agent (on-arvo:ag wire sign-arvo)
|
||||
[[(emit-event %on-arvo wire [- +<]:sign-arvo) cards] this]
|
||||
::
|
||||
++ on-fail
|
||||
|= [=term =tang]
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
%- (print bowl "{<dap.bowl>}: on-fail with term {<term>}")
|
||||
%- (print bowl |.("{<dap.bowl>}: on-fail with term {<term>}"))
|
||||
=^ cards agent (on-fail:ag term tang)
|
||||
[[(emit-event %on-fail term) cards] this]
|
||||
--
|
||||
::
|
||||
++ print
|
||||
|= [=bowl:gall =tape]
|
||||
|= [=bowl:gall render=(trap tape)]
|
||||
^+ same
|
||||
=? . bowl-print
|
||||
%- (slog >bowl< ~)
|
||||
.
|
||||
?. loud same
|
||||
%- (slog leaf+tape ~)
|
||||
%- (slog [%leaf $:render] ~)
|
||||
same
|
||||
::
|
||||
++ emit-event
|
||||
|
13
pkg/arvo/mar/observe/action.hoon
Normal file
@ -0,0 +1,13 @@
|
||||
/- sur=observe-hook
|
||||
|_ =action:sur
|
||||
++ grad %noun
|
||||
++ grow
|
||||
|%
|
||||
++ noun action
|
||||
--
|
||||
::
|
||||
++ grab
|
||||
|%
|
||||
++ noun action:sur
|
||||
--
|
||||
--
|
7
pkg/arvo/sur/observe-hook.hoon
Normal file
@ -0,0 +1,7 @@
|
||||
|%
|
||||
+$ observer [app=term =path thread=term]
|
||||
+$ action
|
||||
$% [%watch =observer]
|
||||
[%ignore =observer]
|
||||
==
|
||||
--
|
28
pkg/arvo/ted/invite/accepted-graph.hoon
Normal file
@ -0,0 +1,28 @@
|
||||
/- spider, inv=invite-store, graph-view
|
||||
/+ strandio
|
||||
::
|
||||
=* strand strand:spider
|
||||
=* fail strand-fail:strand
|
||||
=* poke-our poke-our:strandio
|
||||
=* flog-text flog-text:strandio
|
||||
::
|
||||
^- thread:spider
|
||||
|= arg=vase
|
||||
=/ m (strand ,vase)
|
||||
^- form:m
|
||||
=+ !<([=update:inv ~] arg)
|
||||
?. ?=(%accepted -.update)
|
||||
(pure:m !>(~))
|
||||
;< =bowl:spider bind:m get-bowl:strandio
|
||||
=* invite invite.update
|
||||
?: =(our.bowl entity.resource.invite)
|
||||
:: do not crash because that will kill the invitatory subscription
|
||||
(pure:m !>(~))
|
||||
;< ~ bind:m
|
||||
%+ poke-our %spider
|
||||
=- spider-start+!>([`tid.bowl ~ %graph-join -])
|
||||
%+ slop
|
||||
!> ^- action:graph-view
|
||||
[%join resource.invite ship.invite]
|
||||
!>(~)
|
||||
(pure:m !>(~))
|
@ -1,17 +1,16 @@
|
||||
import React, { useRef, useCallback, useEffect } from "react";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { Col } from "@tlon/indigo-react";
|
||||
import React, { useRef, useCallback, useEffect } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Association } from "~/types/metadata-update";
|
||||
import { StoreState } from "~/logic/store/type";
|
||||
import { useFileDrag } from "~/logic/lib/useDrag";
|
||||
import ChatWindow from "./components/ChatWindow";
|
||||
import ChatInput from "./components/ChatInput";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { deSig } from "~/logic/lib/util";
|
||||
import { SubmitDragger } from "~/views/components/s3-upload";
|
||||
import { useLocalStorageState } from "~/logic/lib/useLocalStorageState";
|
||||
import { Association } from '~/types/metadata-update';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import { useFileDrag } from '~/logic/lib/useDrag';
|
||||
import ChatWindow from './components/ChatWindow';
|
||||
import ChatInput from './components/ChatInput';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { SubmitDragger } from '~/views/components/s3-upload';
|
||||
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
|
||||
|
||||
type ChatResourceProps = StoreState & {
|
||||
association: Association;
|
||||
@ -20,22 +19,22 @@ type ChatResourceProps = StoreState & {
|
||||
} & RouteComponentProps;
|
||||
|
||||
export function ChatResource(props: ChatResourceProps) {
|
||||
const station = props.association["app-path"];
|
||||
const station = props.association['app-path'];
|
||||
if (!props.chatInitialized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { envelopes, config } = (props.inbox?.[station]) ? props.inbox[station] : {envelopes: [], config: {}};
|
||||
const { envelopes, config } = (props.inbox?.[station]) ? props.inbox[station] : { envelopes: [], config: {} };
|
||||
const { read, length } = (config) ? config : undefined;
|
||||
|
||||
const groupPath = props.association["group-path"];
|
||||
const groupPath = props.association['group-path'];
|
||||
const group = props.groups[groupPath];
|
||||
const contacts = props.contacts[groupPath] || {};
|
||||
|
||||
const pendingMessages = (props.pendingMessages.get(station) || []).map(
|
||||
(value) => ({
|
||||
value => ({
|
||||
...value,
|
||||
pending: true,
|
||||
pending: true
|
||||
})
|
||||
);
|
||||
|
||||
@ -62,7 +61,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
const unreadCount = length - read;
|
||||
const unreadMsg = unreadCount > 0 && envelopes[unreadCount - 1];
|
||||
|
||||
const [, owner, name] = station.split("/");
|
||||
const [, owner, name] = station.split('/');
|
||||
const ourContact = contacts?.[window.ship];
|
||||
const lastMsgNum = envelopes.length || 0;
|
||||
|
||||
@ -81,24 +80,24 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
const { bind, dragging } = useFileDrag(onFileDrag);
|
||||
|
||||
const [unsent, setUnsent] = useLocalStorageState<Record<string, string>>(
|
||||
"chat-unsent",
|
||||
'chat-unsent',
|
||||
{}
|
||||
);
|
||||
|
||||
const appendUnsent = useCallback(
|
||||
(u: string) => setUnsent((s) => ({ ...s, [station]: u })),
|
||||
(u: string) => setUnsent(s => ({ ...s, [station]: u })),
|
||||
[station]
|
||||
);
|
||||
|
||||
const clearUnsent = useCallback(() => setUnsent((s) => _.omit(s, station)), [
|
||||
station,
|
||||
const clearUnsent = useCallback(() => setUnsent(s => _.omit(s, station)), [
|
||||
station
|
||||
]);
|
||||
|
||||
const scrollTo = new URLSearchParams(location.search).get('msg');
|
||||
useEffect(() => {
|
||||
const clear = () => {
|
||||
props.history.replace(location.pathname);
|
||||
}
|
||||
};
|
||||
setTimeout(clear, 10000);
|
||||
return clear;
|
||||
}, [station]);
|
||||
@ -141,7 +140,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
s3={props.s3}
|
||||
hideAvatars={props.hideAvatars}
|
||||
placeholder="Message..."
|
||||
message={unsent[station] || ""}
|
||||
message={unsent[station] || ''}
|
||||
deleteMessage={clearUnsent}
|
||||
/>
|
||||
</Col>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import ChatEditor from './chat-editor';
|
||||
import { S3Upload, SubmitDragger } from '~/views/components/s3-upload' ;
|
||||
import { S3Upload } from '~/views/components/s3-upload' ;
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Envelope } from '~/types/chat-update';
|
||||
import { Contacts, S3Configuration } from '~/types';
|
||||
import { Row } from '@tlon/indigo-react';
|
||||
import { Contacts } from '~/types';
|
||||
import { Row, BaseImage, Box, Icon } from '@tlon/indigo-react';
|
||||
|
||||
interface ChatInputProps {
|
||||
api: GlobalApi;
|
||||
@ -31,7 +31,6 @@ interface ChatInputState {
|
||||
uploadingPaste: boolean;
|
||||
}
|
||||
|
||||
|
||||
export default class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
public s3Uploader: React.RefObject<S3Upload>;
|
||||
private chatEditor: React.RefObject<ChatEditor>;
|
||||
@ -42,7 +41,7 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
this.state = {
|
||||
inCodeMode: false,
|
||||
submitFocus: false,
|
||||
uploadingPaste: false,
|
||||
uploadingPaste: false
|
||||
};
|
||||
|
||||
this.s3Uploader = React.createRef();
|
||||
@ -50,7 +49,6 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
|
||||
this.submit = this.submit.bind(this);
|
||||
this.toggleCode = this.toggleCode.bind(this);
|
||||
|
||||
}
|
||||
|
||||
toggleCode() {
|
||||
@ -82,8 +80,6 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
submit(text) {
|
||||
const { props, state } = this;
|
||||
if (state.inCodeMode) {
|
||||
@ -134,7 +130,6 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
{ url }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uploadError(error) {
|
||||
@ -159,10 +154,11 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
if (!this.readyToUpload()) {
|
||||
return;
|
||||
}
|
||||
if (!this.s3Uploader.current || !this.s3Uploader.current.inputRef.current) return;
|
||||
if (!this.s3Uploader.current || !this.s3Uploader.current.inputRef.current)
|
||||
return;
|
||||
this.s3Uploader.current.inputRef.current.files = files;
|
||||
const fire = document.createEvent("HTMLEvents");
|
||||
fire.initEvent("change", true, true);
|
||||
const fire = document.createEvent('HTMLEvents');
|
||||
fire.initEvent('change', true, true);
|
||||
this.s3Uploader.current?.inputRef.current?.dispatchEvent(fire);
|
||||
}
|
||||
|
||||
@ -179,7 +175,7 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
props.ourContact &&
|
||||
((props.ourContact.avatar !== null) && !props.hideAvatars)
|
||||
)
|
||||
? <img src={props.ourContact.avatar} height={16} width={16} className="dib" />
|
||||
? <BaseImage src={props.ourContact.avatar} height={16} width={16} className="dib" />
|
||||
: <Sigil
|
||||
ship={window.ship}
|
||||
size={16}
|
||||
@ -201,9 +197,9 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
className='cf'
|
||||
zIndex='0'
|
||||
>
|
||||
<div className="pa2 flex items-center">
|
||||
<Row p='2' alignItems='center'>
|
||||
{avatar}
|
||||
</div>
|
||||
</Row>
|
||||
<ChatEditor
|
||||
ref={this.chatEditor}
|
||||
inCodeMode={state.inCodeMode}
|
||||
@ -213,12 +209,13 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
onPaste={this.onPaste.bind(this)}
|
||||
placeholder='Message...'
|
||||
/>
|
||||
<div className="ml2 mr2 flex-shrink-0"
|
||||
style={{
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
flexBasis: 16,
|
||||
}}>
|
||||
<Box
|
||||
mx='2'
|
||||
flexShrink='0'
|
||||
height='16px'
|
||||
width='16px'
|
||||
flexBasis='16px'
|
||||
>
|
||||
<S3Upload
|
||||
ref={this.s3Uploader}
|
||||
configuration={props.s3.configuration}
|
||||
@ -227,28 +224,25 @@ export default class ChatInput extends Component<ChatInputProps, ChatInputState>
|
||||
uploadError={this.uploadError.bind(this)}
|
||||
accept="*"
|
||||
>
|
||||
<img
|
||||
className="invert-d"
|
||||
src="/~landscape/img/ImageUpload.png"
|
||||
<Icon icon='Links'
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
</S3Upload>
|
||||
</div>
|
||||
<div className="mr2 flex-shrink-0" style={{
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
flexBasis: 16,
|
||||
}}>
|
||||
<img style={{
|
||||
filter: state.inCodeMode ? 'invert(100%)' : '',
|
||||
height: '14px',
|
||||
width: '14px',
|
||||
}}
|
||||
</Box>
|
||||
<Box
|
||||
mr='2'
|
||||
flexShrink='0'
|
||||
height='16px'
|
||||
width='16px'
|
||||
flexBasis='16px'
|
||||
>
|
||||
<Icon
|
||||
icon='Dojo'
|
||||
onClick={this.toggleCode}
|
||||
src="/~landscape/img/CodeEval.png"
|
||||
className="contrast-10-d bg-white bg-none-d ba b--gray1-d br1" />
|
||||
</div>
|
||||
color={state.inCodeMode ? 'blue' : 'black'}
|
||||
/>
|
||||
</Box>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
@ -295,27 +295,89 @@ export const MessageContent = ({ content, remoteContentPolicy, measure }) => {
|
||||
};
|
||||
|
||||
export const MessagePlaceholder = ({ height, index, className = '', style = {}, ...props }) => (
|
||||
<div className={`w-100 f7 pl3 pt4 pr3 cf flex lh-copy ${className}`} style={{ height, ...style }} {...props}>
|
||||
<div className="fl pr3 v-top bg-white bg-gray0-d">
|
||||
<span
|
||||
className="db bg-gray2 bg-white-d"
|
||||
<Box
|
||||
width='100%'
|
||||
fontSize='2'
|
||||
pl='3' pt='4'
|
||||
pr='3'
|
||||
display='flex'
|
||||
lineHeight='tall'
|
||||
className={className}
|
||||
style={{ height, ...style }}
|
||||
{...props}
|
||||
>
|
||||
<Box pr='3' verticalAlign='top' backgroundColor='white' style={{ float: 'left' }}>
|
||||
<Text
|
||||
display='block'
|
||||
background='gray'
|
||||
width='24px'
|
||||
height='24px'
|
||||
borderRadius='50%'
|
||||
style={{
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
borderRadius: "50%",
|
||||
visibility: (index % 5 == 0) ? "initial" : "hidden",
|
||||
}}
|
||||
></span>
|
||||
</div>
|
||||
<div className="fr clamp-message white-d" style={{ flexGrow: 1, marginTop: -8 }}>
|
||||
<div className="hide-child" style={{paddingTop: "6px", visibility: (index % 5 == 0) ? "initial" : "hidden" }}>
|
||||
<p className={`v-mid f9 gray2 dib mr3 c-default`}>
|
||||
<span className="mw5 db"><span className="bg-gray5 bg-gray1-d db w-100 h-100"></span></span>
|
||||
</p>
|
||||
<p className="v-mid mono f9 gray2 dib"><span className="bg-gray5 bg-gray1-d db w-100 h-100" style={{height: "1em", width: `${(index % 3 + 1) * 3}em`}}></span></p>
|
||||
<p className="v-mid mono f9 ml2 gray2 dib child dn-s"><span className="bg-gray5 bg-gray1-d db w-100 h-100"></span></p>
|
||||
</div>
|
||||
<span className="bg-gray5 bg-gray1-d db w-100 h-100 db" style={{height: `1em`, width: `${(index % 5) * 20}%`}}></span>
|
||||
</div>
|
||||
</div>
|
||||
></Text>
|
||||
</Box>
|
||||
<Box
|
||||
style={{ float: 'right', flexGrow: 1 }}
|
||||
color='black'
|
||||
className="clamp-message"
|
||||
>
|
||||
<Box
|
||||
className="hide-child"
|
||||
paddingTop='4'
|
||||
style={{visibility: (index % 5 == 0) ? "initial" : "hidden" }}
|
||||
>
|
||||
<Text
|
||||
display='inline-block'
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
gray
|
||||
cursor='default'
|
||||
>
|
||||
<Text maxWidth='32rem' display='block'>
|
||||
<Text
|
||||
backgroundColor='gray'
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'></Text>
|
||||
</Text>
|
||||
</Text>
|
||||
<Text
|
||||
display='inline-block'
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
gray
|
||||
>
|
||||
<Text
|
||||
background='gray'
|
||||
display='block'
|
||||
height='1em'
|
||||
style={{ width: `${(index % 3 + 1) * 3}em` }}
|
||||
></Text>
|
||||
</Text>
|
||||
<Text
|
||||
mono
|
||||
verticalAlign='middle'
|
||||
fontSize='0'
|
||||
ml='2'
|
||||
gray
|
||||
display={['none', 'inline-block']}
|
||||
className="child">
|
||||
<Text
|
||||
backgroundColor='gray'
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
></Text>
|
||||
</Text>
|
||||
</Box>
|
||||
<Text
|
||||
display='block'
|
||||
backgroundColor='gray'
|
||||
height='1em'
|
||||
style={{ width: `${(index % 5) * 20}%` }}></Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,22 +1,34 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { Box, LoadingSpinner, Text } from '@tlon/indigo-react';
|
||||
|
||||
export const BacklogElement = (props) => {
|
||||
if (!props.isChatLoading) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="center mw6 absolute z-9999" style={{ left: 0, right: 0, top: 48}}>
|
||||
<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="/~landscape/img/Spinner.png"
|
||||
width={16}
|
||||
height={16}
|
||||
<Box
|
||||
marginLeft='auto'
|
||||
marginRight='auto'
|
||||
maxWidth='32rem'
|
||||
position='absolute'
|
||||
zIndex='9999'
|
||||
style={{ left: 0, right: 0, top: 0 }}
|
||||
>
|
||||
<Box
|
||||
display='flex'
|
||||
justifyContent='center'
|
||||
p='3'
|
||||
m='3'
|
||||
border='1px solid'
|
||||
borderColor='washedGray'
|
||||
backgroundColor='white'
|
||||
>
|
||||
<LoadingSpinner
|
||||
foreground='black'
|
||||
background='gray'
|
||||
/>
|
||||
<p className="lh-copy db ml3">Past messages are being restored</p>
|
||||
</div>
|
||||
</div>
|
||||
<Text display='block' ml='3' lineHeight='tall'>Past messages are being restored</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
ProfileOverlay,
|
||||
OVERLAY_HEIGHT
|
||||
} from './profile-overlay';
|
||||
import { Box, BaseImage } from '@tlon/indigo-react';
|
||||
|
||||
export class OverlaySigil extends PureComponent {
|
||||
constructor() {
|
||||
@ -58,7 +59,7 @@ export class OverlaySigil extends PureComponent {
|
||||
const { hideAvatars } = props;
|
||||
|
||||
const img = (props.contact && (props.contact.avatar !== null) && !hideAvatars)
|
||||
? <img src={props.contact.avatar} height={16} width={16} className="dib" />
|
||||
? <BaseImage display='inline-block' src={props.contact.avatar} height={16} width={16} />
|
||||
: <Sigil
|
||||
ship={props.ship}
|
||||
size={16}
|
||||
@ -69,9 +70,11 @@ export class OverlaySigil extends PureComponent {
|
||||
/>;
|
||||
|
||||
return (
|
||||
<div
|
||||
<Box
|
||||
cursor='pointer'
|
||||
position='relative'
|
||||
onClick={this.profileShow}
|
||||
className={props.className + ' pointer relative'}
|
||||
className={props.className}
|
||||
ref={this.containerRef}
|
||||
>
|
||||
{state.profileClicked && (
|
||||
@ -91,7 +94,7 @@ export class OverlaySigil extends PureComponent {
|
||||
/>
|
||||
)}
|
||||
{img}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
|
||||
import { Box, Col, Button, Text } from "@tlon/indigo-react";
|
||||
import { Box, Col, Button, Text, BaseImage } from '@tlon/indigo-react';
|
||||
|
||||
export const OVERLAY_HEIGHT = 250;
|
||||
|
||||
@ -51,8 +51,8 @@ export class ProfileOverlay extends PureComponent {
|
||||
|
||||
const isOwn = window.ship === ship;
|
||||
|
||||
let img = contact?.avatar && !hideAvatars
|
||||
? <img src={contact.avatar} height={160} width={160} className="brt2 dib" />
|
||||
const img = contact?.avatar && !hideAvatars
|
||||
? <BaseImage display='inline-block' src={contact.avatar} height={160} width={160} className="brt2" />
|
||||
: <Sigil
|
||||
ship={ship}
|
||||
size={160}
|
||||
@ -63,7 +63,7 @@ export class ProfileOverlay extends PureComponent {
|
||||
const showNickname = contact?.nickname && !hideNicknames;
|
||||
|
||||
// TODO: we need to rethink this "top-level profile view" of other ships
|
||||
/*if (!group.hidden) {
|
||||
/* if (!group.hidden) {
|
||||
}*/
|
||||
|
||||
const isHidden = group.hidden;
|
||||
@ -103,7 +103,7 @@ export class ProfileOverlay extends PureComponent {
|
||||
<Button
|
||||
mt='2'
|
||||
width='100%'
|
||||
style={{ cursor: 'pointer '}}
|
||||
style={{ cursor: 'pointer ' }}
|
||||
onClick={() => (isHidden) ? history.push('/~profile/identity') : history.push(`${history.location.pathname}/popover/profile`)}
|
||||
>
|
||||
Edit Identity
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Box, Text, Button } from '@tlon/indigo-react';
|
||||
|
||||
export class ResubscribeElement extends Component {
|
||||
onClickResubscribe() {
|
||||
@ -9,21 +10,23 @@ export class ResubscribeElement extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { props } = this;
|
||||
if (props.isChatUnsynced) {
|
||||
return (
|
||||
<div className="db pa3 ma3 ba b--yellow2 bg-yellow0">
|
||||
<p className="lh-copy db">
|
||||
<Box p='3' m='3' border='1px solid' borderColor='yellow' backgroundColor='lightYellow'>
|
||||
<Text lineHeight='tall' display='block'>
|
||||
Your ship has been disconnected from the chat's host.
|
||||
This may be due to a bad connection, going offline, lack of permission,
|
||||
or an over-the-air update.
|
||||
</p>
|
||||
<a onClick={this.onClickResubscribe.bind(this)}
|
||||
className="db underline black pointer mt3"
|
||||
</Text>
|
||||
<Button
|
||||
primary
|
||||
mt='3'
|
||||
onClick={this.onClickResubscribe.bind(this)}
|
||||
>
|
||||
Reconnect to this chat
|
||||
</a>
|
||||
</div>
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
|
@ -95,20 +95,6 @@ h2 {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
|
||||
/* spinner */
|
||||
|
||||
.spin-active {
|
||||
animation: spin 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {transform: rotate(0deg);}
|
||||
25% {transform: rotate(90deg);}
|
||||
50% {transform: rotate(180deg);}
|
||||
75% {transform: rotate(270deg);}
|
||||
100% {transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
.embed-container iframe {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -15,18 +15,13 @@ import Groups from './components/Groups';
|
||||
|
||||
const ScrollbarLessBox = styled(Box)`
|
||||
scrollbar-width: none !important;
|
||||
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export default class LaunchApp extends React.Component {
|
||||
componentDidMount() {
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
|
@ -12,8 +12,7 @@ import { RouteComponentProps } from "react-router-dom";
|
||||
import { LinkItem } from "./components/link-item";
|
||||
import { LinkSubmit } from "./components/link-submit";
|
||||
import { LinkPreview } from "./components/link-preview";
|
||||
import { CommentSubmit } from "./components/comment-submit";
|
||||
import { Comments } from "./components/comments";
|
||||
import { Comments } from "~/views/components/comments";
|
||||
|
||||
import "./css/custom.css";
|
||||
|
||||
@ -70,9 +69,9 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
render={(props) => {
|
||||
return (
|
||||
<Col width="100%" p={4} alignItems="center" maxWidth="768px">
|
||||
<Row width="100%" flexShrink='0'>
|
||||
<Col width="100%" flexShrink='0'>
|
||||
<LinkSubmit s3={s3} name={name} ship={ship.slice(1)} api={api} />
|
||||
</Row>
|
||||
</Col>
|
||||
{Array.from(graph).map(([date, node]) => {
|
||||
const contact = contactDetails[node.post.author];
|
||||
return (
|
||||
@ -123,16 +122,10 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
commentNumber={node.children.size}
|
||||
remoteContentPolicy={remoteContentPolicy}
|
||||
/>
|
||||
<Row flexShrink='0'>
|
||||
<CommentSubmit
|
||||
name={name}
|
||||
ship={ship}
|
||||
api={api}
|
||||
parentIndex={node.post.index}
|
||||
/>
|
||||
</Row>
|
||||
<Comments
|
||||
comments={node.children}
|
||||
ship={ship}
|
||||
name={name}
|
||||
comments={node}
|
||||
resource={resourcePath}
|
||||
contacts={contactDetails}
|
||||
api={api}
|
||||
|
@ -1,63 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import moment from 'moment';
|
||||
import { Box, Text, Row } from '@tlon/indigo-react';
|
||||
import RichText from '~/views/components/RichText';
|
||||
import { MentionText } from "~/views/components/MentionText";
|
||||
|
||||
export const CommentItem = (props) => {
|
||||
const content = props.post.contents;
|
||||
const timeSent =
|
||||
moment.unix(props.post['time-sent'] / 1000).format('hh:mm a');
|
||||
|
||||
const showAvatar = props.avatar && !props.hideAvatars;
|
||||
const showNickname = props.nickname && !props.hideNicknames;
|
||||
const img = showAvatar
|
||||
? <img src={props.avatar} height={36} width={36} className="dib" />
|
||||
: <Sigil
|
||||
ship={`~${props.post.author}`}
|
||||
size={36}
|
||||
color={`#${props.color}`}
|
||||
classes={(!!props.member ? 'mix-blend-diff' : '')}
|
||||
/>;
|
||||
|
||||
const disabled = props.pending || props.post.author !== window.ship;
|
||||
|
||||
const onDelete = async () => {
|
||||
const [ship,name] = props.resource.split('/');
|
||||
await props.api.graph.removeNodes(`~${ship}`, name, [props.post.index]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box width="100%" py={3} opacity={props.pending ? '0.6' : '1'}>
|
||||
<Row backgroundColor='white'>
|
||||
{img}
|
||||
<Row fontSize={0} alignItems="center" ml={2}>
|
||||
<Text mono={!props.hasNickname} title={props.post.author}>
|
||||
{showNickname ? props.nickname : cite(props.post.author)}
|
||||
</Text>
|
||||
<Text gray ml={2}>{timeSent}</Text>
|
||||
{!disabled && (
|
||||
<>
|
||||
<Box cursor="pointer" pl="2" color="red" onClick={onDelete}>
|
||||
Delete
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
</Row>
|
||||
</Row>
|
||||
<Row>
|
||||
<Text py={3} fontSize={1}>
|
||||
<MentionText
|
||||
remoteContentPolicy={props.remoteContentPolicy}
|
||||
contacts={props.contacts}
|
||||
content={content}
|
||||
/>
|
||||
</Text>
|
||||
</Row>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,84 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { createPost } from '~/logic/api/graph';
|
||||
import { deSig } from "~/logic/lib/util";
|
||||
import { scanForMentions } from "~/logic/lib/graph";
|
||||
|
||||
|
||||
export class CommentSubmit extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
comment: '',
|
||||
commentFocus: false,
|
||||
disabled: false
|
||||
};
|
||||
}
|
||||
|
||||
onClickPost() {
|
||||
const parentIndex = this.props.parentIndex || '';
|
||||
const content = scanForMentions(this.state.comment);
|
||||
let post = createPost(content, parentIndex);
|
||||
|
||||
this.setState({ disabled: true }, () => {
|
||||
this.props.api.graph.addPost(
|
||||
`~${deSig(this.props.ship)}`,
|
||||
this.props.name,
|
||||
post
|
||||
).then((r) => {
|
||||
this.setState({
|
||||
disabled: false,
|
||||
comment: ''
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setComment(event) {
|
||||
this.setState({ comment: event.target.value });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, props } = this;
|
||||
const focus = (state.commentFocus)
|
||||
? 'b--black b--white-d'
|
||||
: 'b--gray4 b--gray2-d';
|
||||
|
||||
const activeClasses = state.comment
|
||||
? 'black white-d pointer'
|
||||
: 'gray2 b--gray2';
|
||||
|
||||
return (
|
||||
<div className={'w-100 relative ba br1 mt6 mb6 ' + focus}>
|
||||
<textarea
|
||||
className="w-100 bg-gray0-d white-d f8 pa2 pr8"
|
||||
style={{ resize: 'none', height: 75 }}
|
||||
placeholder="Leave a comment on this link"
|
||||
onChange={this.setComment.bind(this)}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
(e.getModifierState('Control') || e.metaKey) &&
|
||||
e.key === 'Enter'
|
||||
) {
|
||||
this.onClickPost();
|
||||
}
|
||||
}}
|
||||
onFocus={() => this.setState({ commentFocus: true })}
|
||||
onBlur={() => this.setState({ commentFocus: false })}
|
||||
value={state.comment}
|
||||
/>
|
||||
<button
|
||||
className={
|
||||
'f8 bg-gray0-d ml2 absolute ' + activeClasses
|
||||
}
|
||||
disabled={state.disabled}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
style={{ bottom: 12, right: 8 }}>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommentItem } from './comment-item';
|
||||
import { getContactDetails } from '~/logic/lib/util';
|
||||
|
||||
|
||||
export const Comments = (props) => {
|
||||
const {
|
||||
hideNicknames,
|
||||
hideAvatars,
|
||||
remoteContentPolicy
|
||||
} = props;
|
||||
|
||||
const contacts = props.contacts ? props.contacts : {};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ Array.from(props.comments).map(([date, comment]) => {
|
||||
const { nickname, color, member, avatar } =
|
||||
getContactDetails(contacts[comment.post.author]);
|
||||
|
||||
const nameClass = nickname && !hideNicknames ? 'inter' : 'mono';
|
||||
|
||||
return (
|
||||
<CommentItem
|
||||
api={props.api}
|
||||
resource={props.resource}
|
||||
key={comment.post.index}
|
||||
contacts={contacts}
|
||||
post={comment.post}
|
||||
nickname={nickname}
|
||||
hasNickname={Boolean(nickname)}
|
||||
color={color}
|
||||
avatar={avatar}
|
||||
member={member}
|
||||
hideNicknames={hideNicknames}
|
||||
hideAvatars={hideAvatars}
|
||||
remoteContentPolicy={remoteContentPolicy}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,17 +1,16 @@
|
||||
import React from 'react';
|
||||
import { Row, Col, Anchor, Box, Text } from '@tlon/indigo-react';
|
||||
import { Row, Col, Anchor, Box, Text, BaseImage } from '@tlon/indigo-react';
|
||||
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
|
||||
import { roleForShip } from "~/logic/lib/group";
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
|
||||
export const LinkItem = (props) => {
|
||||
const {
|
||||
node,
|
||||
nickname,
|
||||
color,
|
||||
avatar,
|
||||
resource,
|
||||
hideAvatars,
|
||||
@ -34,13 +33,13 @@ export const LinkItem = (props) => {
|
||||
const showNickname = nickname && !hideNicknames;
|
||||
|
||||
const img = showAvatar
|
||||
? <img src={props.avatar} height={36} width={36} className="dib" />
|
||||
? <BaseImage display='inline-block' src={props.avatar} height={36} width={36} />
|
||||
: <Sigil ship={`~${author}`} size={36} color={'#' + props.color} />;
|
||||
|
||||
const baseUrl = props.baseUrl || `/~404/${resource}`;
|
||||
|
||||
const ourRole = group ? roleForShip(group, window.ship) : undefined;
|
||||
const [ship, name] = resource.split("/");
|
||||
const [ship, name] = resource.split('/');
|
||||
|
||||
return (
|
||||
<Row minWidth='0' flexShrink='0' width="100%" alignItems="center" py={3} bg="white">
|
||||
@ -53,19 +52,21 @@ export const LinkItem = (props) => {
|
||||
href={contents[1].url}
|
||||
width="100%"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<Text display='inline-block' overflow='hidden' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre'}}> {contents[0].text}</Text>
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Text display='inline-block' overflow='hidden' style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}>{contents[0].text}</Text>
|
||||
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} ↗</Text>
|
||||
</Anchor>
|
||||
<Box width="100%">
|
||||
<Text
|
||||
fontFamily={showNickname ? 'sans' : 'mono'} pr={2}>
|
||||
fontFamily={showNickname ? 'sans' : 'mono'} pr={2}
|
||||
>
|
||||
{showNickname ? nickname : cite(author) }
|
||||
</Text>
|
||||
<Link to={`${baseUrl}/${index}`}>
|
||||
<Text color="gray">{size} comments</Text>
|
||||
</Link>
|
||||
{(ourRole === "admin" || node.post.author === window.ship)
|
||||
{(ourRole === 'admin' || node.post.author === window.ship)
|
||||
&& (<Text color='red' ml='2' cursor='pointer' onClick={() => api.graph.removeNodes(`~${ship}`, name, [node.post.index])}>Delete</Text>)}
|
||||
</Box>
|
||||
</Col>
|
||||
|
@ -1,18 +1,17 @@
|
||||
import React from 'react';
|
||||
import { cite } from '~/logic/lib/util';
|
||||
import RemoteContent from "~/views/components/RemoteContent";
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
|
||||
import { Box, Col, Anchor, Text } from '@tlon/indigo-react';
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
const URLparser = new RegExp(
|
||||
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
|
||||
);
|
||||
|
||||
|
||||
export const LinkPreview = (props) => {
|
||||
const showNickname = props.nickname && !props.hideNicknames;
|
||||
const nameClass = showNickname ? 'inter' : 'mono';
|
||||
const author = props.post.author;
|
||||
const title = props.post.contents[0].text;
|
||||
const url = props.post.contents[1].url;
|
||||
@ -32,29 +31,36 @@ export const LinkPreview = (props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="pb6 w-100">
|
||||
<div className='w-100 tc'>{embed}</div>
|
||||
<div className="flex flex-column ml2 pt6 flex-auto">
|
||||
<a href={url}
|
||||
className="w-100 flex"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<p className="f8 truncate">{title}</p>
|
||||
<span className="gray2 ml2 f8 dib v-btm flex-shrink-0" style={{ whiteSpace: 'nowrap' }}>
|
||||
{hostname} ↗
|
||||
</span>
|
||||
</a>
|
||||
<div className="w-100 pt1">
|
||||
<span className={'f9 pr2 white-d dib ' + nameClass} title={author}>
|
||||
<Box pb='6' width='100%'>
|
||||
<Box width='100%' textAlign='center'>{embed}</Box>
|
||||
<Col flex='1 1 auto' minWidth='0' minHeight='0' pt='6'>
|
||||
<Anchor href={url}
|
||||
lineHeight="tall"
|
||||
display='flex'
|
||||
style={{ textDecoration: 'none' }}
|
||||
width='100%'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<Text
|
||||
display='inline-block'
|
||||
overflow='hidden'
|
||||
style={{ textOverflow: 'ellipsis', whiteSpace: 'pre' }}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
<Text ml="2" color="gray" display='inline-block' flexShrink='0'>{hostname} ↗</Text>
|
||||
</Anchor>
|
||||
<Box width='100%' pt='1'>
|
||||
<Text fontSize='0' pr='2' display='inline-block' mono={!showNickname} title={author}>
|
||||
{showNickname ? props.nickname : cite(`~${author}`)}
|
||||
</span>
|
||||
<span className="f9 inter gray2 pr3 dib">{timeSent}</span>
|
||||
<span className="f9 inter gray2 dib">
|
||||
</Text>
|
||||
<Text fontSize='0' gray pr='3' display='inline-block'>{timeSent}</Text>
|
||||
<Text gray fontSize='0' display='inline-block'>
|
||||
{props.commentNumber} comments
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
</Col>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,14 +2,12 @@ import React, { Component } from 'react';
|
||||
import { hasProvider } from 'oembed-parser';
|
||||
|
||||
import { S3Upload, SubmitDragger } from '~/views/components/s3-upload';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { Icon } from "@tlon/indigo-react";
|
||||
import { Box, Text, BaseInput, Button } from '@tlon/indigo-react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { S3State } from '~/types';
|
||||
|
||||
import { createPost } from '~/logic/api/graph';
|
||||
|
||||
|
||||
interface LinkSubmitProps {
|
||||
api: GlobalApi;
|
||||
s3: S3State;
|
||||
@ -58,7 +56,7 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
this.setState({ disabled: true });
|
||||
|
||||
const parentIndex = this.props.parentIndex || '';
|
||||
let post = createPost([
|
||||
const post = createPost([
|
||||
{ text: title },
|
||||
{ url: link }
|
||||
], parentIndex);
|
||||
@ -80,7 +78,7 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
setLinkValid(linkValue) {
|
||||
const URLparser = new RegExp(
|
||||
/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/
|
||||
);;
|
||||
);
|
||||
|
||||
let linkValid = URLparser.test(linkValue);
|
||||
|
||||
@ -101,7 +99,7 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
if (result.title && !this.state.linkTitle) {
|
||||
this.setState({ linkTitle: result.title });
|
||||
}
|
||||
}).catch((error) => {/*noop*/});
|
||||
}).catch((error) => { /* noop*/ });
|
||||
} else if (!this.state.linkTitle) {
|
||||
this.setState({
|
||||
linkTitle: decodeURIComponent(linkValue
|
||||
@ -113,7 +111,7 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
.replace('_', ' ')
|
||||
.replace(/\d{4}\.\d{1,2}\.\d{2}\.\.\d{2}\.\d{2}\.\d{2}-/, '')
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,19 +168,12 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
return;
|
||||
}
|
||||
this.s3Uploader.current.inputRef.current.files = files;
|
||||
const fire = document.createEvent("HTMLEvents");
|
||||
fire.initEvent("change", true, true);
|
||||
const fire = document.createEvent('HTMLEvents');
|
||||
fire.initEvent('change', true, true);
|
||||
this.s3Uploader.current?.inputRef.current?.dispatchEvent(fire);
|
||||
}
|
||||
|
||||
render() {
|
||||
const activeClasses = (this.state.linkValid && !this.state.disabled)
|
||||
? 'green2 pointer' : 'gray2';
|
||||
|
||||
const focus = (this.state.submitFocus)
|
||||
? 'b--black b--white-d'
|
||||
: 'b--gray4 b--gray2-d';
|
||||
|
||||
const isS3Ready =
|
||||
( this.props.s3.credentials.secretAccessKey &&
|
||||
this.props.s3.credentials.endpoint &&
|
||||
@ -190,44 +181,57 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex-shrink-0 relative ba br1 w-100 mb6 ${focus}`}
|
||||
<>
|
||||
<Box
|
||||
flexShrink='0'
|
||||
position='relative'
|
||||
border='1px solid'
|
||||
borderColor={this.state.submitFocus ? 'black' : 'washedGray'}
|
||||
width='100%'
|
||||
borderRadius='2'
|
||||
onDragEnter={this.onDragEnter.bind(this)}
|
||||
onDragOver={e => {
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
if (isS3Ready) {
|
||||
this.setState({ dragover: true})
|
||||
this.setState({ dragover: true });
|
||||
}
|
||||
}}
|
||||
onDragLeave={() => this.setState({ dragover: false })}
|
||||
onDrop={this.onDrop}
|
||||
>
|
||||
{this.state.dragover ? <SubmitDragger /> : null}
|
||||
<div className="relative">
|
||||
<Box position='relative'>
|
||||
{
|
||||
( this.state.linkValue ||
|
||||
this.state.urlFocus ||
|
||||
this.state.disabled
|
||||
) ? null : (
|
||||
isS3Ready ? (
|
||||
<span className="gray2 absolute pl2 pt3 pb2 f8"
|
||||
style={{pointerEvents: 'none'}}>
|
||||
<Text gray position='absolute'
|
||||
pl='2' pt='2'
|
||||
pb='2' fontSize='0'
|
||||
style={{ pointerEvents: 'none' }}
|
||||
>
|
||||
Drop or
|
||||
<span className="pointer green2"
|
||||
style={{pointerEvents: 'all'}}
|
||||
<Text cursor='pointer' color='blue'
|
||||
style={{ pointerEvents: 'all' }}
|
||||
onClick={(event) => {
|
||||
if (!this.readyToUpload()) {
|
||||
return;
|
||||
}
|
||||
this.s3Uploader.current.inputRef.current.click();
|
||||
}}> upload </span>
|
||||
}}
|
||||
> upload </Text>
|
||||
a file, or paste a link here
|
||||
</span>
|
||||
</Text>
|
||||
) : (
|
||||
<span className="gray2 absolute pl2 pt3 pb2 f8"
|
||||
style={{pointerEvents: 'none'}}>
|
||||
<Text gray position='absolute'
|
||||
pl='2' pt='2'
|
||||
pb='2' fontSize='0'
|
||||
style={{ pointerEvents: 'none' }}
|
||||
>
|
||||
Paste a link here
|
||||
</span>
|
||||
</Text>
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -238,10 +242,16 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
uploadSuccess={this.uploadSuccess.bind(this)}
|
||||
uploadError={this.uploadError.bind(this)}
|
||||
className="dn absolute pt3 pb2 pl2 w-100"
|
||||
></S3Upload> : null}
|
||||
<input
|
||||
></S3Upload> : null}
|
||||
<BaseInput
|
||||
type="url"
|
||||
className="pl2 w-100 f8 pt3 pb2 white-d bg-transparent"
|
||||
pl='2'
|
||||
width='100%'
|
||||
fontSize='0'
|
||||
pt='2'
|
||||
pb='2'
|
||||
color='black'
|
||||
backgroundColor='transparent'
|
||||
onChange={this.setLinkValue}
|
||||
onBlur={() => this.setState({ submitFocus: false, urlFocus: false })}
|
||||
onFocus={() => this.setState({ submitFocus: true, urlFocus: true })}
|
||||
@ -255,10 +265,15 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
}}
|
||||
value={this.state.linkValue}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
</Box>
|
||||
<BaseInput
|
||||
pl='2'
|
||||
backgroundColor='transparent'
|
||||
width='100%'
|
||||
fontSize='0'
|
||||
color='black'
|
||||
type="text"
|
||||
className="pl2 bg-transparent w-100 f8 white-d linkTitle"
|
||||
className="linkTitle"
|
||||
style={{
|
||||
resize: 'none',
|
||||
height: 40
|
||||
@ -276,23 +291,18 @@ export class LinkSubmit extends Component<LinkSubmitProps, LinkSubmitState> {
|
||||
}}
|
||||
value={this.state.linkTitle}
|
||||
/>
|
||||
{!this.state.disabled ? <button
|
||||
className={
|
||||
'bg-transparent f8 flex-shrink-0 pr2 pl2 pt2 pb3 ' + activeClasses
|
||||
}
|
||||
disabled={!this.state.linkValid || this.state.disabled}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
style={{
|
||||
bottom: 12,
|
||||
right: 8
|
||||
}}
|
||||
>
|
||||
</Box>
|
||||
<Box mt='2' mb='4'>
|
||||
<Button
|
||||
flexShrink='0'
|
||||
primary
|
||||
disabled={!this.state.linkValid || this.state.disabled}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
>
|
||||
Post link
|
||||
</button> : null}
|
||||
<Spinner awaiting={this.state.disabled} classes="nowrap flex items-center pr2 pl2 pt2 pb4" style={{flex: '1 1 14rem'}} text="Posting to collection..." />
|
||||
|
||||
|
||||
</div>
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
) ;
|
||||
}
|
||||
}
|
||||
|
@ -32,11 +32,13 @@ export function Author(props: AuthorProps) {
|
||||
{showImage && (
|
||||
<Box>
|
||||
{showAvatar ? (
|
||||
<img src={contact?.avatar} height={24} width={24} className="dib" />
|
||||
<img src={contact?.avatar} height={16} width={16} className="dib" />
|
||||
) : (
|
||||
<Sigil
|
||||
ship={ship}
|
||||
size={24}
|
||||
size={16}
|
||||
icon
|
||||
padded
|
||||
color={color}
|
||||
classes={contact?.color ? '' : "mix-blend-diff"}
|
||||
/>
|
||||
@ -45,8 +47,10 @@ export function Author(props: AuthorProps) {
|
||||
)}
|
||||
<Box
|
||||
ml={showImage ? 2 : 0}
|
||||
color="gray"
|
||||
color="black"
|
||||
lineHeight='tall'
|
||||
fontFamily={showNickname ? "sans" : "mono"}
|
||||
fontWeight={showNickname ? '500' : '400'}
|
||||
>
|
||||
{name}
|
||||
</Box>
|
||||
|
@ -5,7 +5,7 @@ import bigInt from 'big-integer';
|
||||
|
||||
import { Link, RouteComponentProps } from "react-router-dom";
|
||||
import { Spinner } from "~/views/components/Spinner";
|
||||
import { Comments } from "./Comments";
|
||||
import { Comments } from "~/views/components/Comments";
|
||||
import { NoteNavigation } from "./NoteNavigation";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
||||
@ -34,13 +34,13 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
const deletePost = async () => {
|
||||
setDeleting(true);
|
||||
const indices = [note.post.index]
|
||||
await api.graph.removeNodes(ship, book, indices);
|
||||
await api.graph.removeNodes(ship, book, indices);
|
||||
props.history.push(rootUrl);
|
||||
};
|
||||
|
||||
const comments = getComments(note);
|
||||
const [revNum, title, body, post] = getLatestRevision(note);
|
||||
|
||||
|
||||
const noteId = bigInt(note.post.index.split('/')[1]);
|
||||
|
||||
let adminLinks: JSX.Element | null = null;
|
||||
@ -106,8 +106,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
/>
|
||||
<Comments
|
||||
ship={ship}
|
||||
book={props.book}
|
||||
note={props.note}
|
||||
name={props.book}
|
||||
comments={comments}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import * as Yup from "yup";
|
||||
import { Formik, FormikHelpers, Form, useFormikContext } from "formik";
|
||||
import { AsyncButton } from "../../../components/AsyncButton";
|
||||
import { AsyncButton } from "./AsyncButton";
|
||||
import { ManagedTextAreaField as TextArea } from "@tlon/indigo-react";
|
||||
|
||||
interface FormSchema {
|
@ -1,27 +1,24 @@
|
||||
import React, { useState } from "react";
|
||||
import CommentInput from "./CommentInput";
|
||||
import { Comment, NoteId } from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Box, Row } from "@tlon/indigo-react";
|
||||
import styled from "styled-components";
|
||||
import { Author } from "./Author";
|
||||
import { GraphNode, TextContent } from "~/types/graph-update";
|
||||
import tokenizeMessage from "~/logic/lib/tokenizeMessage";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import { LocalUpdateRemoteContentPolicy } from "~/types";
|
||||
import { MentionText } from "~/views/components/MentionText";
|
||||
import React from 'react';
|
||||
import { Contacts } from '~/types/contact-update';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Box, Row } from '@tlon/indigo-react';
|
||||
import styled from 'styled-components';
|
||||
import { Author } from '~/views/apps/publish/components/Author';
|
||||
import { GraphNode, TextContent } from '~/types/graph-update';
|
||||
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
|
||||
import { LocalUpdateRemoteContentPolicy } from '~/types';
|
||||
import { MentionText } from '~/views/components/MentionText';
|
||||
|
||||
const ClickBox = styled(Box)`
|
||||
cursor: pointer;
|
||||
padding-left: ${(p) => p.theme.space[2]}px;
|
||||
padding-left: ${p => p.theme.space[2]}px;
|
||||
`;
|
||||
|
||||
interface CommentItemProps {
|
||||
pending?: boolean;
|
||||
comment: GraphNode;
|
||||
contacts: Contacts;
|
||||
book: string;
|
||||
name: string;
|
||||
ship: string;
|
||||
api: GlobalApi;
|
||||
hideNicknames: boolean;
|
||||
@ -30,24 +27,24 @@ interface CommentItemProps {
|
||||
}
|
||||
|
||||
export function CommentItem(props: CommentItemProps) {
|
||||
const { ship, contacts, book, api, remoteContentPolicy } = props;
|
||||
const { ship, contacts, name, api, remoteContentPolicy } = props;
|
||||
const commentData = props.comment?.post;
|
||||
const comment = commentData.contents;
|
||||
|
||||
const disabled = props.pending || window.ship !== commentData.author;
|
||||
|
||||
const onDelete = async () => {
|
||||
await api.graph.removeNodes(ship, book, [commentData?.index]);
|
||||
await api.graph.removeNodes(ship, name, [commentData?.index]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={4} opacity={commentData?.pending ? "60%" : "100%"}>
|
||||
<Box mb={4} opacity={commentData?.pending ? '60%' : '100%'}>
|
||||
<Row bg="white" my={3}>
|
||||
<Author
|
||||
showImage
|
||||
contacts={contacts}
|
||||
ship={commentData?.author}
|
||||
date={commentData?.["time-sent"]}
|
||||
date={commentData?.['time-sent']}
|
||||
hideAvatars={props.hideAvatars}
|
||||
hideNicknames={props.hideNicknames}
|
||||
>
|
@ -1,22 +1,18 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Col } from "@tlon/indigo-react";
|
||||
import { CommentItem } from "./CommentItem";
|
||||
import CommentInput from "./CommentInput";
|
||||
import { dateToDa } from "~/logic/lib/util";
|
||||
import { Comment, Note, NoteId } from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import _ from "lodash";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { FormikHelpers } from "formik";
|
||||
import {GraphNode, Graph} from "~/types/graph-update";
|
||||
import {createPost} from "~/logic/api/graph";
|
||||
import { LocalUpdateRemoteContentPolicy } from "~/types";
|
||||
import {scanForMentions} from "~/logic/lib/graph";
|
||||
import React from 'react';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import { CommentItem } from './CommentItem';
|
||||
import CommentInput from './CommentInput';
|
||||
import { Contacts } from '~/types/contact-update';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { FormikHelpers } from 'formik';
|
||||
import { GraphNode } from '~/types/graph-update';
|
||||
import { createPost } from '~/logic/api/graph';
|
||||
import { LocalUpdateRemoteContentPolicy } from '~/types';
|
||||
import { scanForMentions } from '~/logic/lib/graph';
|
||||
|
||||
interface CommentsProps {
|
||||
comments: GraphNode;
|
||||
book: string;
|
||||
note: GraphNode;
|
||||
name: string;
|
||||
ship: string;
|
||||
contacts: Contacts;
|
||||
api: GlobalApi;
|
||||
@ -26,16 +22,16 @@ interface CommentsProps {
|
||||
}
|
||||
|
||||
export function Comments(props: CommentsProps) {
|
||||
const { comments, ship, book, note, api } = props;
|
||||
const { comments, ship, name, api } = props;
|
||||
|
||||
const onSubmit = async (
|
||||
{ comment },
|
||||
actions: FormikHelpers<{ comment: string }>
|
||||
) => {
|
||||
try {
|
||||
const content = scanForMentions(comment)
|
||||
const content = scanForMentions(comment);
|
||||
const post = createPost(content, comments?.post?.index);
|
||||
await api.graph.addPost(ship, book, post)
|
||||
await api.graph.addPost(ship, name, post);
|
||||
actions.resetForm();
|
||||
actions.setStatus({ success: null });
|
||||
} catch (e) {
|
||||
@ -53,7 +49,7 @@ export function Comments(props: CommentsProps) {
|
||||
key={idx.toString()}
|
||||
contacts={props.contacts}
|
||||
api={api}
|
||||
book={book}
|
||||
name={name}
|
||||
ship={ship}
|
||||
hideNicknames={props.hideNicknames}
|
||||
hideAvatars={props.hideAvatars}
|
@ -1,18 +1,19 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { LoadingSpinner, Text } from '@tlon/indigo-react';
|
||||
|
||||
const Spinner = ({
|
||||
classes = '',
|
||||
text = '',
|
||||
awaiting = false
|
||||
}) => awaiting ? (
|
||||
<div className={classes + ' z-2 bg-white bg-gray0-d white-d flex'}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~landscape/img/Spinner.png"
|
||||
width={16}
|
||||
height={16}
|
||||
<Text zIndex='2' display='flex' className={classes}>
|
||||
<LoadingSpinner
|
||||
foreground='black'
|
||||
background='gray'
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
<p className="dib f9 ml2 v-mid inter">{text}</p>
|
||||
</div>
|
||||
<Text display='inline-block' ml='2' verticalAlign='middle' flexShrink='0'>{text}</Text>
|
||||
</Text>
|
||||
) : null;
|
||||
|
||||
export { Spinner as default, Spinner };
|
||||
export { Spinner as default, Spinner };
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import { BaseInput, Box, Text, Icon } from "@tlon/indigo-react";
|
||||
import { BaseInput, Box, Text, Icon, LoadingSpinner } from "@tlon/indigo-react";
|
||||
|
||||
import S3Client from '~/logic/lib/s3';
|
||||
import { Spinner } from './Spinner';
|
||||
import { S3Credentials, S3Configuration } from '~/types';
|
||||
import { dateToDa, deSig } from '~/logic/lib/util';
|
||||
|
||||
@ -148,7 +147,7 @@ export class S3Upload extends Component<S3UploadProps, S3UploadState> {
|
||||
accept={accept}
|
||||
onChange={this.onChange.bind(this)} />
|
||||
{this.state.isUploading
|
||||
? <Spinner awaiting={true} classes={className} />
|
||||
? <LoadingSpinner background="gray" foreground="black" />
|
||||
: <Text cursor='pointer' className={className} onClick={this.onClick.bind(this)}>{display}</Text>
|
||||
}
|
||||
</>
|
||||
|
@ -23,11 +23,9 @@ type LandscapeProps = StoreState & {
|
||||
export default class Landscape extends Component<LandscapeProps, {}> {
|
||||
componentDidMount() {
|
||||
document.title = 'OS1 - Landscape';
|
||||
// preload spinner asset
|
||||
new Image().src = '/~landscape/img/Spinner.png';
|
||||
|
||||
this.props.subscription.startApp('groups')
|
||||
this.props.subscription.startApp('chat')
|
||||
this.props.subscription.startApp('groups');
|
||||
this.props.subscription.startApp('chat');
|
||||
this.props.subscription.startApp('graph');
|
||||
}
|
||||
|
||||
|