mirror of
https://github.com/ilyakooo0/urbit.git
synced 2025-01-05 13:55:54 +03:00
spa: migrate publish fe to new architecture
This commit is contained in:
parent
de3f3ca246
commit
5bd022733b
@ -10,6 +10,7 @@ import DojoApp from './apps/dojo/DojoApp';
|
||||
import StatusBar from './components/StatusBar';
|
||||
import GroupsApp from './apps/groups/GroupsApp';
|
||||
import LinksApp from './apps/links/LinksApp';
|
||||
import PublishApp from './apps/publish/PublishApp';
|
||||
|
||||
// const Style = createGlobalStyle`
|
||||
// ${cssReset}
|
||||
@ -36,7 +37,7 @@ const Home = () => (
|
||||
<Link className="db" to='/~dojo'>Dojo</Link>
|
||||
<Link className="db" to='/~groups'>Groups</Link>
|
||||
<Link className="db" to='/~link'>Links</Link>
|
||||
{/* <Link to='/~publish'>Publish</Link> */}
|
||||
<Link className="db" to='~publish'>Publish</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -75,6 +76,10 @@ export default class App extends React.Component {
|
||||
p => <LinksApp ship={this.ship} channel={channel} {...p} />
|
||||
}
|
||||
/>
|
||||
<Route path="/~publish" render={
|
||||
p => <PublishApp ship={this.ship} channel={channel} {...p} />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Router>
|
||||
</Root>
|
||||
|
315
pkg/interface/src/apps/publish/PublishApp.js
Normal file
315
pkg/interface/src/apps/publish/PublishApp.js
Normal file
@ -0,0 +1,315 @@
|
||||
import React from 'react';
|
||||
import { Route } from 'react-router-dom';
|
||||
import _ from 'lodash';
|
||||
|
||||
import './css/custom.css';
|
||||
|
||||
import Api from './api';
|
||||
import Store from './store';
|
||||
import Subscription from './subscription';
|
||||
|
||||
import { Skeleton } from './components/skeleton';
|
||||
import { NewScreen } from './components/lib/new';
|
||||
import { JoinScreen } from './components/lib/join';
|
||||
import { Notebook } from './components/lib/notebook';
|
||||
import { Note } from './components/lib/note';
|
||||
import { NewPost } from './components/lib/new-post';
|
||||
import { EditPost } from './components/lib/edit-post';
|
||||
|
||||
export default class PublishApp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.store = new Store();
|
||||
this.store.setStateHandler(this.setState.bind(this));
|
||||
|
||||
this.state = this.store.state;
|
||||
this.unreadTotal = 0;
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
resetControllers() {
|
||||
this.api = null;
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.title = 'OS1 - Publish';
|
||||
|
||||
this.store.clear();
|
||||
const channel = new this.props.channel();
|
||||
this.api = new Api(this.props.ship, channel, this.store);
|
||||
|
||||
this.subscription = new Subscription(this.store, this.api, channel);
|
||||
this.subscription.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription.delete();
|
||||
this.store.clear();
|
||||
this.resetControllers();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
|
||||
const contacts = state.contacts ? state.contacts : {};
|
||||
const associations = state.associations ? state.associations : { contacts: {} };
|
||||
const selectedGroups = state.selectedGroups ? state.selectedGroups : [];
|
||||
|
||||
const unreadTotal = _.chain(state.notebooks)
|
||||
.values()
|
||||
.map(_.values)
|
||||
.flatten() // flatten into array of notebooks
|
||||
.map('num-unread')
|
||||
.reduce((acc, count) => acc + count, 0)
|
||||
.value();
|
||||
|
||||
if (this.unreadTotal !== unreadTotal) {
|
||||
window.title = unreadTotal > 0 ? `Publish - (${unreadTotal})` : 'Publish';
|
||||
this.unreadTotal = unreadTotal;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Route exact path="/~publish"
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={false}
|
||||
active={'sidebar'}
|
||||
rightPanelHide={true}
|
||||
sidebarShown={true}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
>
|
||||
<div className={`h-100 w-100 overflow-x-hidden flex flex-column
|
||||
bg-white bg-gray0-d dn db-ns`}
|
||||
>
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f9 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
Select or create a notebook to begin.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/new"
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={false}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
>
|
||||
<NewScreen
|
||||
associations={associations.contacts}
|
||||
notebooks={state.notebooks}
|
||||
groups={state.groups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/join/:ship?/:notebook?"
|
||||
render={(props) => {
|
||||
const ship = props.match.params.ship || '';
|
||||
const notebook = props.match.params.notebook || '';
|
||||
return (
|
||||
<Skeleton
|
||||
popout={false}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
>
|
||||
<JoinScreen
|
||||
notebooks={state.notebooks}
|
||||
ship={ship}
|
||||
notebook={notebook}
|
||||
api={this.api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/:popout?/notebook/:ship/:notebook/:view?"
|
||||
render={(props) => {
|
||||
const view = (props.match.params.view)
|
||||
? props.match.params.view
|
||||
: 'posts';
|
||||
|
||||
const popout = Boolean(props.match.params.popout) || false;
|
||||
|
||||
const ship = props.match.params.ship || '';
|
||||
const notebook = props.match.params.notebook || '';
|
||||
|
||||
const path = `${ship}/${notebook}`;
|
||||
|
||||
const bookGroupPath =
|
||||
state.notebooks[ship][notebook]['subscribers-group-path'];
|
||||
|
||||
const notebookContacts = (bookGroupPath in contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
|
||||
if (view === 'new') {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
path={path}
|
||||
api={this.api}
|
||||
>
|
||||
<NewPost
|
||||
notebooks={state.notebooks}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
selectedGroups={selectedGroups}
|
||||
path={path}
|
||||
api={this.api}
|
||||
>
|
||||
<Notebook
|
||||
notebooks={state.notebooks}
|
||||
view={view}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
groups={state.groups}
|
||||
contacts={contacts}
|
||||
notebookContacts={notebookContacts}
|
||||
associations={associations.contacts}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
permissions={state.permissions}
|
||||
api={this.api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/:popout?/note/:ship/:notebook/:note/:edit?"
|
||||
render={(props) => {
|
||||
const ship = props.match.params.ship || '';
|
||||
const notebook = props.match.params.notebook || '';
|
||||
const path = `${ship}/${notebook}`;
|
||||
const note = props.match.params.note || '';
|
||||
|
||||
const popout = Boolean(props.match.params.popout) || false;
|
||||
|
||||
const bookGroupPath =
|
||||
state.notebooks[ship][notebook]['subscribers-group-path'];
|
||||
const notebookContacts = (bookGroupPath in state.contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
|
||||
const edit = Boolean(props.match.params.edit) || false;
|
||||
|
||||
if (edit) {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
selectedGroups={selectedGroups}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
path={path}
|
||||
api={this.api}
|
||||
>
|
||||
<EditPost
|
||||
notebooks={state.notebooks}
|
||||
book={notebook}
|
||||
note={note}
|
||||
ship={ship}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={'rightPanel'}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
path={path}
|
||||
api={this.api}
|
||||
>
|
||||
<Note
|
||||
notebooks={state.notebooks}
|
||||
book={notebook}
|
||||
groups={state.groups}
|
||||
contacts={notebookContacts}
|
||||
ship={ship}
|
||||
note={note}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
api={this.api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,16 +1,17 @@
|
||||
import _ from 'lodash';
|
||||
import { store } from './store'
|
||||
|
||||
class UrbitApi {
|
||||
setAuthTokens(authTokens) {
|
||||
this.authTokens = authTokens;
|
||||
export default class Api {
|
||||
constructor(ship, channel, store) {
|
||||
this.ship = ship;
|
||||
this.channel = channel;
|
||||
this.store = store;
|
||||
this.bindPaths = [];
|
||||
}
|
||||
|
||||
bind(path, method, ship = this.authTokens.ship, appl = "publish", success, fail) {
|
||||
bind(path, method, ship = this.ship, appl = 'publish', success, fail) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.urb.subscribe(ship, appl, path,
|
||||
this.channel.subscribe(ship, appl, path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
@ -31,7 +32,7 @@ class UrbitApi {
|
||||
|
||||
action(appl, mark, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.urb.poke(ship, appl, mark, data,
|
||||
this.channel.poke(this.ship, appl, mark, data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
@ -44,86 +45,87 @@ class UrbitApi {
|
||||
// TODO add error handling
|
||||
|
||||
handleErrors(response) {
|
||||
if (!response.ok) throw Error(response.status);
|
||||
if (!response.ok)
|
||||
throw Error(response.status);
|
||||
return response;
|
||||
}
|
||||
|
||||
fetchNotebooks() {
|
||||
fetch('/~publish/notebooks.json')
|
||||
.then((response) => response.json())
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
store.handleEvent({
|
||||
this.store.handleEvent({
|
||||
type: 'notebooks',
|
||||
data: json,
|
||||
data: json
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotebook(host, book) {
|
||||
fetch(`/~publish/${host}/${book}.json`)
|
||||
.then((response) => response.json())
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
store.handleEvent({
|
||||
this.store.handleEvent({
|
||||
type: 'notebook',
|
||||
data: json,
|
||||
host: host,
|
||||
notebook: book,
|
||||
notebook: book
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchNote(host, book, note) {
|
||||
fetch(`/~publish/${host}/${book}/${note}.json`)
|
||||
.then((response) => response.json())
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
store.handleEvent({
|
||||
this.store.handleEvent({
|
||||
type: 'note',
|
||||
data: json,
|
||||
host: host,
|
||||
notebook: book,
|
||||
note: note,
|
||||
note: note
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotesPage(host, book, start, length) {
|
||||
fetch(`/~publish/notes/${host}/${book}/${start}/${length}.json`)
|
||||
.then((response) => response.json())
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
store.handleEvent({
|
||||
this.store.handleEvent({
|
||||
type: 'notes-page',
|
||||
data: json,
|
||||
host: host,
|
||||
notebook: book,
|
||||
startIndex: start,
|
||||
length: length,
|
||||
length: length
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchCommentsPage(host, book, note, start, length) {
|
||||
fetch(`/~publish/comments/${host}/${book}/${note}/${start}/${length}.json`)
|
||||
.then((response) => response.json())
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
store.handleEvent({
|
||||
this.store.handleEvent({
|
||||
type: 'comments-page',
|
||||
data: json,
|
||||
host: host,
|
||||
notebook: book,
|
||||
note: note,
|
||||
startIndex: start,
|
||||
length: length,
|
||||
length: length
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (store.state.sidebarShown === true) {
|
||||
if (this.store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
store.handleEvent({
|
||||
type: "local",
|
||||
this.store.handleEvent({
|
||||
type: 'local',
|
||||
data: {
|
||||
'sidebarToggle': sidebarBoolean
|
||||
}
|
||||
@ -131,15 +133,11 @@ class UrbitApi {
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
store.handleEvent({
|
||||
type: "local",
|
||||
this.store.handleEvent({
|
||||
type: 'local',
|
||||
data: {
|
||||
selected: selected
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export let api = new UrbitApi();
|
||||
window.api = api;
|
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
const CommentInput = React.forwardRef((props, ref) => (
|
||||
<textarea
|
||||
{...props}
|
||||
ref={ref}
|
||||
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', resize: 'vertical' }}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
(e.getModifierState('Control') || event.metaKey) &&
|
||||
e.key === 'Enter'
|
||||
) {
|
||||
props.onSubmit();
|
||||
}
|
||||
}}
|
||||
></textarea>
|
||||
));
|
||||
|
||||
CommentInput.displayName = 'commentInput';
|
||||
|
||||
export default CommentInput;
|
@ -1,8 +1,8 @@
|
||||
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 { Sigil } from '../../../../lib/sigil';
|
||||
import CommentInput from './comment-input';
|
||||
import { uxToHex, cite } from '../../../../lib/util';
|
||||
|
||||
export class CommentItem extends Component {
|
||||
constructor(props) {
|
@ -1,46 +1,45 @@
|
||||
import React, { Component } from 'react'
|
||||
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';
|
||||
import CommentInput from './comment-input';
|
||||
import { dateToDa } from '../../../../lib/util';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
|
||||
export class Comments extends Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
commentBody: '',
|
||||
pending: new Set(),
|
||||
awaiting: null,
|
||||
editing: null,
|
||||
}
|
||||
editing: null
|
||||
};
|
||||
this.commentSubmit = this.commentSubmit.bind(this);
|
||||
this.commentChange = this.commentChange.bind(this);
|
||||
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
let previousComments = prevProps.comments[0] || {};
|
||||
let currentComments = this.props.comments[0] || {};
|
||||
let previous = Object.keys(previousComments) || [];
|
||||
let current = Object.keys(currentComments) || [];
|
||||
const previousComments = prevProps.comments[0] || {};
|
||||
const currentComments = this.props.comments[0] || {};
|
||||
const previous = Object.keys(previousComments) || [];
|
||||
const current = Object.keys(currentComments) || [];
|
||||
if ((prevProps.comments && this.props.comments) &&
|
||||
(previous !== current)) {
|
||||
let pendingSet = this.state.pending;
|
||||
const pendingSet = this.state.pending;
|
||||
Object.keys(currentComments).map((com) => {
|
||||
let obj = currentComments[com];
|
||||
for (let each of pendingSet.values()) {
|
||||
if (obj.content === each["new-comment"].body) {
|
||||
const obj = currentComments[com];
|
||||
for (const each of pendingSet.values()) {
|
||||
if (obj.content === each['new-comment'].body) {
|
||||
pendingSet.delete(each);
|
||||
this.setState({ pending: pendingSet });
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
commentSubmit(evt){
|
||||
let comment = {
|
||||
"new-comment": {
|
||||
commentSubmit(evt) {
|
||||
const comment = {
|
||||
'new-comment': {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
@ -48,22 +47,22 @@ export class Comments extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
let pendingState = this.state.pending;
|
||||
const pendingState = this.state.pending;
|
||||
pendingState.add(comment);
|
||||
this.setState({pending: pendingState});
|
||||
this.setState({ pending: pendingState });
|
||||
|
||||
this.textArea.value = '';
|
||||
this.setState({commentBody: "", awaiting: 'new'});
|
||||
let submit = window.api.action("publish", "publish-action", comment);
|
||||
this.setState({ commentBody: '', awaiting: 'new' });
|
||||
const submit = this.props.api.action('publish', 'publish-action', comment);
|
||||
submit.then(() => {
|
||||
this.setState({ awaiting: null });
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
commentChange(evt){
|
||||
commentChange(evt) {
|
||||
this.setState({
|
||||
commentBody: evt.target.value,
|
||||
})
|
||||
commentBody: evt.target.value
|
||||
});
|
||||
}
|
||||
|
||||
commentEdit(idx) {
|
||||
@ -74,12 +73,10 @@ export class Comments extends Component {
|
||||
this.setState({ editing: null });
|
||||
}
|
||||
|
||||
|
||||
commentUpdate(idx, body) {
|
||||
|
||||
let path = Object.keys(this.props.comments[idx])[0];
|
||||
let comment = {
|
||||
"edit-comment": {
|
||||
const path = Object.keys(this.props.comments[idx])[0];
|
||||
const comment = {
|
||||
'edit-comment': {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
@ -88,17 +85,19 @@ export class Comments extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({ awaiting: 'edit' })
|
||||
this.setState({ awaiting: 'edit' });
|
||||
|
||||
window.api
|
||||
.action('publish', 'publish-action', comment)
|
||||
.then(() => { this.setState({ awaiting: null, editing: null }) })
|
||||
.then(() => {
|
||||
this.setState({ awaiting: null, editing: null });
|
||||
});
|
||||
}
|
||||
|
||||
commentDelete(idx) {
|
||||
let path = Object.keys(this.props.comments[idx])[0];
|
||||
let comment = {
|
||||
"del-comment": {
|
||||
const path = Object.keys(this.props.comments[idx])[0];
|
||||
const comment = {
|
||||
'del-comment': {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
@ -106,11 +105,12 @@ export class Comments extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({ awaiting: { kind: 'del', what: idx }})
|
||||
this.setState({ awaiting: { kind: 'del', what: idx } });
|
||||
window.api
|
||||
.action('publish', 'publish-action', comment)
|
||||
.then(() => { this.setState({ awaiting: null }) })
|
||||
|
||||
.then(() => {
|
||||
this.setState({ awaiting: null });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -120,15 +120,15 @@ export class Comments extends Component {
|
||||
|
||||
const { editing } = this.state;
|
||||
|
||||
let pendingArray = Array.from(this.state.pending).map((com, i) => {
|
||||
let da = dateToDa(new Date);
|
||||
let comment = {
|
||||
const pendingArray = Array.from(this.state.pending).map((com, i) => {
|
||||
const da = dateToDa(new Date());
|
||||
const comment = {
|
||||
[da]: {
|
||||
author: `~${window.ship}`,
|
||||
content: com["new-comment"].body,
|
||||
"date-created": Math.round(new Date().getTime())
|
||||
content: com['new-comment'].body,
|
||||
'date-created': Math.round(new Date().getTime())
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<CommentItem
|
||||
comment={comment}
|
||||
@ -136,10 +136,10 @@ export class Comments extends Component {
|
||||
contacts={this.props.contacts}
|
||||
pending={true}
|
||||
/>
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
let commentArray = this.props.comments.map((com, i) => {
|
||||
const commentArray = this.props.comments.map((com, i) => {
|
||||
return (
|
||||
<CommentItem
|
||||
comment={com}
|
||||
@ -150,17 +150,17 @@ export class Comments extends Component {
|
||||
onEdit={() => this.commentEdit(i)}
|
||||
onEditCancel={this.commentEditCancel.bind(this)}
|
||||
editing={i === editing}
|
||||
disabled={!!this.state.awaiting || editing}
|
||||
/>
|
||||
disabled={Boolean(this.state.awaiting) || editing}
|
||||
/>
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
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";
|
||||
const disableComment = ((this.state.commentBody === '') || (Boolean(this.state.awaiting)));
|
||||
const 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 =
|
||||
const spinnerText =
|
||||
this.state.awaiting === 'new'
|
||||
? 'Posting commment...'
|
||||
: this.state.awaiting === 'edit'
|
||||
@ -171,26 +171,30 @@ export class Comments extends Component {
|
||||
<div>
|
||||
<div className="mv8 relative">
|
||||
<div>
|
||||
<CommentInput style={{resize:'vertical'}}
|
||||
ref={(el) => {this.textArea = el}}
|
||||
<CommentInput style={{ resize:'vertical' }}
|
||||
ref={(el) => {
|
||||
this.textArea = el;
|
||||
}}
|
||||
onChange={this.commentChange}
|
||||
value={this.state.commentBody}
|
||||
disabled={!!this.state.editing}
|
||||
onSubmit={this.commentSubmit}>
|
||||
disabled={Boolean(this.state.editing)}
|
||||
onSubmit={this.commentSubmit}
|
||||
>
|
||||
</CommentInput>
|
||||
</div>
|
||||
<button disabled={disableComment}
|
||||
onClick={this.commentSubmit}
|
||||
className={commentClass}>
|
||||
className={commentClass}
|
||||
>
|
||||
Add comment
|
||||
</button>
|
||||
<Spinner text={spinnerText} awaiting={this.state.awaiting} classes="absolute bottom-0 right-0 pb2"/>
|
||||
<Spinner text={spinnerText} awaiting={this.state.awaiting} classes="absolute bottom-0 right-0 pb2" />
|
||||
</div>
|
||||
{pendingArray}
|
||||
{commentArray}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Comments
|
||||
export default Comments;
|
@ -1,5 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export class Dropdown extends Component {
|
||||
constructor(props) {
|
||||
@ -10,7 +9,7 @@ export class Dropdown extends Component {
|
||||
this.collapseAndDispatch = this.collapseAndDispatch.bind(this);
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -24,60 +23,67 @@ export class Dropdown extends Component {
|
||||
handleClickOutside(evt) {
|
||||
if (this.optsList && !this.optsList.contains(evt.target) &&
|
||||
this.optsButton && !this.optsButton.contains(evt.target)) {
|
||||
this.setState({open: false});
|
||||
this.setState({ open: false });
|
||||
}
|
||||
}
|
||||
|
||||
toggleDropdown() {
|
||||
this.setState({open: !this.state.open});
|
||||
this.setState({ open: !this.state.open });
|
||||
}
|
||||
|
||||
collapseAndDispatch(action){
|
||||
this.setState({open: false}, action);
|
||||
collapseAndDispatch(action) {
|
||||
this.setState({ open: false }, action);
|
||||
}
|
||||
|
||||
render() {
|
||||
let alignment = (!!this.props.align)
|
||||
? this.props.align : "right";
|
||||
const alignment = (this.props.align)
|
||||
? this.props.align : 'right';
|
||||
|
||||
let display = (this.state.open)
|
||||
? "block" : "none";
|
||||
const display = (this.state.open)
|
||||
? 'block' : 'none';
|
||||
|
||||
let optionsClass = (this.state.open)
|
||||
? "open" : "closed";
|
||||
const optionsClass = (this.state.open)
|
||||
? 'open' : 'closed';
|
||||
|
||||
let leftAlign = "";
|
||||
let rightAlign = "0";
|
||||
let leftAlign = '';
|
||||
let rightAlign = '0';
|
||||
|
||||
if (alignment === "left") {
|
||||
leftAlign = "0"
|
||||
rightAlign = ""
|
||||
if (alignment === 'left') {
|
||||
leftAlign = '0';
|
||||
rightAlign = '';
|
||||
}
|
||||
|
||||
let optionsList = this.props.options.map((val, i) => {
|
||||
const optionsList = this.props.options.map((val, i) => {
|
||||
return (
|
||||
<button key={i} className={val.cls}
|
||||
onClick={() => this.collapseAndDispatch(val.action)}>
|
||||
onClick={() => this.collapseAndDispatch(val.action)}
|
||||
>
|
||||
{val.txt}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={"options relative dib pr3 pointer " + optionsClass}
|
||||
ref={(el) => {this.optsButton = el}}
|
||||
onClick={this.toggleDropdown}>
|
||||
<div className={'options relative dib pr3 pointer ' + optionsClass}
|
||||
ref={(el) => {
|
||||
this.optsButton = el;
|
||||
}}
|
||||
onClick={this.toggleDropdown}
|
||||
>
|
||||
<button className="bg-transparent white-d pointer mb1 br2 pa2 pr4">
|
||||
{this.props.buttonText}
|
||||
</button>
|
||||
<div className="absolute flex flex-column pv2 ba b--gray4 br2 z-1 bg-white bg-gray0-d"
|
||||
ref={(el) => {this.optsList = el}}
|
||||
style={{left: leftAlign, right: rightAlign, width:this.props.width, display: display}}>
|
||||
ref={(el) => {
|
||||
this.optsList = el;
|
||||
}}
|
||||
style={{ left: leftAlign, right: rightAlign, width:this.props.width, display: display }}
|
||||
>
|
||||
{optionsList}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Dropdown
|
||||
export default Dropdown;
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { SidebarSwitcher } from '../../../../components/SidebarSwitch';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import { dateToDa } from '/lib/util';
|
||||
import { dateToDa } from '../../../../lib/util';
|
||||
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
|
||||
@ -14,7 +14,7 @@ export class EditPost extends Component {
|
||||
body: '',
|
||||
submit: false,
|
||||
awaiting: false
|
||||
}
|
||||
};
|
||||
this.postSubmit = this.postSubmit.bind(this);
|
||||
this.bodyChange = this.bodyChange.bind(this);
|
||||
}
|
||||
@ -25,63 +25,62 @@ export class EditPost extends Component {
|
||||
!(props.notebooks[props.ship][props.book]) ||
|
||||
!(props.notebooks[props.ship][props.book].notes[props.note]) ||
|
||||
!(props.notebooks[props.ship][props.book].notes[props.note].file)) {
|
||||
window.api.fetchNote(props.ship, props.book, props.note);
|
||||
}
|
||||
else {
|
||||
let notebook = props.notebooks[props.ship][props.book];
|
||||
let note = notebook.notes[props.note];
|
||||
let file = note.file;
|
||||
let body = file.slice(file.indexOf(';>') + 3);
|
||||
this.setState({body: body});
|
||||
this.props.api.fetchNote(props.ship, props.book, props.note);
|
||||
} else {
|
||||
const notebook = props.notebooks[props.ship][props.book];
|
||||
const note = notebook.notes[props.note];
|
||||
const file = note.file;
|
||||
const body = file.slice(file.indexOf(';>') + 3);
|
||||
this.setState({ body: body });
|
||||
}
|
||||
}
|
||||
|
||||
postSubmit() {
|
||||
let { props, state } = this;
|
||||
let notebook = props.notebooks[props.ship][props.book];
|
||||
let note = notebook.notes[props.note];
|
||||
let title = note.title;
|
||||
let editNote = {
|
||||
"edit-note": {
|
||||
const { props, state } = this;
|
||||
const notebook = props.notebooks[props.ship][props.book];
|
||||
const note = notebook.notes[props.note];
|
||||
const title = note.title;
|
||||
const editNote = {
|
||||
'edit-note': {
|
||||
who: props.ship.slice(1),
|
||||
book: props.book,
|
||||
note: props.note,
|
||||
title: title,
|
||||
body: state.body
|
||||
}
|
||||
}
|
||||
this.setState({awaiting: true});
|
||||
window.api.action("publish", "publish-action", editNote).then(() => {
|
||||
let editIndex = props.location.pathname.indexOf("/edit");
|
||||
let noteHref = props.location.pathname.slice(0, editIndex);
|
||||
this.setState({awaiting: false});
|
||||
};
|
||||
this.setState({ awaiting: true });
|
||||
this.props.api.action('publish', 'publish-action', editNote).then(() => {
|
||||
const editIndex = props.location.pathname.indexOf('/edit');
|
||||
const noteHref = props.location.pathname.slice(0, editIndex);
|
||||
this.setState({ awaiting: false });
|
||||
props.history.push(noteHref);
|
||||
});
|
||||
}
|
||||
|
||||
bodyChange(editor, data, value) {
|
||||
let submit = !(value === '');
|
||||
const submit = !(value === '');
|
||||
this.setState({ body: value, submit: submit });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let notebook = props.notebooks[props.ship][props.book];
|
||||
let note = notebook.notes[props.note];
|
||||
let title = note.title;
|
||||
let date = dateToDa(new Date(note["date-created"]));
|
||||
const notebook = props.notebooks[props.ship][props.book];
|
||||
const note = notebook.notes[props.note];
|
||||
const title = note.title;
|
||||
let date = dateToDa(new Date(note['date-created']));
|
||||
date = date.slice(1, -10);
|
||||
|
||||
let submitStyle = (state.submit)
|
||||
? { color: '#2AA779', cursor: "pointer" }
|
||||
: { color: '#B1B2B3', cursor: "auto" };
|
||||
const submitStyle = (state.submit)
|
||||
? { color: '#2AA779', cursor: 'pointer' }
|
||||
: { color: '#B1B2B3', cursor: 'auto' };
|
||||
|
||||
let hrefIndex = props.location.pathname.indexOf("/note/");
|
||||
let publishsubStr = props.location.pathname.substr(hrefIndex)
|
||||
let popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
const hrefIndex = props.location.pathname.indexOf('/note/');
|
||||
const publishsubStr = props.location.pathname.substr(hrefIndex);
|
||||
const popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
|
||||
let hiddenOnPopout = (props.popout)
|
||||
? "" : "dib-m dib-l dib-xl";
|
||||
const hiddenOnPopout = (props.popout)
|
||||
? '' : 'dib-m dib-l dib-xl';
|
||||
|
||||
const options = {
|
||||
mode: 'markdown',
|
||||
@ -98,18 +97,21 @@ export class EditPost extends Component {
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<button
|
||||
className="v-mid bg-transparent w-100 w-80-m w-90-l mw6 tl h1 pl4"
|
||||
disabled={!state.submit}
|
||||
style={submitStyle}
|
||||
onClick={this.postSubmit}>
|
||||
onClick={this.postSubmit}
|
||||
>
|
||||
Save "{title}"
|
||||
</button>
|
||||
<Link
|
||||
className={"dn absolute right-1 top-1 " + hiddenOnPopout}
|
||||
className={'dn absolute right-1 top-1 ' + hiddenOnPopout}
|
||||
to={popoutHref}
|
||||
target="_blank">
|
||||
target="_blank"
|
||||
>
|
||||
<img src="/~publish/popout.png"
|
||||
height={16}
|
||||
width={16}
|
||||
@ -127,12 +129,12 @@ export class EditPost extends Component {
|
||||
onBeforeChange={(e, d, v) => this.bodyChange(e, d, v)}
|
||||
onChange={(editor, data, value) => {}}
|
||||
/>
|
||||
<Spinner text="Editing post..." awaiting={this.state.awaiting} classes="absolute bottom-1 right-1 ba b--gray1-d pa2"/>
|
||||
<Spinner text="Editing post..." awaiting={this.state.awaiting} classes="absolute bottom-1 right-1 ba b--gray1-d pa2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditPost;
|
||||
export default EditPost;
|
@ -3,23 +3,23 @@ import { NotebookItem } from './notebook-item';
|
||||
|
||||
export class GroupItem extends Component {
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let association = !!props.association ? props.association : {};
|
||||
const { props } = this;
|
||||
const association = props.association ? props.association : {};
|
||||
|
||||
let title = association["app-path"] ? association["app-path"] : "Unmanaged Notebooks";
|
||||
let title = association['app-path'] ? association['app-path'] : 'Unmanaged Notebooks';
|
||||
if (association.metadata && association.metadata.title) {
|
||||
title = association.metadata.title !== ""
|
||||
title = association.metadata.title !== ''
|
||||
? association.metadata.title : title;
|
||||
}
|
||||
|
||||
let groupedBooks = !!props.groupedBooks ? props.groupedBooks : [];
|
||||
let first = (props.index === 0) ? "pt1" : "pt4";
|
||||
const groupedBooks = props.groupedBooks ? props.groupedBooks : [];
|
||||
const first = (props.index === 0) ? 'pt1' : 'pt4';
|
||||
|
||||
let notebookItems = groupedBooks.map((each, i) => {
|
||||
let unreads = props.notebooks[each]["num-unread"] || 0;
|
||||
const notebookItems = groupedBooks.map((each, i) => {
|
||||
const unreads = props.notebooks[each]['num-unread'] || 0;
|
||||
let title = each.substr(1);
|
||||
if (props.notebooks[each].title) {
|
||||
title = (props.notebooks[each].title !== "")
|
||||
title = (props.notebooks[each].title !== '')
|
||||
? props.notebooks[each].title : title;
|
||||
}
|
||||
return (
|
||||
@ -30,15 +30,15 @@ export class GroupItem extends Component {
|
||||
path={each}
|
||||
selected={(props.path === each)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className={first}>
|
||||
<p className="f9 ph4 pb2 fw6 gray3">{title}</p>
|
||||
{notebookItems}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupItem;
|
||||
export default GroupItem;
|
@ -1,7 +1,6 @@
|
||||
import React, { Component } from 'react'
|
||||
import classnames from 'classnames';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
export class JoinScreen extends Component {
|
||||
@ -21,10 +20,10 @@ export class JoinScreen extends Component {
|
||||
componentDidMount() {
|
||||
// direct join from incoming URL
|
||||
if ((this.props.ship) && (this.props.notebook)) {
|
||||
let incomingBook = `${this.props.ship}/${this.props.notebook}`;
|
||||
this.setState({book: incomingBook}, () => {
|
||||
const incomingBook = `${this.props.ship}/${this.props.notebook}`;
|
||||
this.setState({ book: incomingBook }, () => {
|
||||
this.onClickJoin();
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,13 +31,13 @@ export class JoinScreen extends Component {
|
||||
// redirect to notebook when we have it
|
||||
if (this.props.notebooks) {
|
||||
if (this.state.awaiting) {
|
||||
let book = this.state.awaiting.split("/");
|
||||
let ship = book[0];
|
||||
let notebook = book[1];
|
||||
const book = this.state.awaiting.split('/');
|
||||
const ship = book[0];
|
||||
const notebook = book[1];
|
||||
if ((ship in this.props.notebooks) &&
|
||||
(notebook in this.props.notebooks[ship])) {
|
||||
this.setState({disable: false, book: "/"});
|
||||
this.props.history.push(`/~publish/notebook/${ship}/${notebook}`)
|
||||
this.setState({ disable: false, book: '/' });
|
||||
this.props.history.push(`/~publish/notebook/${ship}/${notebook}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,40 +65,39 @@ export class JoinScreen extends Component {
|
||||
onClickJoin() {
|
||||
const { props, state } = this;
|
||||
|
||||
let text = state.book;
|
||||
const text = state.book;
|
||||
|
||||
let book = text.split('/');
|
||||
let ship = book[0];
|
||||
const ship = book[0];
|
||||
book.splice(0, 1);
|
||||
book = '/' + book.join('/');
|
||||
|
||||
if (this.notebooksInclude(state.book, props.notebooks)) {
|
||||
let href = `/~publish/notebook/${ship}${book}`
|
||||
const href = `/~publish/notebook/${ship}${book}`;
|
||||
return props.history.push(href);
|
||||
}
|
||||
|
||||
if (book.length < 2 || !urbitOb.isValidPatp(ship)) {
|
||||
this.setState({
|
||||
error: true,
|
||||
error: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let actionData = {
|
||||
const actionData = {
|
||||
subscribe: {
|
||||
who: ship.replace('~',''),
|
||||
book: /\/?(.*)/.exec(book)[1]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: askHistory setting
|
||||
this.setState({disable: true});
|
||||
window.api.action("publish","publish-action", actionData).catch((err) => {
|
||||
console.log(err)
|
||||
this.setState({ disable: true });
|
||||
this.props.api.action('publish','publish-action', actionData).catch((err) => {
|
||||
console.log(err);
|
||||
}).then(() => {
|
||||
this.setState({awaiting: text})
|
||||
this.setState({ awaiting: text });
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
bookChange(event) {
|
||||
@ -109,10 +107,10 @@ export class JoinScreen extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const { state } = this;
|
||||
|
||||
let joinClasses = "db f9 green2 ba pa2 b--green2 bg-gray0-d pointer";
|
||||
if ((state.disable) || (!state.book) || (state.book === "/")) {
|
||||
let joinClasses = 'db f9 green2 ba pa2 b--green2 bg-gray0-d pointer';
|
||||
if ((state.disable) || (!state.book) || (state.book === '/')) {
|
||||
joinClasses = 'db f9 gray2 ba pa2 b--gray3 bg-gray0-d';
|
||||
}
|
||||
|
||||
@ -126,39 +124,43 @@ export class JoinScreen extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"h-100 w-100 pt4 overflow-x-hidden flex flex-column " +
|
||||
"bg-gray0-d white-d pa3"}>
|
||||
<div className={'h-100 w-100 pt4 overflow-x-hidden flex flex-column ' +
|
||||
'bg-gray0-d white-d pa3'}
|
||||
>
|
||||
<div
|
||||
className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
|
||||
<Link to="/~publish/">{"⟵ All Notebooks"}</Link>
|
||||
className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8"
|
||||
>
|
||||
<Link to="/~publish/">{'⟵ All Notebooks'}</Link>
|
||||
</div>
|
||||
<h2 className="mb3 f8">Subscribe to an Existing Notebook</h2>
|
||||
<div className="w-100">
|
||||
<p className="f8 lh-copy mt3 db">Enter a <span className="mono">~ship/notebook-name</span></p>
|
||||
<p className="f9 gray2 mb4">Notebook names use lowercase, hyphens, and slashes.</p>
|
||||
<textarea
|
||||
ref={ e => { this.textarea = e; } }
|
||||
className={"f7 mono ba bg-gray0-d white-d pa3 mb2 db " +
|
||||
"focus-b--black focus-b--white-d b--gray3 b--gray2-d nowrap "}
|
||||
ref={ (e) => {
|
||||
this.textarea = e;
|
||||
} }
|
||||
className={'f7 mono ba bg-gray0-d white-d pa3 mb2 db ' +
|
||||
'focus-b--black focus-b--white-d b--gray3 b--gray2-d nowrap '}
|
||||
placeholder="~zod/dream-journal"
|
||||
spellCheck="false"
|
||||
rows={1}
|
||||
onKeyPress={e => {
|
||||
if (e.key === "Enter") {
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.onClickJoin();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
resize: 'none',
|
||||
resize: 'none'
|
||||
}}
|
||||
onChange={this.bookChange}
|
||||
value={this.state.book}
|
||||
/>
|
||||
/>
|
||||
{errElem}
|
||||
<br />
|
||||
<button
|
||||
disabled={(this.state.disable) || (!state.book) || (state.book === "/")}
|
||||
disabled={(this.state.disable) || (!state.book) || (state.book === '/')}
|
||||
onClick={this.onClickJoin.bind(this)}
|
||||
className={joinClasses}
|
||||
>Join Notebook</button>
|
||||
@ -169,4 +171,4 @@ export class JoinScreen extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default JoinScreen;
|
||||
export default JoinScreen;
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react'
|
||||
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2'
|
||||
import { dateToDa, stringToSymbol } from '/lib/util';
|
||||
import React, { Component } from 'react';
|
||||
import { SidebarSwitcher } from '../../../../components/SidebarSwitch';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import { dateToDa, stringToSymbol } from '../../../../lib/util';
|
||||
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
|
||||
@ -16,7 +16,7 @@ export class NewPost extends Component {
|
||||
submit: false,
|
||||
awaiting: null,
|
||||
disabled: false
|
||||
}
|
||||
};
|
||||
|
||||
this.postSubmit = this.postSubmit.bind(this);
|
||||
this.titleChange = this.titleChange.bind(this);
|
||||
@ -24,63 +24,63 @@ export class NewPost extends Component {
|
||||
}
|
||||
|
||||
postSubmit() {
|
||||
const { state, props } = this;
|
||||
const { state } = this;
|
||||
if (state.submit && !state.disabled) {
|
||||
let newNote = {
|
||||
"new-note": {
|
||||
const newNote = {
|
||||
'new-note': {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: stringToSymbol(this.state.title),
|
||||
title: this.state.title,
|
||||
body: this.state.body,
|
||||
body: this.state.body
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({ disabled: true });
|
||||
window.api.action("publish", "publish-action", newNote).then(() => {
|
||||
this.props.api.action('publish', 'publish-action', newNote).then(() => {
|
||||
this.setState({ awaiting: newNote['new-note'].note });
|
||||
}).catch((err) => {
|
||||
if (err.includes("note already exists")) {
|
||||
let timestamp = Math.floor(Date.now() / 1000);
|
||||
newNote["new-note"].note += "-" + timestamp;
|
||||
if (err.includes('note already exists')) {
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
newNote['new-note'].note += '-' + timestamp;
|
||||
this.setState({ awaiting: newNote['new-note'].note });
|
||||
window.api.action("publish", "publish-action", newNote);
|
||||
this.props.api.action('publish', 'publish-action', newNote);
|
||||
} else {
|
||||
this.setState({ disabled: false, awaiting: null })
|
||||
this.setState({ disabled: false, awaiting: null });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
window.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
this.props.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
componentDidUpdate() {
|
||||
const notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
if (notebook.notes[this.state.awaiting]) {
|
||||
this.setState({ disabled: false, awaiting: null });
|
||||
let popout = (this.props.popout) ? "popout/" : "";
|
||||
let redirect =
|
||||
const popout = (this.props.popout) ? 'popout/' : '';
|
||||
const redirect =
|
||||
`/~publish/${popout}note/${this.props.ship}/${this.props.book}/${this.state.awaiting}`;
|
||||
this.props.history.push(redirect);
|
||||
}
|
||||
}
|
||||
|
||||
titleChange(evt) {
|
||||
let submit = !(evt.target.value === '' || this.state.body === '');
|
||||
this.setState({title: evt.target.value, submit: submit});
|
||||
const submit = !(evt.target.value === '' || this.state.body === '');
|
||||
this.setState({ title: evt.target.value, submit: submit });
|
||||
}
|
||||
|
||||
bodyChange(editor, data, value) {
|
||||
let submit = !(value === '' || this.state.title === '');
|
||||
this.setState({body: value, submit: submit});
|
||||
const submit = !(value === '' || this.state.title === '');
|
||||
this.setState({ body: value, submit: submit });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let notebook = props.notebooks[props.ship][props.book];
|
||||
const notebook = props.notebooks[props.ship][props.book];
|
||||
|
||||
const options = {
|
||||
mode: 'markdown',
|
||||
@ -91,42 +91,45 @@ export class NewPost extends Component {
|
||||
cursorHeight: 0.85
|
||||
};
|
||||
|
||||
let date = dateToDa(new Date()).slice(1, -10);
|
||||
const date = dateToDa(new Date()).slice(1, -10);
|
||||
|
||||
let submitStyle = ((!state.disabled && state.submit) && (state.awaiting === null))
|
||||
? { color: '#2AA779', cursor: "pointer" }
|
||||
: { color: '#B1B2B3', cursor: "auto" };
|
||||
const submitStyle = ((!state.disabled && state.submit) && (state.awaiting === null))
|
||||
? { color: '#2AA779', cursor: 'pointer' }
|
||||
: { color: '#B1B2B3', cursor: 'auto' };
|
||||
|
||||
let hrefIndex = props.location.pathname.indexOf("/notebook/");
|
||||
let publishsubStr = props.location.pathname.substr(hrefIndex)
|
||||
let popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
const hrefIndex = props.location.pathname.indexOf('/notebook/');
|
||||
const publishsubStr = props.location.pathname.substr(hrefIndex);
|
||||
const popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
|
||||
let hiddenOnPopout = (props.popout)
|
||||
? "" : "dib-m dib-l dib-xl";
|
||||
const hiddenOnPopout = (props.popout)
|
||||
? '' : 'dib-m dib-l dib-xl';
|
||||
|
||||
let newIndex = props.location.pathname.indexOf("/new");
|
||||
let backHref = props.location.pathname.slice(0, newIndex);
|
||||
const newIndex = props.location.pathname.indexOf('/new');
|
||||
const backHref = props.location.pathname.slice(0, newIndex);
|
||||
return (
|
||||
<div className="f9 h-100 relative">
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt4 pb4 f9 pl4">
|
||||
<Link to={backHref}>{"<- Back"}</Link>
|
||||
<Link to={backHref}>{'<- Back'}</Link>
|
||||
</div>
|
||||
<div className="w-100 tl pv4 flex justify-center">
|
||||
<SidebarSwitcher
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<button
|
||||
className={"bg-transparent v-mid w-100 w-90-l w-80-m mw6 tl h1 pl4"}
|
||||
className={'bg-transparent v-mid w-100 w-90-l w-80-m mw6 tl h1 pl4'}
|
||||
disabled={(!state.submit && state.disabled) || (state.awaiting !== null)}
|
||||
style={submitStyle}
|
||||
onClick={this.postSubmit}>
|
||||
onClick={this.postSubmit}
|
||||
>
|
||||
Publish To {notebook.title}
|
||||
</button>
|
||||
<Link
|
||||
className={"dn absolute right-1 top-1 " + hiddenOnPopout}
|
||||
className={'dn absolute right-1 top-1 ' + hiddenOnPopout}
|
||||
to={popoutHref}
|
||||
target="_blank">
|
||||
target="_blank"
|
||||
>
|
||||
<img src="/~publish/popout.png"
|
||||
height={16}
|
||||
width={16}
|
@ -1,9 +1,8 @@
|
||||
import React, { Component } from 'react';
|
||||
import { InviteSearch } from './invite-search';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { uuid, isPatTa, deSig, stringToSymbol } from "/lib/util";
|
||||
import urbitOb from 'urbit-ob';
|
||||
import { InviteSearch } from '../../../../components/InviteSearch';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { stringToSymbol } from '../../../../lib/util';
|
||||
|
||||
export class NewScreen extends Component {
|
||||
constructor(props) {
|
||||
@ -18,7 +17,7 @@ export class NewScreen extends Component {
|
||||
},
|
||||
disabled: false,
|
||||
createGroup: false,
|
||||
awaiting: false,
|
||||
awaiting: false
|
||||
};
|
||||
|
||||
this.idChange = this.idChange.bind(this);
|
||||
@ -29,10 +28,10 @@ export class NewScreen extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props, state } = this;
|
||||
if (props.notebooks && (("~" + window.ship) in props.notebooks)) {
|
||||
if (state.awaiting in props.notebooks["~" + window.ship]) {
|
||||
let notebook = `/~${window.ship}/${state.awaiting}`;
|
||||
props.history.push("/~publish/notebook" + notebook);
|
||||
if (props.notebooks && (('~' + window.ship) in props.notebooks)) {
|
||||
if (state.awaiting in props.notebooks['~' + window.ship]) {
|
||||
const notebook = `/~${window.ship}/${state.awaiting}`;
|
||||
props.history.push('/~publish/notebook' + notebook);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -50,72 +49,72 @@ export class NewScreen extends Component {
|
||||
}
|
||||
|
||||
createGroupChange(event) {
|
||||
this.setState({createGroup: !!event.target.checked});
|
||||
this.setState({ createGroup: Boolean(event.target.checked) });
|
||||
}
|
||||
|
||||
setInvite(value) {
|
||||
this.setState({invites: value})
|
||||
this.setState({ invites: value });
|
||||
}
|
||||
|
||||
onClickCreate() {
|
||||
const { props, state } = this;
|
||||
let bookId = stringToSymbol(state.idName);
|
||||
const bookId = stringToSymbol(state.idName);
|
||||
let groupInfo = null;
|
||||
if (state.invites.groups.length > 0) {
|
||||
groupInfo = {
|
||||
"group-path": state.invites.groups[0],
|
||||
"invitees": [],
|
||||
"use-preexisting": true,
|
||||
"make-managed": false,
|
||||
'group-path': state.invites.groups[0],
|
||||
'invitees': [],
|
||||
'use-preexisting': true,
|
||||
'make-managed': false
|
||||
};
|
||||
} else if (this.state.createGroup) {
|
||||
groupInfo = {
|
||||
"group-path": `/~${window.ship}/${bookId}`,
|
||||
"invitees": state.invites.ships,
|
||||
"use-preexisting": false,
|
||||
"make-managed": true,
|
||||
'group-path': `/~${window.ship}/${bookId}`,
|
||||
'invitees': state.invites.ships,
|
||||
'use-preexisting': false,
|
||||
'make-managed': true
|
||||
};
|
||||
} else {
|
||||
groupInfo = {
|
||||
"group-path": `/~/~${window.ship}/${bookId}`,
|
||||
"invitees": state.invites.ships,
|
||||
"use-preexisting": false,
|
||||
"make-managed": false,
|
||||
'group-path': `/~/~${window.ship}/${bookId}`,
|
||||
'invitees': state.invites.ships,
|
||||
'use-preexisting': false,
|
||||
'make-managed': false
|
||||
};
|
||||
}
|
||||
|
||||
let action = {
|
||||
"new-book": {
|
||||
const action = {
|
||||
'new-book': {
|
||||
book: bookId,
|
||||
title: state.idName,
|
||||
about: state.description,
|
||||
coms: true,
|
||||
group: groupInfo
|
||||
}
|
||||
}
|
||||
this.setState({awaiting: bookId, disabled: true}, () => {
|
||||
props.api.action("publish", "publish-action", action).then(() => {
|
||||
};
|
||||
this.setState({ awaiting: bookId, disabled: true }, () => {
|
||||
props.api.action('publish', 'publish-action', action).then(() => {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let createGroupClasses = this.state.createGroup
|
||||
? "relative checked bg-green2 br3 h1 toggle v-mid z-0"
|
||||
: "relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0";
|
||||
const createGroupClasses = this.state.createGroup
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
let createClasses = "pointer db f9 green2 bg-gray0-d ba pv3 ph4 mv7 b--green2";
|
||||
let createClasses = 'pointer db f9 green2 bg-gray0-d ba pv3 ph4 mv7 b--green2';
|
||||
if (!this.state.idName || this.state.disabled) {
|
||||
createClasses = "db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 mv7 b--gray3";
|
||||
createClasses = 'db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 mv7 b--gray3';
|
||||
}
|
||||
|
||||
let createGroupToggle =
|
||||
const createGroupToggle =
|
||||
!((this.state.invites.ships.length > 0) && (this.state.invites.groups.length === 0))
|
||||
? null
|
||||
: <div className="mv7">
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ WebkitAppearance: "none", width: 28 }}
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
className={createGroupClasses}
|
||||
onChange={this.createGroupChange}
|
||||
/>
|
||||
@ -125,7 +124,6 @@ export class NewScreen extends Component {
|
||||
</p>
|
||||
</div>;
|
||||
|
||||
|
||||
let idErrElem = <span />;
|
||||
if (this.state.idError) {
|
||||
idErrElem = (
|
||||
@ -138,10 +136,11 @@ export class NewScreen extends Component {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"h-100 w-100 mw6 pa3 pt4 overflow-x-hidden flex flex-column white-d"
|
||||
}>
|
||||
'h-100 w-100 mw6 pa3 pt4 overflow-x-hidden flex flex-column white-d'
|
||||
}
|
||||
>
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
|
||||
<Link to="/~publish/">{"⟵ All Notebooks"}</Link>
|
||||
<Link to="/~publish/">{'⟵ All Notebooks'}</Link>
|
||||
</div>
|
||||
<h2 className="mb3 f8">New Notebook</h2>
|
||||
<div className="w-100">
|
||||
@ -150,12 +149,14 @@ export class NewScreen extends Component {
|
||||
Provide a name for your notebook
|
||||
</p>
|
||||
<textarea
|
||||
className={"f7 ba bg-gray0-d white-d pa3 db w-100 " +
|
||||
"focus-b--black focus-b--white-d b--gray3 b--gray2-d"}
|
||||
className={
|
||||
'f7 ba bg-gray0-d white-d pa3 db w-100 ' +
|
||||
'focus-b--black focus-b--white-d b--gray3 b--gray2-d'
|
||||
}
|
||||
placeholder="eg. My Journal"
|
||||
rows={1}
|
||||
style={{
|
||||
resize: "none"
|
||||
resize: 'none'
|
||||
}}
|
||||
onChange={this.idChange}
|
||||
value={this.state.idName}
|
||||
@ -165,25 +166,30 @@ export class NewScreen extends Component {
|
||||
Description
|
||||
<span className="gray3 ml1">(Optional)</span>
|
||||
</p>
|
||||
<p className="f9 gray2 db mb2 pt1">What's your notebook about?</p>
|
||||
<p className="f9 gray2 db mb2 pt1">
|
||||
What's your notebook about?
|
||||
</p>
|
||||
<textarea
|
||||
className={"f7 ba bg-gray0-d white-d pa3 db w-100 " +
|
||||
"focus-b--black focus-b--white-d b--gray3 b--gray2-d"}
|
||||
className={
|
||||
'f7 ba bg-gray0-d white-d pa3 db w-100 ' +
|
||||
'focus-b--black focus-b--white-d b--gray3 b--gray2-d'
|
||||
}
|
||||
placeholder="Notebook description"
|
||||
rows={1}
|
||||
style={{
|
||||
resize: "none"
|
||||
resize: 'none'
|
||||
}}
|
||||
onChange={this.descriptionChange}
|
||||
value={this.state.description}
|
||||
/>
|
||||
<p className="f8 mt4 lh-copy db">
|
||||
Invite
|
||||
<span className="gray3 ml1">
|
||||
(Optional)
|
||||
</span>
|
||||
Invite
|
||||
<span className="gray3 ml1">(Optional)</span>
|
||||
</p>
|
||||
<p className="f9 gray2 db mb2 pt1">
|
||||
Selected ships will be invited to read your notebook. Selected
|
||||
groups will be invited to read and write notes.
|
||||
</p>
|
||||
<p className="f9 gray2 db mb2 pt1">Selected ships will be invited to read your notebook. Selected groups will be invited to read and write notes.</p>
|
||||
<InviteSearch
|
||||
associations={this.props.associations}
|
||||
groupResults={true}
|
||||
@ -195,12 +201,17 @@ export class NewScreen extends Component {
|
||||
/>
|
||||
{createGroupToggle}
|
||||
<button
|
||||
disabled={this.state.disabled}
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className={createClasses}>
|
||||
Create Notebook
|
||||
disabled={this.state.disabled}
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className={createClasses}
|
||||
>
|
||||
Create Notebook
|
||||
</button>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt3" text="Creating notebook..."/>
|
||||
<Spinner
|
||||
awaiting={this.state.awaiting}
|
||||
classes="mt3"
|
||||
text="Creating notebook..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
@ -1,20 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
import moment from 'moment';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
export class NoteNavigation extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
|
||||
let nextComponent = null;
|
||||
let prevComponent = null;
|
||||
let nextUrl = ''
|
||||
let prevUrl = ''
|
||||
let nextUrl = '';
|
||||
let prevUrl = '';
|
||||
|
||||
let popout = (this.props.popout) ? "popout/" : "";
|
||||
const popout = (this.props.popout) ? 'popout/' : '';
|
||||
|
||||
if (this.props.next && this.props.prev) {
|
||||
nextUrl = `/~publish/${popout}note/${this.props.ship}/${this.props.book}/${this.props.next.id}`;
|
||||
@ -24,15 +21,14 @@ export class NoteNavigation extends Component {
|
||||
<div className="f9 gray2 mb2">Next</div>
|
||||
<div className="f9 mb1 truncate">{this.props.next.title}</div>
|
||||
<div className="f9 gray2">{this.props.next.date}</div>
|
||||
</Link>
|
||||
</Link>;
|
||||
|
||||
prevComponent =
|
||||
<Link to={prevUrl} className="di flex-column flex-auto w-100 pv6 bt br bb b--gray3">
|
||||
<div className="f9 gray2 mb2">Previous</div>
|
||||
<div className="f9 mb1 truncate">{this.props.prev.title}</div>
|
||||
<div className="f9 gray2">{this.props.prev.date}</div>
|
||||
</Link>
|
||||
|
||||
</Link>;
|
||||
} else if (this.props.prev) {
|
||||
prevUrl = `/~publish/${popout}note/${this.props.ship}/${this.props.book}/${this.props.prev.id}`;
|
||||
prevComponent =
|
||||
@ -40,7 +36,7 @@ export class NoteNavigation extends Component {
|
||||
<div className="f9 gray2 mb2">Previous</div>
|
||||
<div className="f9 mb1 truncate">{this.props.prev.title}</div>
|
||||
<div className="f9 gray2">{this.props.prev.date}</div>
|
||||
</Link>
|
||||
</Link>;
|
||||
} else if (this.props.next) {
|
||||
nextUrl = `/~publish/${popout}note/${this.props.ship}/${this.props.book}/${this.props.next.id}`;
|
||||
nextComponent =
|
||||
@ -48,8 +44,7 @@ export class NoteNavigation extends Component {
|
||||
<div className="f9 gray2 mb2">Next</div>
|
||||
<div className="f9 mb1 truncate">{this.props.next.title}</div>
|
||||
<div className="f9 gray2">{this.props.next.date}</div>
|
||||
</Link>
|
||||
|
||||
</Link>;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -57,8 +52,8 @@ export class NoteNavigation extends Component {
|
||||
{prevComponent}
|
||||
{nextComponent}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NoteNavigation
|
||||
export default NoteNavigation;
|
@ -1,25 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SidebarSwitcher } from '../../../../components/SidebarSwitch';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { Comments } from './comments';
|
||||
import { NoteNavigation } from './note-navigation';
|
||||
import moment from 'moment';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { cite } from '../../lib/util';
|
||||
import { cite } from '../../../../lib/util';
|
||||
|
||||
export class Note extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
deleting: false
|
||||
}
|
||||
};
|
||||
moment.updateLocale('en', {
|
||||
relativeTime: {
|
||||
past: function(input) {
|
||||
return input === 'just now'
|
||||
? input
|
||||
: input + ' ago'
|
||||
: input + ' ago';
|
||||
},
|
||||
s : 'just now',
|
||||
future : 'in %s',
|
||||
@ -32,7 +32,7 @@ export class Note extends Component {
|
||||
M : '1 month',
|
||||
MM : '%d months',
|
||||
y : '1 year',
|
||||
yy : '%d years',
|
||||
yy : '%d years'
|
||||
}
|
||||
});
|
||||
this.scrollElement = React.createRef();
|
||||
@ -41,15 +41,15 @@ export class Note extends Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let readAction = {
|
||||
const readAction = {
|
||||
read: {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
note: this.props.note
|
||||
}
|
||||
}
|
||||
window.api.action("publish", "publish-action", readAction);
|
||||
window.api.fetchNote(this.props.ship, this.props.book, this.props.note);
|
||||
};
|
||||
this.props.api.action('publish', 'publish-action', readAction);
|
||||
this.props.api.fetchNote(this.props.ship, this.props.book, this.props.note);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -57,7 +57,7 @@ export class Note extends Component {
|
||||
!(this.props.notebooks[this.props.ship][this.props.book]) ||
|
||||
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note]) ||
|
||||
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note].file)) {
|
||||
window.api.fetchNote(this.props.ship, this.props.book, this.props.note);
|
||||
this.props.api.fetchNote(this.props.ship, this.props.book, this.props.note);
|
||||
}
|
||||
this.onScroll();
|
||||
}
|
||||
@ -66,65 +66,64 @@ export class Note extends Component {
|
||||
if (!(this.props.notebooks[this.props.ship]) ||
|
||||
!(this.props.notebooks[this.props.ship][this.props.book]) ||
|
||||
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note]) ||
|
||||
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note].file))
|
||||
{
|
||||
window.api.fetchNote(this.props.ship, this.props.book, this.props.note);
|
||||
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note].file)) {
|
||||
this.props.api.fetchNote(this.props.ship, this.props.book, this.props.note);
|
||||
}
|
||||
if ((prevProps.book !== this.props.book) ||
|
||||
(prevProps.note !== this.props.note) ||
|
||||
(prevProps.ship !== this.props.ship)) {
|
||||
let readAction = {
|
||||
const readAction = {
|
||||
read: {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
note: this.props.note
|
||||
}
|
||||
}
|
||||
window.api.action("publish", "publish-action", readAction);
|
||||
};
|
||||
this.props.api.action('publish', 'publish-action', readAction);
|
||||
}
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
let note = notebook.notes[this.props.note];
|
||||
const notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
const note = notebook.notes[this.props.note];
|
||||
|
||||
if (!note.comments) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scrollTop = this.scrollElement.scrollTop;
|
||||
let clientHeight = this.scrollElement.clientHeight;
|
||||
let scrollHeight = this.scrollElement.scrollHeight;
|
||||
const scrollTop = this.scrollElement.scrollTop;
|
||||
const clientHeight = this.scrollElement.clientHeight;
|
||||
const scrollHeight = this.scrollElement.scrollHeight;
|
||||
|
||||
let atBottom = false;
|
||||
if (scrollHeight - scrollTop - clientHeight < 40) {
|
||||
atBottom = true;
|
||||
}
|
||||
|
||||
let loadedComments = note.comments.length;
|
||||
let allComments = note["num-comments"];
|
||||
const loadedComments = note.comments.length;
|
||||
const allComments = note['num-comments'];
|
||||
|
||||
let fullyLoaded = (loadedComments === allComments);
|
||||
const fullyLoaded = (loadedComments === allComments);
|
||||
|
||||
if (atBottom && !fullyLoaded) {
|
||||
window.api.fetchCommentsPage(this.props.ship,
|
||||
this.props.api.fetchCommentsPage(this.props.ship,
|
||||
this.props.book, this.props.note, loadedComments, 30);
|
||||
}
|
||||
}
|
||||
|
||||
deletePost() {
|
||||
const { props } = this;
|
||||
let deleteAction = {
|
||||
"del-note": {
|
||||
const deleteAction = {
|
||||
'del-note': {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
note: this.props.note
|
||||
}
|
||||
}
|
||||
let popout = (props.popout) ? "popout/" : "";
|
||||
let baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
this.setState({deleting: true});
|
||||
window.api.action("publish", "publish-action", deleteAction)
|
||||
};
|
||||
const popout = (props.popout) ? 'popout/' : '';
|
||||
const baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
this.setState({ deleting: true });
|
||||
this.props.api.action('publish', 'publish-action', deleteAction)
|
||||
.then(() => {
|
||||
props.history.push(baseUrl);
|
||||
});
|
||||
@ -132,14 +131,14 @@ export class Note extends Component {
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
let notebook = props.notebooks[props.ship][props.book] || {};
|
||||
let comments = notebook.notes[props.note].comments || false;
|
||||
let title = notebook.notes[props.note].title || "";
|
||||
let author = notebook.notes[props.note].author || "";
|
||||
let file = notebook.notes[props.note].file || "";
|
||||
let date = moment(notebook.notes[props.note]["date-created"]).fromNow() || 0;
|
||||
const notebook = props.notebooks[props.ship][props.book] || {};
|
||||
const comments = notebook.notes[props.note].comments || false;
|
||||
const title = notebook.notes[props.note].title || '';
|
||||
const author = notebook.notes[props.note].author || '';
|
||||
const file = notebook.notes[props.note].file || '';
|
||||
const date = moment(notebook.notes[props.note]['date-created']).fromNow() || 0;
|
||||
|
||||
let contact = !!(author.substr(1) in props.contacts)
|
||||
const contact = author.substr(1) in props.contacts
|
||||
? props.contacts[author.substr(1)] : false;
|
||||
|
||||
let name = author;
|
||||
@ -156,64 +155,68 @@ export class Note extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
let newfile = file.slice(file.indexOf(';>')+2);
|
||||
let prevId = notebook.notes[props.note]["prev-note"] || null;
|
||||
let nextId = notebook.notes[props.note]["next-note"] || null;
|
||||
const newfile = file.slice(file.indexOf(';>')+2);
|
||||
const prevId = notebook.notes[props.note]['prev-note'] || null;
|
||||
const nextId = notebook.notes[props.note]['next-note'] || null;
|
||||
|
||||
let prev = (prevId === null)
|
||||
const prev = (prevId === null)
|
||||
? null
|
||||
: {
|
||||
id: prevId,
|
||||
title: notebook.notes[prevId].title,
|
||||
date: moment(notebook.notes[prevId]["date-created"]).fromNow()
|
||||
}
|
||||
let next = (nextId === null)
|
||||
date: moment(notebook.notes[prevId]['date-created']).fromNow()
|
||||
};
|
||||
const next = (nextId === null)
|
||||
? null
|
||||
: {
|
||||
id: nextId,
|
||||
title: notebook.notes[nextId].title,
|
||||
date: moment(notebook.notes[nextId]["date-created"]).fromNow()
|
||||
}
|
||||
date: moment(notebook.notes[nextId]['date-created']).fromNow()
|
||||
};
|
||||
|
||||
let editPost = null;
|
||||
let editUrl = props.location.pathname + "/edit";
|
||||
const editUrl = props.location.pathname + '/edit';
|
||||
if (`~${window.ship}` === author) {
|
||||
editPost = <div className="dib">
|
||||
<Link className="green2 f9" to={editUrl}>Edit</Link>
|
||||
<p className="dib f9 red2 ml2 pointer"
|
||||
onClick={(() => this.deletePost())}>Delete</p>
|
||||
</div>
|
||||
onClick={(() => this.deletePost())}
|
||||
>Delete</p>
|
||||
</div>;
|
||||
}
|
||||
|
||||
let popout = (props.popout) ? "popout/" : "";
|
||||
const popout = (props.popout) ? 'popout/' : '';
|
||||
|
||||
let hrefIndex = props.location.pathname.indexOf("/note/");
|
||||
let publishsubStr = props.location.pathname.substr(hrefIndex);
|
||||
let popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
const hrefIndex = props.location.pathname.indexOf('/note/');
|
||||
const publishsubStr = props.location.pathname.substr(hrefIndex);
|
||||
const popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
|
||||
let hiddenOnPopout = props.popout ? "" : "dib-m dib-l dib-xl";
|
||||
const hiddenOnPopout = props.popout ? '' : 'dib-m dib-l dib-xl';
|
||||
|
||||
let baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
const baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
return (
|
||||
<div
|
||||
className="h-100 overflow-y-scroll"
|
||||
onScroll={this.onScroll}
|
||||
ref={el => {
|
||||
ref={(el) => {
|
||||
this.scrollElement = el;
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<div className="h-100 flex flex-column items-center pa4">
|
||||
<div className="w-100 flex justify-center pb6">
|
||||
<SidebarSwitcher
|
||||
popout={props.popout}
|
||||
sidebarShown={props.sidebarShown}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Link className="f9 w-100 w-90-m w-90-l mw6 tl" to={baseUrl}>
|
||||
{"<- Notebook index"}
|
||||
{'<- Notebook index'}
|
||||
</Link>
|
||||
<Link
|
||||
to={popoutHref}
|
||||
className={"dn absolute right-1 top-1 " + hiddenOnPopout}
|
||||
target="_blank">
|
||||
className={'dn absolute right-1 top-1 ' + hiddenOnPopout}
|
||||
target="_blank"
|
||||
>
|
||||
<img src="/~publish/popout.png"
|
||||
height={16}
|
||||
width={16}
|
||||
@ -223,13 +226,15 @@ export class Note extends Component {
|
||||
<div className="w-100 mw6">
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 mb1"
|
||||
style={{overflowWrap: "break-word"}}>{title}</div>
|
||||
style={{ overflowWrap: 'break-word' }}
|
||||
>{title}</div>
|
||||
<div className="flex mb6">
|
||||
<div
|
||||
className={
|
||||
"di f9 gray2 mr2 " + (contact.nickname ? null : "mono")
|
||||
'di f9 gray2 mr2 ' + (contact.nickname ? null : 'mono')
|
||||
}
|
||||
title={author}>
|
||||
title={author}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
<div className="di">
|
||||
@ -237,8 +242,9 @@ export class Note extends Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className="md"
|
||||
style={{overflowWrap: "break-word"}}>
|
||||
<ReactMarkdown source={newfile} linkTarget={"_blank"} />
|
||||
style={{ overflowWrap: 'break-word' }}
|
||||
>
|
||||
<ReactMarkdown source={newfile} linkTarget={'_blank'} />
|
||||
</div>
|
||||
<NoteNavigation
|
||||
popout={props.popout}
|
||||
@ -253,6 +259,7 @@ export class Note extends Component {
|
||||
note={props.note}
|
||||
comments={comments}
|
||||
contacts={props.contacts}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Spinner text="Deleting post..." awaiting={this.state.deleting} classes="absolute bottom-1 right-1 ba b--gray1-d pa2" />
|
||||
</div>
|
||||
@ -262,4 +269,4 @@ export class Note extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default Note
|
||||
export default Note;
|
@ -0,0 +1,28 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export class NotebookItem extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const selectedClass = (props.selected) ? 'bg-gray5 bg-gray1-d c-default' : 'pointer hover-bg-gray5 hover-bg-gray1-d';
|
||||
|
||||
const unread = (props.unreadCount > 0)
|
||||
? <p className="dib f9 fr"><span className="dib white bg-gray3 bg-gray2-d fw6 br1" style={{ padding: '1px 5px' }}>
|
||||
{props.unreadCount}
|
||||
</span></p> : <span />;
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={'/~publish/notebook/' + props.path}
|
||||
>
|
||||
<div className={'w-100 v-mid f9 ph4 pv1 ' + selectedClass}>
|
||||
<p className="dib f9">{props.title}</p>
|
||||
{unread}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NotebookItem;
|
@ -1,18 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import moment from 'moment';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { cite } from '../../lib/util';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { cite } from '../../../../lib/util';
|
||||
|
||||
export class NotebookPosts extends Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
moment.updateLocale('en', {
|
||||
relativeTime: {
|
||||
past: function(input) {
|
||||
return input === 'just now'
|
||||
? input
|
||||
: input + ' ago'
|
||||
: input + ' ago';
|
||||
},
|
||||
s : 'just now',
|
||||
future : 'in %s',
|
||||
@ -25,23 +25,23 @@ export class NotebookPosts extends Component {
|
||||
M : '1 month',
|
||||
MM : '%d months',
|
||||
y : '1 year',
|
||||
yy : '%d years',
|
||||
yy : '%d years'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
let notes = [];
|
||||
const notes = [];
|
||||
|
||||
for (var i=0; i<props.list.length; i++) {
|
||||
let noteId = props.list[i];
|
||||
let note = props.notes[noteId];
|
||||
for (let i=0; i<props.list.length; i++) {
|
||||
const noteId = props.list[i];
|
||||
const note = props.notes[noteId];
|
||||
if (!note) {
|
||||
break;
|
||||
}
|
||||
|
||||
let contact = !!(note.author.substr(1) in props.contacts)
|
||||
const contact = note.author.substr(1) in props.contacts
|
||||
? props.contacts[note.author.substr(1)] : false;
|
||||
|
||||
let name = note.author;
|
||||
@ -52,40 +52,44 @@ export class NotebookPosts extends Component {
|
||||
if (name === note.author) {
|
||||
name = cite(note.author);
|
||||
}
|
||||
let comment = "No Comments";
|
||||
if (note["num-comments"] == 1) {
|
||||
comment = "1 Comment";
|
||||
} else if (note["num-comments"] > 1) {
|
||||
comment = `${note["num-comments"]} Comments`;
|
||||
let comment = 'No Comments';
|
||||
if (note['num-comments'] == 1) {
|
||||
comment = '1 Comment';
|
||||
} else if (note['num-comments'] > 1) {
|
||||
comment = `${note['num-comments']} Comments`;
|
||||
}
|
||||
let date = moment(note["date-created"]).fromNow();
|
||||
let popout = (props.popout) ? "popout/" : "";
|
||||
let url = `/~publish/${popout}note/${props.host}/${props.notebookName}/${noteId}`
|
||||
const date = moment(note['date-created']).fromNow();
|
||||
const popout = (props.popout) ? 'popout/' : '';
|
||||
const url = `/~publish/${popout}note/${props.host}/${props.notebookName}/${noteId}`;
|
||||
|
||||
notes.push(
|
||||
<Link key={i} to={url}>
|
||||
<div className="mv6">
|
||||
<div className="mb1"
|
||||
style={{overflowWrap: "break-word"}}>
|
||||
style={{ overflowWrap: 'break-word' }}
|
||||
>
|
||||
{note.title}
|
||||
</div>
|
||||
<p className="mb1"
|
||||
style={{overflowWrap: "break-word"}}>
|
||||
style={{ overflowWrap: 'break-word' }}
|
||||
>
|
||||
<ReactMarkdown
|
||||
unwrapDisallowed
|
||||
allowedTypes={['text', 'root', 'break', 'paragraph']}
|
||||
source={note.snippet} />
|
||||
source={note.snippet}
|
||||
/>
|
||||
</p>
|
||||
<div className="flex">
|
||||
<div className={(contact.nickname ? null : "mono") +
|
||||
" gray2 mr3"}
|
||||
title={note.author}>{name}</div>
|
||||
<div className={(contact.nickname ? null : 'mono') +
|
||||
' gray2 mr3'}
|
||||
title={note.author}
|
||||
>{name}</div>
|
||||
<div className="gray2 mr3">{date}</div>
|
||||
<div className="gray2">{comment}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -93,8 +97,7 @@ export class NotebookPosts extends Component {
|
||||
{notes}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default NotebookPosts
|
||||
export default NotebookPosts;
|
@ -1,14 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link, Switch, Route } from 'react-router-dom';
|
||||
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SidebarSwitcher } from '../../../../components/SidebarSwitch';
|
||||
import { NotebookPosts } from './notebook-posts';
|
||||
import { Subscribers } from './subscribers';
|
||||
import { Settings } from './settings';
|
||||
import Sidebar from './sidebar';
|
||||
import { cite } from '../../lib/util';
|
||||
import { cite } from '../../../../lib/util';
|
||||
|
||||
export class Notebook extends Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onScroll = this.onScroll.bind(this);
|
||||
@ -16,104 +15,106 @@ export class Notebook extends Component {
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
let scrollTop = this.scrollElement.scrollTop;
|
||||
let clientHeight = this.scrollElement.clientHeight;
|
||||
let scrollHeight = this.scrollElement.scrollHeight;
|
||||
const notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
const scrollTop = this.scrollElement.scrollTop;
|
||||
const clientHeight = this.scrollElement.clientHeight;
|
||||
const scrollHeight = this.scrollElement.scrollHeight;
|
||||
|
||||
let atBottom = false;
|
||||
if (scrollHeight - scrollTop - clientHeight < 40) {
|
||||
atBottom = true;
|
||||
}
|
||||
if (!notebook.notes) {
|
||||
window.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
this.props.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
return;
|
||||
}
|
||||
|
||||
let loadedNotes = Object.keys(notebook.notes).length;
|
||||
let allNotes = notebook["notes-by-date"].length;
|
||||
const loadedNotes = Object.keys(notebook.notes).length;
|
||||
const allNotes = notebook['notes-by-date'].length;
|
||||
|
||||
let fullyLoaded = (loadedNotes === allNotes);
|
||||
const fullyLoaded = (loadedNotes === allNotes);
|
||||
|
||||
if (atBottom && !fullyLoaded) {
|
||||
window.api.fetchNotesPage(this.props.ship, this.props.book, loadedNotes, 30);
|
||||
this.props.api.fetchNotesPage(this.props.ship, this.props.book, loadedNotes, 30);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
window.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
componentWillMount() {
|
||||
this.props.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
const notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
if (!notebook.subscribers) {
|
||||
window.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
this.props.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
const notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
if (notebook.notes) {
|
||||
this.onScroll();
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribe() {
|
||||
let action = {
|
||||
const action = {
|
||||
unsubscribe: {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
book: this.props.book
|
||||
}
|
||||
}
|
||||
window.api.action("publish", "publish-action", action);
|
||||
this.props.history.push("/~publish");
|
||||
};
|
||||
this.props.api.action('publish', 'publish-action', action);
|
||||
this.props.history.push('/~publish');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
// popout logic
|
||||
let hrefIndex = props.location.pathname.indexOf("/notebook/");
|
||||
let publishsubStr = props.location.pathname.substr(hrefIndex);
|
||||
let popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
const hrefIndex = props.location.pathname.indexOf('/notebook/');
|
||||
const publishsubStr = props.location.pathname.substr(hrefIndex);
|
||||
const popoutHref = `/~publish/popout${publishsubStr}`;
|
||||
|
||||
let hiddenOnPopout = props.popout ? "" : "dib-m dib-l dib-xl";
|
||||
const hiddenOnPopout = props.popout ? '' : 'dib-m dib-l dib-xl';
|
||||
|
||||
let notebook = props.notebooks[props.ship][props.book];
|
||||
const notebook = props.notebooks[props.ship][props.book];
|
||||
|
||||
let tabStyles = {
|
||||
posts: "bb b--gray4 b--gray2-d gray2 pv4 ph2",
|
||||
about: "bb b--gray4 b--gray2-d gray2 pv4 ph2",
|
||||
subscribers: "bb b--gray4 b--gray2-d gray2 pv4 ph2",
|
||||
settings: "bb b--gray4 b--gray2-d pr2 gray2 pv4 ph2",
|
||||
const tabStyles = {
|
||||
posts: 'bb b--gray4 b--gray2-d gray2 pv4 ph2',
|
||||
about: 'bb b--gray4 b--gray2-d gray2 pv4 ph2',
|
||||
subscribers: 'bb b--gray4 b--gray2-d gray2 pv4 ph2',
|
||||
settings: 'bb b--gray4 b--gray2-d pr2 gray2 pv4 ph2'
|
||||
};
|
||||
tabStyles[props.view] = "bb b--black b--white-d black white-d pv4 ph2";
|
||||
tabStyles[props.view] = 'bb b--black b--white-d black white-d pv4 ph2';
|
||||
|
||||
let inner = null;
|
||||
switch (props.view) {
|
||||
case "posts":
|
||||
let notesList = notebook["notes-by-date"] || [];
|
||||
let notes = notebook.notes || null;
|
||||
case 'posts': {
|
||||
const notesList = notebook['notes-by-date'] || [];
|
||||
const notes = notebook.notes || null;
|
||||
inner = <NotebookPosts notes={notes}
|
||||
popout={props.popout}
|
||||
list={notesList}
|
||||
host={props.ship}
|
||||
notebookName={props.book}
|
||||
contacts={props.notebookContacts}
|
||||
/>
|
||||
/>;
|
||||
break;
|
||||
case "about":
|
||||
inner = <p className="f8 lh-solid">{notebook.about}</p>
|
||||
}
|
||||
case 'about':
|
||||
inner = <p className="f8 lh-solid">{notebook.about}</p>;
|
||||
break;
|
||||
case "subscribers":
|
||||
case 'subscribers':
|
||||
inner = <Subscribers
|
||||
host={this.props.ship}
|
||||
book={this.props.book}
|
||||
notebook={notebook}
|
||||
permissions={this.props.permissions}
|
||||
groups={this.props.groups}/>
|
||||
groups={this.props.groups}
|
||||
/>;
|
||||
break;
|
||||
case "settings":
|
||||
case 'settings':
|
||||
inner = <Settings
|
||||
host={this.props.ship}
|
||||
book={this.props.book}
|
||||
@ -121,14 +122,15 @@ export class Notebook extends Component {
|
||||
groups={this.props.groups}
|
||||
contacts={this.props.contacts}
|
||||
associations={this.props.associations}
|
||||
history={this.props.history}/>
|
||||
history={this.props.history}
|
||||
/>;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// displaying nicknames, sigil colors for contacts
|
||||
let contact = !!(props.ship.substr(1) in props.notebookContacts)
|
||||
const contact = props.ship.substr(1) in props.notebookContacts
|
||||
? props.notebookContacts[props.ship.substr(1)] : false;
|
||||
let name = props.ship;
|
||||
if (contact) {
|
||||
@ -140,38 +142,39 @@ export class Notebook extends Component {
|
||||
name = cite(props.ship);
|
||||
}
|
||||
|
||||
let popout = (props.popout) ? "popout/" : "";
|
||||
let base = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
let about = base + '/about';
|
||||
let subs = base + '/subscribers';
|
||||
let settings = base + '/settings';
|
||||
let newUrl = base + '/new';
|
||||
const popout = (props.popout) ? 'popout/' : '';
|
||||
const base = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
const about = base + '/about';
|
||||
const subs = base + '/subscribers';
|
||||
const settings = base + '/settings';
|
||||
const newUrl = base + '/new';
|
||||
|
||||
let newPost = null;
|
||||
if (notebook["writers-group-path"] in props.groups){
|
||||
let writers = notebook["writers-group-path"];
|
||||
if (notebook['writers-group-path'] in props.groups) {
|
||||
const writers = notebook['writers-group-path'];
|
||||
if (props.groups[writers].has(window.ship)) {
|
||||
newPost =
|
||||
<Link to={newUrl} className="NotebookButton bg-light-green green2">
|
||||
New Post
|
||||
</Link>
|
||||
</Link>;
|
||||
}
|
||||
}
|
||||
|
||||
let unsub = (window.ship === props.ship.slice(1))
|
||||
const unsub = (window.ship === props.ship.slice(1))
|
||||
? null
|
||||
: <button onClick={this.unsubscribe}
|
||||
className="NotebookButton bg-white bg-gray0-d black white-d ba b--black b--gray2-d ml3">
|
||||
className="NotebookButton bg-white bg-gray0-d black white-d ba b--black b--gray2-d ml3"
|
||||
>
|
||||
Unsubscribe
|
||||
</button>
|
||||
</button>;
|
||||
|
||||
let subsComponent = (this.props.ship.slice(1) !== window.ship)
|
||||
const subsComponent = (this.props.ship.slice(1) !== window.ship)
|
||||
? null
|
||||
: <Link to={subs} className={tabStyles.subscribers}>
|
||||
Subscribers
|
||||
</Link>;
|
||||
|
||||
let settingsComponent = (this.props.ship.slice(1) !== window.ship)
|
||||
const settingsComponent = (this.props.ship.slice(1) !== window.ship)
|
||||
? null
|
||||
: <Link to={settings} className={tabStyles.settings}>
|
||||
Settings
|
||||
@ -182,20 +185,23 @@ export class Notebook extends Component {
|
||||
className="overflow-y-scroll"
|
||||
style={{ paddingLeft: 16, paddingRight: 16 }}
|
||||
onScroll={this.onScroll}
|
||||
ref={el => {
|
||||
ref={(el) => {
|
||||
this.scrollElement = el;
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<div className="w-100 dn-m dn-l dn-xl inter pt4 pb6 f9">
|
||||
<Link to="/~publish">{"<- All Notebooks"}</Link>
|
||||
<Link to="/~publish">{'<- All Notebooks'}</Link>
|
||||
</div>
|
||||
<div className="center mw6 f9 h-100"
|
||||
style={{ paddingLeft: 16, paddingRight: 16 }}>
|
||||
style={{ paddingLeft: 16, paddingRight: 16 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
popout={props.popout}
|
||||
sidebarShown={props.sidebarShown}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<Link
|
||||
className={"dn absolute right-1 top-1 " + hiddenOnPopout}
|
||||
className={'dn absolute right-1 top-1 ' + hiddenOnPopout}
|
||||
to={popoutHref}
|
||||
target="_blank"
|
||||
>
|
||||
@ -207,13 +213,15 @@ export class Notebook extends Component {
|
||||
<div className="h-100 pt0 pt8-m pt8-l pt8-xl no-scrollbar">
|
||||
<div
|
||||
className="flex justify-between"
|
||||
style={{ marginBottom: 32 }}>
|
||||
style={{ marginBottom: 32 }}
|
||||
>
|
||||
<div className="flex-col">
|
||||
<div className="mb1">{notebook.title}</div>
|
||||
<span>
|
||||
<span className="gray3 mr1">by</span>
|
||||
<span className={contact.nickname ? null : "mono"}
|
||||
title={props.ship}>
|
||||
<span className={contact.nickname ? null : 'mono'}
|
||||
title={props.ship}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</span>
|
||||
@ -234,10 +242,11 @@ export class Notebook extends Component {
|
||||
{subsComponent}
|
||||
{settingsComponent}
|
||||
<div className="bb b--gray4 b--gray2-d gray2 pv4 ph2"
|
||||
style={{ flexGrow: 1 }}></div>
|
||||
style={{ flexGrow: 1 }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div style={{ height: "calc(100% - 188px)" }} className="f9 lh-solid">
|
||||
<div style={{ height: 'calc(100% - 188px)' }} className="f9 lh-solid">
|
||||
{inner}
|
||||
</div>
|
||||
</div>
|
||||
@ -247,4 +256,4 @@ export class Notebook extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default Notebook
|
||||
export default Notebook;
|
@ -1,20 +1,20 @@
|
||||
import React, { Component } from 'react';
|
||||
import { writeText } from '../../lib/util';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { InviteSearch } from '/components/lib/invite-search';
|
||||
import { writeText } from '../../../../lib/util';
|
||||
import { Spinner } from '../../../../components/Spinner';
|
||||
import { InviteSearch } from '../../../../components/InviteSearch';
|
||||
|
||||
export class Settings extends Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
title: "",
|
||||
description: "",
|
||||
title: '',
|
||||
description: '',
|
||||
comments: false,
|
||||
disabled: false,
|
||||
type: "Editing",
|
||||
type: 'Editing',
|
||||
targetGroup: null,
|
||||
inclusive: false,
|
||||
}
|
||||
inclusive: false
|
||||
};
|
||||
this.deleteNotebook = this.deleteNotebook.bind(this);
|
||||
this.changeTitle = this.changeTitle.bind(this);
|
||||
this.changeDescription = this.changeDescription.bind(this);
|
||||
@ -30,7 +30,7 @@ export class Settings extends Component {
|
||||
title: props.notebook.title,
|
||||
description: props.notebook.about,
|
||||
comments: props.notebook.comments
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,13 +40,13 @@ export class Settings extends Component {
|
||||
if (props.notebook) {
|
||||
if (prevProps.notebook && prevProps.notebook !== props.notebook) {
|
||||
if (prevProps.notebook.title !== props.notebook.title) {
|
||||
this.setState({title: props.notebook.title});
|
||||
this.setState({ title: props.notebook.title });
|
||||
}
|
||||
if (prevProps.notebook.about !== props.notebook.about) {
|
||||
this.setState({description: props.notebook.about});
|
||||
this.setState({ description: props.notebook.about });
|
||||
}
|
||||
if (prevProps.notebook.comments !== props.notebook.comments) {
|
||||
this.setState({comments: props.notebook.comments})
|
||||
this.setState({ comments: props.notebook.comments });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,17 +54,17 @@ export class Settings extends Component {
|
||||
}
|
||||
|
||||
changeTitle(event) {
|
||||
this.setState({title: event.target.value});
|
||||
this.setState({ title: event.target.value });
|
||||
}
|
||||
|
||||
changeDescription(event) {
|
||||
this.setState({description: event.target.value});
|
||||
this.setState({ description: event.target.value });
|
||||
}
|
||||
|
||||
changeComments() {
|
||||
this.setState({comments: !this.state.comments, disabled: true}, (() => {
|
||||
window.api.action("publish", "publish-action", {
|
||||
"edit-book": {
|
||||
this.setState({ comments: !this.state.comments, disabled: true }, (() => {
|
||||
this.props.api.action('publish', 'publish-action', {
|
||||
'edit-book': {
|
||||
book: this.props.book,
|
||||
title: this.props.notebook.title,
|
||||
about: this.props.notebook.about,
|
||||
@ -72,20 +72,20 @@ export class Settings extends Component {
|
||||
group: null
|
||||
}
|
||||
}).then(() => {
|
||||
this.setState({disabled: false});
|
||||
})
|
||||
this.setState({ disabled: false });
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
deleteNotebook(){
|
||||
let action = {
|
||||
"del-book": {
|
||||
deleteNotebook() {
|
||||
const action = {
|
||||
'del-book': {
|
||||
book: this.props.book
|
||||
}
|
||||
}
|
||||
this.setState({ disabled: true, type: "Deleting" });
|
||||
window.api.action("publish", "publish-action", action).then(() => {
|
||||
this.props.history.push("/~publish");
|
||||
};
|
||||
this.setState({ disabled: true, type: 'Deleting' });
|
||||
this.props.api.action('publish', 'publish-action', action).then(() => {
|
||||
this.props.history.push('/~publish');
|
||||
});
|
||||
}
|
||||
|
||||
@ -98,7 +98,7 @@ export class Settings extends Component {
|
||||
}
|
||||
|
||||
changeInclusive(event) {
|
||||
this.setState({ inclusive: !!event.target.checked });
|
||||
this.setState({ inclusive: Boolean(event.target.checked) });
|
||||
}
|
||||
|
||||
groupifyNotebook() {
|
||||
@ -108,13 +108,13 @@ export class Settings extends Component {
|
||||
disabled: true,
|
||||
type: 'Converting'
|
||||
}, (() => {
|
||||
window.api.action("publish", "publish-action", {
|
||||
this.props.api.action('publish', 'publish-action', {
|
||||
groupify: {
|
||||
book: props.book,
|
||||
target: state.targetGroup,
|
||||
inclusive: state.inclusive,
|
||||
inclusive: state.inclusive
|
||||
}
|
||||
}).then(() => this.setState({disabled: false}));
|
||||
}).then(() => this.setState({ disabled: false }));
|
||||
}));
|
||||
}
|
||||
|
||||
@ -132,20 +132,20 @@ export class Settings extends Component {
|
||||
} else {
|
||||
// don't give the option to make inclusive if we don't own the target
|
||||
// group
|
||||
let targetOwned = (state.targetGroup)
|
||||
const targetOwned = (state.targetGroup)
|
||||
? state.targetGroup.slice(0, window.ship.length+3) === `/~${window.ship}/`
|
||||
: false;
|
||||
let inclusiveToggle = <div/>
|
||||
let inclusiveToggle = <div />;
|
||||
if (targetOwned) {
|
||||
//TODO toggle component into /lib
|
||||
let inclusiveClasses = state.inclusive
|
||||
? "relative checked bg-green2 br3 h1 toggle v-mid z-0"
|
||||
: "relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0";
|
||||
// TODO toggle component into /lib
|
||||
const inclusiveClasses = state.inclusive
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
inclusiveToggle = (
|
||||
<div className="mt4">
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ WebkitAppearance: "none", width: 28 }}
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
className={inclusiveClasses}
|
||||
onChange={this.changeInclusive}
|
||||
/>
|
||||
@ -161,7 +161,7 @@ export class Settings extends Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={"w-100 fl mt3 mb3"} style={{maxWidth: "29rem"}}>
|
||||
<div className={'w-100 fl mt3 mb3'} style={{ maxWidth: '29rem' }}>
|
||||
<p className="f8 mt3 lh-copy db">Convert Notebook</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Convert this notebook into a group with associated chat, or select a
|
||||
@ -182,8 +182,9 @@ export class Settings extends Component {
|
||||
{inclusiveToggle}
|
||||
<button
|
||||
onClick={this.groupifyNotebook.bind(this)}
|
||||
className={"dib f9 black gray4-d bg-gray0-d ba pa2 mt4 b--black b--gray1-d pointer"}
|
||||
disabled={this.state.disabled}>
|
||||
className={'dib f9 black gray4-d bg-gray0-d ba pa2 mt4 b--black b--gray1-d pointer'}
|
||||
disabled={this.state.disabled}
|
||||
>
|
||||
Convert to group
|
||||
</button>
|
||||
</div>
|
||||
@ -192,34 +193,33 @@ export class Settings extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
let commentsSwitchClasses = (this.state.comments)
|
||||
? "relative checked bg-green2 br3 h1 toggle v-mid z-0"
|
||||
: "relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0";
|
||||
const commentsSwitchClasses = (this.state.comments)
|
||||
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
|
||||
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
|
||||
|
||||
let copyShortcode = <div>
|
||||
const copyShortcode = <div>
|
||||
<p className="f9 mt3 lh-copy">Share</p>
|
||||
<p className="f9 gray2 mb4">Share a shortcode to join this notebook</p>
|
||||
<div className="relative w-100 flex" style={{ maxWidth: "29rem" }}>
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
className={"f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"pa3 db w-100 flex-auto mr3"}
|
||||
className={'f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'pa3 db w-100 flex-auto mr3'}
|
||||
disabled={true}
|
||||
value={`${this.props.host}/${this.props.book}` || ""}
|
||||
value={`${this.props.host}/${this.props.book}` || ''}
|
||||
/>
|
||||
<span className="f8 pointer absolute pa3 inter"
|
||||
style={{ right: 12, top: 1 }}
|
||||
ref="copy"
|
||||
onClick={() => {
|
||||
writeText(`${this.props.host}/${this.props.book}`);
|
||||
this.refs.copy.innerText = "Copied"
|
||||
}}>
|
||||
this.refs.copy.innerText = 'Copied';
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
if (this.props.host.slice(1) === window.ship) {
|
||||
return (
|
||||
@ -233,25 +233,26 @@ export class Settings extends Component {
|
||||
</p>
|
||||
<button
|
||||
className="bg-transparent b--red2 red2 pointer dib f9 ba pa2"
|
||||
onClick={this.deleteNotebook}>
|
||||
onClick={this.deleteNotebook}
|
||||
>
|
||||
Delete this notebook
|
||||
</button>
|
||||
<p className="f9 mt6 lh-copy">Rename</p>
|
||||
<p className="f9 gray2 db mb4">Change the name of this notebook</p>
|
||||
<div className="relative w-100 flex" style={{ maxWidth: "29rem" }}>
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
className={
|
||||
"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"
|
||||
'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'
|
||||
}
|
||||
value={this.state.title}
|
||||
onChange={this.changeTitle}
|
||||
disabled={this.state.disabled}
|
||||
onBlur={() => {
|
||||
this.setState({ disabled: true });
|
||||
window.api
|
||||
.action("publish", "publish-action", {
|
||||
"edit-book": {
|
||||
this.props.api
|
||||
.action('publish', 'publish-action', {
|
||||
'edit-book': {
|
||||
book: this.props.book,
|
||||
title: this.state.title,
|
||||
about: this.props.notebook.about,
|
||||
@ -260,26 +261,26 @@ export class Settings extends Component {
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.setState({ disabled: false })
|
||||
this.setState({ disabled: false });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="f9 mt6 lh-copy">Change description</p>
|
||||
<p className="f9 gray2 db mb4">Change the description of this notebook</p>
|
||||
<div className="relative w-100 flex" style={{ maxWidth: "29rem" }}>
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
className={
|
||||
"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"
|
||||
'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'
|
||||
}
|
||||
value={this.state.description}
|
||||
onChange={this.changeDescription}
|
||||
onBlur={() => {
|
||||
this.setState({ disabled: true });
|
||||
window.api
|
||||
.action("publish", "publish-action", {
|
||||
"edit-book": {
|
||||
this.props.api
|
||||
.action('publish', 'publish-action', {
|
||||
'edit-book': {
|
||||
book: this.props.book,
|
||||
title: this.props.notebook.title,
|
||||
about: this.state.description,
|
||||
@ -296,7 +297,7 @@ export class Settings extends Component {
|
||||
<div className="mv6">
|
||||
<input
|
||||
type="checkbox"
|
||||
style={{ WebkitAppearance: "none", width: 28 }}
|
||||
style={{ WebkitAppearance: 'none', width: 28 }}
|
||||
className={commentsSwitchClasses}
|
||||
onChange={this.changeComments}
|
||||
/>
|
||||
@ -318,4 +319,4 @@ export class Settings extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default Settings
|
||||
export default Settings;
|
@ -1,27 +1,24 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class SidebarInvite extends Component {
|
||||
|
||||
onAccept() {
|
||||
let action = {
|
||||
const action = {
|
||||
accept: {
|
||||
path: '/publish',
|
||||
uid: this.props.uid,
|
||||
uid: this.props.uid
|
||||
}
|
||||
}
|
||||
window.api.action("invite-store", "invite-action", action);
|
||||
};
|
||||
window.api.action('invite-store', 'invite-action', action);
|
||||
}
|
||||
|
||||
onDecline() {
|
||||
let action = {
|
||||
const action = {
|
||||
decline: {
|
||||
path: '/publish',
|
||||
uid: this.props.uid,
|
||||
uid: this.props.uid
|
||||
}
|
||||
}
|
||||
window.api.action("invite-store", "invite-action", action);
|
||||
};
|
||||
this.props.api.action('invite-store', 'invite-action', action);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -36,16 +33,18 @@ export class SidebarInvite extends Component {
|
||||
</div>
|
||||
<a
|
||||
className="dib pointer pa2 f9 bg-green2 white mt4"
|
||||
onClick={this.onAccept.bind(this)}>
|
||||
onClick={this.onAccept.bind(this)}
|
||||
>
|
||||
Accept Invite
|
||||
</a>
|
||||
<a
|
||||
className="dib pointer ml4 pa2 f9 bg-black bg-gray0-d white mt4"
|
||||
onClick={this.onDecline.bind(this)}>
|
||||
onClick={this.onDecline.bind(this)}
|
||||
>
|
||||
Decline
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { SidebarInvite } from './sidebar-invite';
|
||||
import { Welcome } from './welcome';
|
||||
import { GroupItem } from './group-item';
|
||||
import { alphabetiseAssociations } from '../../lib/util';
|
||||
import { alphabetiseAssociations } from '../../../../lib/util';
|
||||
|
||||
export class Sidebar extends Component {
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let activeClasses = (props.active === "sidebar") ? " " : "dn-s ";
|
||||
const { props } = this;
|
||||
const activeClasses = (props.active === 'sidebar') ? ' ' : 'dn-s ';
|
||||
let hiddenClasses = true;
|
||||
if (props.popout) {
|
||||
hiddenClasses = false;
|
||||
@ -16,7 +16,7 @@ export class Sidebar extends Component {
|
||||
hiddenClasses = props.sidebarShown;
|
||||
};
|
||||
|
||||
let sidebarInvites = !(props.invites && props.invites['/publish'])
|
||||
const sidebarInvites = !(props.invites && props.invites['/publish'])
|
||||
? null
|
||||
: Object.keys(props.invites['/publish'])
|
||||
.map((uid, i) => {
|
||||
@ -24,35 +24,37 @@ export class Sidebar extends Component {
|
||||
<SidebarInvite
|
||||
uid={uid}
|
||||
invite={props.invites['/publish'][uid]}
|
||||
key={i} />
|
||||
)
|
||||
api={this.props.api}
|
||||
key={i}
|
||||
/>
|
||||
);
|
||||
});
|
||||
let associations = !!props.associations ? alphabetiseAssociations(props.associations.contacts) : {};
|
||||
const associations = props.associations ? alphabetiseAssociations(props.associations.contacts) : {};
|
||||
|
||||
let notebooks = {};
|
||||
Object.keys(props.notebooks).map(host => {
|
||||
Object.keys(props.notebooks[host]).map(notebook => {
|
||||
let title = `${host}/${notebook}`;
|
||||
const notebooks = {};
|
||||
Object.keys(props.notebooks).map((host) => {
|
||||
Object.keys(props.notebooks[host]).map((notebook) => {
|
||||
const title = `${host}/${notebook}`;
|
||||
notebooks[title] = props.notebooks[host][notebook];
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
let groupedNotebooks = {};
|
||||
Object.keys(notebooks).map(book => {
|
||||
if (notebooks[book]["subscribers-group-path"].startsWith("/~/")) {
|
||||
if (groupedNotebooks["/~/"]) {
|
||||
let array = groupedNotebooks["/~/"];
|
||||
const groupedNotebooks = {};
|
||||
Object.keys(notebooks).map((book) => {
|
||||
if (notebooks[book]['subscribers-group-path'].startsWith('/~/')) {
|
||||
if (groupedNotebooks['/~/']) {
|
||||
const array = groupedNotebooks['/~/'];
|
||||
array.push(book);
|
||||
groupedNotebooks["/~/"] = array;
|
||||
groupedNotebooks['/~/'] = array;
|
||||
} else {
|
||||
groupedNotebooks["/~/"] = [book];
|
||||
groupedNotebooks['/~/'] = [book];
|
||||
};
|
||||
};
|
||||
let path = !!notebooks[book]["subscribers-group-path"]
|
||||
? notebooks[book]["subscribers-group-path"] : book;
|
||||
const path = notebooks[book]['subscribers-group-path']
|
||||
? notebooks[book]['subscribers-group-path'] : book;
|
||||
if (path in associations) {
|
||||
if (groupedNotebooks[path]) {
|
||||
let array = groupedNotebooks[path];
|
||||
const array = groupedNotebooks[path];
|
||||
array.push(book);
|
||||
groupedNotebooks[path] = array;
|
||||
} else {
|
||||
@ -61,24 +63,27 @@ export class Sidebar extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
let selectedGroups = !!props.selectedGroups ? props.selectedGroups: [];
|
||||
let groupedItems = Object.keys(associations)
|
||||
const selectedGroups = props.selectedGroups ? props.selectedGroups: [];
|
||||
const groupedItems = Object.keys(associations)
|
||||
.filter((each) => {
|
||||
if (selectedGroups.length === 0) {
|
||||
return true;
|
||||
}
|
||||
let selectedPaths = selectedGroups.map((e) => { return e[0] });
|
||||
const selectedPaths = selectedGroups.map((e) => {
|
||||
return e[0];
|
||||
});
|
||||
return (selectedPaths.includes(each));
|
||||
})
|
||||
.map((each, i) => {
|
||||
let books = groupedNotebooks[each] || [];
|
||||
if (books.length === 0) return;
|
||||
const books = groupedNotebooks[each] || [];
|
||||
if (books.length === 0)
|
||||
return;
|
||||
if ((selectedGroups.length === 0) &&
|
||||
groupedNotebooks["/~/"] &&
|
||||
groupedNotebooks["/~/"].length !== 0) {
|
||||
groupedNotebooks['/~/'] &&
|
||||
groupedNotebooks['/~/'].length !== 0) {
|
||||
i = i + 1;
|
||||
}
|
||||
return(
|
||||
return (
|
||||
<GroupItem
|
||||
key={i}
|
||||
index={i}
|
||||
@ -87,31 +92,32 @@ export class Sidebar extends Component {
|
||||
notebooks={notebooks}
|
||||
path={props.path}
|
||||
/>
|
||||
)
|
||||
})
|
||||
);
|
||||
});
|
||||
if ((selectedGroups.length === 0) &&
|
||||
groupedNotebooks["/~/"] &&
|
||||
groupedNotebooks["/~/"].length !== 0) {
|
||||
groupedNotebooks['/~/'] &&
|
||||
groupedNotebooks['/~/'].length !== 0) {
|
||||
groupedItems.unshift(
|
||||
<GroupItem
|
||||
key={"/~/"}
|
||||
key={'/~/'}
|
||||
index={0}
|
||||
association={"/~/"}
|
||||
groupedBooks={groupedNotebooks["/~/"]}
|
||||
association={'/~/'}
|
||||
groupedBooks={groupedNotebooks['/~/']}
|
||||
notebooks={notebooks}
|
||||
path={props.path}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100 " +
|
||||
"flex-shrink-0 pt3 pt0-m pt0-l pt0-xl relative " +
|
||||
"overflow-y-hidden " + activeClasses +
|
||||
(hiddenClasses ? "flex-basis-100-s flex-basis-250-ns" : "dn")
|
||||
}>
|
||||
'bn br-m br-l br-xl b--gray4 b--gray1-d lh-copy h-100 ' +
|
||||
'flex-shrink-0 pt3 pt0-m pt0-l pt0-xl relative ' +
|
||||
'overflow-y-hidden ' + activeClasses +
|
||||
(hiddenClasses ? 'flex-basis-100-s flex-basis-250-ns' : 'dn')
|
||||
}
|
||||
>
|
||||
<a className="db dn-m dn-l dn-xl f9 pb3 pl3" href="/">
|
||||
⟵ Landscape
|
||||
</a>
|
||||
@ -124,8 +130,9 @@ export class Sidebar extends Component {
|
||||
</Link>
|
||||
</div>
|
||||
<div className="overflow-y-auto pb1"
|
||||
style={{height: "calc(100% - 82px)"}}>
|
||||
<Welcome notebooks={props.notebooks}/>
|
||||
style={{ height: 'calc(100% - 82px)' }}
|
||||
>
|
||||
<Welcome notebooks={props.notebooks} />
|
||||
{sidebarInvites}
|
||||
{groupedItems}
|
||||
</div>
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Dropdown } from './dropdown';
|
||||
import { cite } from '../../lib/util';
|
||||
import { cite } from '../../../../lib/util';
|
||||
|
||||
export class Subscribers extends Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.redirect = this.redirect.bind(this);
|
||||
this.addUser = this.addUser.bind(this);
|
||||
@ -11,23 +11,23 @@ export class Subscribers extends Component {
|
||||
}
|
||||
|
||||
addUser(who, path) {
|
||||
let action = {
|
||||
const action = {
|
||||
add: {
|
||||
members: [who],
|
||||
path: path,
|
||||
path: path
|
||||
}
|
||||
}
|
||||
window.api.action("group-store", "group-action", action);
|
||||
};
|
||||
this.props.api.action('group-store', 'group-action', action);
|
||||
}
|
||||
|
||||
removeUser(who, path) {
|
||||
let action = {
|
||||
const action = {
|
||||
remove: {
|
||||
members: [who],
|
||||
path: path,
|
||||
path: path
|
||||
}
|
||||
}
|
||||
window.api.action("group-store", "group-action", action);
|
||||
};
|
||||
this.props.api.action('group-store', 'group-action', action);
|
||||
}
|
||||
|
||||
redirect(url) {
|
||||
@ -35,36 +35,40 @@ export class Subscribers extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let readPath = this.props.notebook["subscribers-group-path"]
|
||||
let readPerms = (readPath)
|
||||
const readPath = this.props.notebook['subscribers-group-path'];
|
||||
const readPerms = (readPath)
|
||||
? this.props.permissions[readPath]
|
||||
: null;
|
||||
let writePath = this.props.notebook["writers-group-path"]
|
||||
let writePerms = (writePath)
|
||||
const writePath = this.props.notebook['writers-group-path'];
|
||||
const writePerms = (writePath)
|
||||
? this.props.permissions[writePath]
|
||||
: null;
|
||||
|
||||
let writers = [];
|
||||
if (writePerms && writePerms.kind === 'white') {
|
||||
let withoutUs = new Set(writePerms.who)
|
||||
const withoutUs = new Set(writePerms.who);
|
||||
withoutUs.delete(window.ship);
|
||||
writers = Array.from(withoutUs).map((who, i) => {
|
||||
let width = 0;
|
||||
let options = [];
|
||||
if (readPath === writePath) {
|
||||
width = 258;
|
||||
let url = `/~groups${writePath}`;
|
||||
const url = `/~groups${writePath}`;
|
||||
options = [{
|
||||
cls: "bg-transparent white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d ph2 pv3",
|
||||
txt: "Manage this group",
|
||||
action: () => {this.redirect(url)}
|
||||
cls: 'bg-transparent white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d ph2 pv3',
|
||||
txt: 'Manage this group',
|
||||
action: () => {
|
||||
this.redirect(url);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
width = 157;
|
||||
options = [{
|
||||
cls: "bg-transparent white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d ph2 pv3",
|
||||
txt: "Demote to subscriber",
|
||||
action: () => {this.removeUser(`~${who}`, writePath)}
|
||||
cls: 'bg-transparent white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d ph2 pv3',
|
||||
txt: 'Demote to subscriber',
|
||||
action: () => {
|
||||
this.removeUser(`~${who}`, writePath);
|
||||
}
|
||||
}];
|
||||
}
|
||||
return (
|
||||
@ -73,10 +77,10 @@ export class Subscribers extends Component {
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={"Options"}
|
||||
buttonText={'Options'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -84,23 +88,27 @@ export class Subscribers extends Component {
|
||||
writers =
|
||||
<div className="f9">
|
||||
There are no participants on this notebook.
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
let subscribers = null;
|
||||
if (readPath !== writePath) {
|
||||
if (this.props.notebook.subscribers){
|
||||
let width = 162;
|
||||
if (this.props.notebook.subscribers) {
|
||||
const width = 162;
|
||||
subscribers = this.props.notebook.subscribers.map((who, i) => {
|
||||
let options = [
|
||||
{ cls: "white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d bg-transparent ph2 pv3",
|
||||
txt: "Promote to participant",
|
||||
action: () => {this.addUser(who, writePath)}
|
||||
},
|
||||
{ cls: "tl red2 pointer w-100 db hover-bg-gray4 hover-bg-gray1-d bg-transparent ph2 pv3",
|
||||
txt: "Ban",
|
||||
action: () => {this.addUser(who, readPath)}
|
||||
const options = [
|
||||
{ cls: 'white-d tl pointer w-100 db hover-bg-gray4 hover-bg-gray1-d bg-transparent ph2 pv3',
|
||||
txt: 'Promote to participant',
|
||||
action: () => {
|
||||
this.addUser(who, writePath);
|
||||
}
|
||||
},
|
||||
{ cls: 'tl red2 pointer w-100 db hover-bg-gray4 hover-bg-gray1-d bg-transparent ph2 pv3',
|
||||
txt: 'Ban',
|
||||
action: () => {
|
||||
this.addUser(who, readPath);
|
||||
}
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div className="flex justify-between" key={i}>
|
||||
@ -108,36 +116,37 @@ export class Subscribers extends Component {
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={"Options"}
|
||||
buttonText={'Options'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
if (subscribers.length === 0) {
|
||||
subscribers =
|
||||
<div className="f9">
|
||||
There are no subscribers to this notebook.
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let subsContainer = (readPath === writePath)
|
||||
const subsContainer = (readPath === writePath)
|
||||
? null
|
||||
: <div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">Subscribers (read access only)</div>
|
||||
{subscribers}
|
||||
</div>;
|
||||
|
||||
|
||||
let bannedContainer = null;
|
||||
if (readPerms && readPerms.kind === 'black') {
|
||||
let width = 72;
|
||||
const width = 72;
|
||||
let banned = Array.from(readPerms.who).map((who, i) => {
|
||||
let options = [{
|
||||
cls: "tl red2 pointer",
|
||||
txt: "Unban",
|
||||
action: () => {this.removeUser(`~${who}`, readPath)}
|
||||
const options = [{
|
||||
cls: 'tl red2 pointer',
|
||||
txt: 'Unban',
|
||||
action: () => {
|
||||
this.removeUser(`~${who}`, readPath);
|
||||
}
|
||||
}];
|
||||
return (
|
||||
<div className="flex justify-between" key={i}>
|
||||
@ -145,16 +154,16 @@ export class Subscribers extends Component {
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={"Options"}
|
||||
buttonText={'Options'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
});
|
||||
if (banned.length === 0) {
|
||||
banned =
|
||||
<div className="f9">
|
||||
There are no users banned from this notebook.
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
bannedContainer =
|
||||
<div className="flex flex-column">
|
||||
@ -163,7 +172,6 @@ export class Subscribers extends Component {
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-column">
|
||||
@ -181,8 +189,8 @@ export class Subscribers extends Component {
|
||||
{subsContainer}
|
||||
{bannedContainer}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Subscribers
|
||||
export default Subscribers;
|
@ -1,41 +1,41 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
|
||||
export class Welcome extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
show: true
|
||||
}
|
||||
};
|
||||
this.disableWelcome = this.disableWelcome.bind(this);
|
||||
}
|
||||
|
||||
disableWelcome() {
|
||||
this.setState({ show: false });
|
||||
localStorage.setItem("urbit-publish:wasWelcomed", JSON.stringify(true));
|
||||
localStorage.setItem('urbit-publish:wasWelcomed', JSON.stringify(true));
|
||||
}
|
||||
|
||||
render() {
|
||||
let wasWelcomed = localStorage.getItem("urbit-publish:wasWelcomed");
|
||||
let wasWelcomed = localStorage.getItem('urbit-publish:wasWelcomed');
|
||||
if (wasWelcomed === null) {
|
||||
localStorage.setItem("urbit-publish:wasWelcomed", JSON.stringify(false));
|
||||
localStorage.setItem('urbit-publish:wasWelcomed', JSON.stringify(false));
|
||||
return wasWelcomed = false;
|
||||
} else {
|
||||
wasWelcomed = JSON.parse(wasWelcomed);
|
||||
}
|
||||
|
||||
let notebooks = !!this.props.notebooks ? this.props.notebooks : {};
|
||||
const notebooks = this.props.notebooks ? this.props.notebooks : {};
|
||||
|
||||
return ((!wasWelcomed && this.state.show) && (notebooks.length !== 0)) ? (
|
||||
<div className="ma4 pa2 white-d bg-welcome-green bg-gray1-d">
|
||||
<p className="f8 lh-copy">Notebooks are for longer-form writing and discussion. Each Notebook is a collection of Markdown-formatted notes with optional comments.</p>
|
||||
<p className="f8 pt2 dib pointer bb"
|
||||
onClick={(() => this.disableWelcome())}>
|
||||
onClick={(() => this.disableWelcome())}
|
||||
>
|
||||
Close this
|
||||
</p>
|
||||
</div>
|
||||
) : <div />
|
||||
) : <div />;
|
||||
}
|
||||
}
|
||||
|
||||
export default Welcome
|
||||
export default Welcome;
|
47
pkg/interface/src/apps/publish/components/skeleton.js
Normal file
47
pkg/interface/src/apps/publish/components/skeleton.js
Normal file
@ -0,0 +1,47 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Sidebar } from './lib/sidebar';
|
||||
|
||||
export class Skeleton extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const rightPanelHide = props.rightPanelHide
|
||||
? 'dn-s' : '';
|
||||
|
||||
const popout = props.popout
|
||||
? props.popout : false;
|
||||
|
||||
const popoutWindow = (popout)
|
||||
? '' : 'h-100-m-40-ns ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl';
|
||||
|
||||
const popoutBorder = (popout)
|
||||
? '': 'ba-m ba-l ba-xl b--gray4 b--gray1-d br1';
|
||||
|
||||
return (
|
||||
<div className={'absolute h-100 w-100 ' + popoutWindow}>
|
||||
<div className={'cf w-100 h-100 flex ' + popoutBorder}>
|
||||
<Sidebar
|
||||
popout={popout}
|
||||
sidebarShown={props.sidebarShown}
|
||||
active={props.active}
|
||||
notebooks={props.notebooks}
|
||||
contacts={props.contacts}
|
||||
path={props.path}
|
||||
invites={props.invites}
|
||||
associations={props.associations}
|
||||
selectedGroups={props.selectedGroups}
|
||||
api={this.props.api}
|
||||
/>
|
||||
<div className={'h-100 w-100 relative white-d flex-auto ' + rightPanelHide} style={{
|
||||
flexGrow: 1
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Skeleton;
|
@ -5,47 +5,6 @@
|
||||
--light-gray: rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow: hidden;
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
|
||||
margin-block-end: unset;
|
||||
margin-block-start: unset;
|
||||
-webkit-margin-before: unset;
|
||||
-webkit-margin-after: unset;
|
||||
font-family: Inter, sans-serif;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea, input, button {
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.inter {
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
}
|
||||
|
||||
.bg-welcome-green {
|
||||
background-color: #ECF6F2;
|
||||
}
|
||||
@ -81,14 +40,6 @@ a {
|
||||
background: rgba(42, 167, 121, 0.1);
|
||||
}
|
||||
|
||||
.focus-b--black:focus {
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
.mix-blend-diff {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.NotebookButton {
|
||||
padding: 8px 12px;
|
||||
border-radius:2px;
|
||||
@ -113,29 +64,6 @@ a {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
|
||||
/* toggler checkbox */
|
||||
.toggle::after {
|
||||
content: "";
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.toggle.checked::after {
|
||||
content: "";
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 14px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.react-codemirror2 {
|
||||
width: 100%;
|
||||
}
|
||||
@ -277,26 +205,6 @@ md img {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.focus-b--black:focus {
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
.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);}
|
||||
}
|
||||
|
||||
.mix-blend-diff {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
@media all and (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #333;
|
68
pkg/interface/src/apps/publish/store.js
Normal file
68
pkg/interface/src/apps/publish/store.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { InitialReducer } from './reducers/initial';
|
||||
import { PrimaryReducer } from './reducers/primary';
|
||||
import { ResponseReducer } from './reducers/response';
|
||||
import { GroupReducer } from './reducers/group';
|
||||
import { InviteReducer } from './reducers/invite';
|
||||
import { PermissionReducer } from './reducers/permission';
|
||||
import MetadataReducer from '../../reducers/metadata-update';
|
||||
|
||||
export default class Store {
|
||||
constructor() {
|
||||
this.state = this.initialState();
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
this.primaryReducer = new PrimaryReducer();
|
||||
this.responseReducer = new ResponseReducer();
|
||||
this.groupReducer = new GroupReducer();
|
||||
this.inviteReducer = new InviteReducer();
|
||||
this.permissionReducer = new PermissionReducer();
|
||||
this.metadataReducer = new MetadataReducer();
|
||||
this.setState = () => {};
|
||||
}
|
||||
|
||||
initialState() {
|
||||
return {
|
||||
notebooks: {},
|
||||
groups: {},
|
||||
contacts: {},
|
||||
associations: {
|
||||
contacts: {}
|
||||
},
|
||||
permissions: {},
|
||||
invites: {},
|
||||
selectedGroups: [],
|
||||
sidebarShown: true
|
||||
};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.handleEvent({
|
||||
data: { clear: true }
|
||||
});
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
handleEvent(evt) {
|
||||
if (evt.data && 'clear' in evt.data && evt.data.clear) {
|
||||
this.setState(this.initialState());
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.from && evt.from.path === '/all') {
|
||||
this.groupReducer.reduce(evt.data, this.state);
|
||||
this.permissionReducer.reduce(evt.data, this.state);
|
||||
} else if (evt.from && evt.from.path === '/app-name/contacts') {
|
||||
this.metadataReducer.reduce(evt.data, this.state);
|
||||
} else if (evt.from && evt.from.path === '/primary') {
|
||||
this.primaryReducer.reduce(evt.data, this.state);
|
||||
this.inviteReducer.reduce(evt.data, this.state);
|
||||
} else if (evt.type) {
|
||||
this.responseReducer.reduce(evt, this.state);
|
||||
}
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
65
pkg/interface/src/apps/publish/subscription.js
Normal file
65
pkg/interface/src/apps/publish/subscription.js
Normal file
@ -0,0 +1,65 @@
|
||||
export default class Subscription {
|
||||
constructor(store, api, channel) {
|
||||
this.store = store;
|
||||
this.api = api;
|
||||
this.channel = channel;
|
||||
|
||||
this.channel.setOnChannelError(this.onChannelError.bind(this));
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.api.ship) {
|
||||
this.initializePublish();
|
||||
} else {
|
||||
console.error('~~~ ERROR: Must set api.ship before operation ~~~');
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.channel.delete();
|
||||
}
|
||||
|
||||
onChannelError(err) {
|
||||
console.error('event source error: ', err);
|
||||
console.log('initiating new channel');
|
||||
setTimeout(2000, () => {
|
||||
this.store.handleEvent({
|
||||
data: { clear : true }
|
||||
});
|
||||
|
||||
this.start();
|
||||
});
|
||||
}
|
||||
|
||||
initializePublish() {
|
||||
this.api.bind('/primary', 'PUT', this.api.ship, 'publish',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
this.api.bind('/all', 'PUT', this.api.ship, 'group-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
this.api.bind('/primary', 'PUT', this.api.ship, 'contact-view',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
this.api.bind('/primary', 'PUT', this.api.ship, 'invite-view',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
this.api.bind('/all', 'PUT', this.api.ship, 'permission-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
this.api.bind('/app-name/contacts', 'PUT', this.api.ship, 'metadata-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
|
||||
handleEvent(diff) {
|
||||
this.store.handleEvent(diff);
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
console.error(err);
|
||||
this.api.bind('/primary', 'PUT', this.api.ship, 'publish',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ const getLocationName = (basePath) => {
|
||||
return 'Groups';
|
||||
if (basePath === '~link')
|
||||
return 'Links';
|
||||
// if (basePath === '~publish') return 'Publish';
|
||||
if (basePath === '~publish')
|
||||
return 'Publish';
|
||||
};
|
||||
|
||||
const StatusBar = (props) => {
|
||||
|
@ -248,4 +248,25 @@ export function getContactDetails(contact) {
|
||||
const color = uxToHex(contact.color || "0x0");
|
||||
const avatar = contact.avatar || null;
|
||||
return { nickname, color, member, avatar };
|
||||
}
|
||||
}
|
||||
|
||||
export function stringToSymbol(str) {
|
||||
let result = '';
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var n = str.charCodeAt(i);
|
||||
if (((n >= 97) && (n <= 122)) ||
|
||||
((n >= 48) && (n <= 57))) {
|
||||
result += str[i];
|
||||
} else if ((n >= 65) && (n <= 90)) {
|
||||
result += String.fromCharCode(n + 32);
|
||||
} else {
|
||||
result += '-';
|
||||
}
|
||||
}
|
||||
result = result.replace(/^[\-\d]+|\-+/g, '-');
|
||||
result = result.replace(/^\-+|\-+$/g, '');
|
||||
if (result === '') {
|
||||
return dateToDa(new Date());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1,226 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
var cssimport = require('gulp-cssimport');
|
||||
var rollup = require('gulp-better-rollup');
|
||||
var cssnano = require('cssnano');
|
||||
var postcss = require('gulp-postcss')
|
||||
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 rollupReplace = require("@rollup/plugin-replace");
|
||||
|
||||
|
||||
var resolve = require('rollup-plugin-node-resolve');
|
||||
var commonjs = require('rollup-plugin-commonjs');
|
||||
var rootImport = require('rollup-plugin-root-import');
|
||||
var globals = require('rollup-plugin-node-globals');
|
||||
|
||||
|
||||
/***
|
||||
Main config options
|
||||
***/
|
||||
|
||||
var urbitrc = require('../urbitrc');
|
||||
|
||||
/***
|
||||
End main config options
|
||||
***/
|
||||
|
||||
gulp.task('css-bundle', function() {
|
||||
let plugins = [
|
||||
cssnano()
|
||||
];
|
||||
return gulp
|
||||
.src('src/index.css')
|
||||
.pipe(cssimport())
|
||||
.pipe(postcss(plugins))
|
||||
.pipe(gulp.dest('../../arvo/app/publish/css'));
|
||||
});
|
||||
|
||||
gulp.task('jsx-transform', function(cb) {
|
||||
return gulp.src('src/**/*.js')
|
||||
.pipe(sucrase({
|
||||
transforms: ['jsx']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('tile-jsx-transform', function(cb) {
|
||||
return gulp.src('tile/**/*.js')
|
||||
.pipe(sucrase({
|
||||
transforms: ['jsx']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('js-imports', function(cb) {
|
||||
return gulp.src('dist/index.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': ['Component', 'cloneElement',
|
||||
'createContext', 'createElement', 'useState', 'useRef',
|
||||
'useLayoutEffect', 'useMemo', 'useEffect', 'forwardRef', 'useContext', 'Children' ],
|
||||
'node_modules/react-is/index.js': [ 'isValidElementType', 'isElement', 'ForwardRef' ],
|
||||
'node_modules/react-dom/index.js': [ 'createPortal' ]
|
||||
}
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
json(),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('../../arvo/app/publish/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
gulp.task('tile-js-imports', function(cb) {
|
||||
return gulp.src('dist/tile.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': [ 'Component' ],
|
||||
}
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
json(),
|
||||
globals(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('../../arvo/app/publish/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
gulp.task('js-imports-prod', function(cb) {
|
||||
return gulp.src('dist/index.js')
|
||||
.pipe(rollup({
|
||||
plugins: [
|
||||
rollupReplace({'process.env.NODE_ENV': JSON.stringify('production')}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/react/index.js': ['Component', 'cloneElement',
|
||||
'createContext', 'createElement', 'useState', 'useRef',
|
||||
'useLayoutEffect', 'useMemo', 'useEffect', 'forwardRef', 'useContext', 'Children' ],
|
||||
'node_modules/react-is/index.js': [ 'isValidElementType', 'isElement', 'ForwardRef' ],
|
||||
'node_modules/react-dom/index.js': [ 'createPortal' ]
|
||||
}
|
||||
}),
|
||||
rootImport({
|
||||
root: `${__dirname}/dist/js`,
|
||||
useEntry: 'prepend',
|
||||
extensions: '.js'
|
||||
}),
|
||||
globals(),
|
||||
json(),
|
||||
resolve()
|
||||
]
|
||||
}, 'umd'))
|
||||
.on('error', function(e){
|
||||
console.log(e);
|
||||
cb();
|
||||
})
|
||||
.pipe(gulp.dest('../../arvo/app/publish/js/'))
|
||||
.on('end', cb);
|
||||
});
|
||||
|
||||
|
||||
gulp.task('js-minify', function () {
|
||||
return gulp.src('../../arvo/app/publish/js/index.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('../../arvo/app/publish/js/'));
|
||||
});
|
||||
|
||||
gulp.task('tile-js-minify', function () {
|
||||
return gulp.src('../../arvo/app/publish/js/tile.js')
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest('../../arvo/app/publish/js/'));
|
||||
});
|
||||
|
||||
gulp.task('rename-index-min', function() {
|
||||
return gulp.src('../../arvo/app/publish/js/index-min.js')
|
||||
.pipe(rename('index.js'))
|
||||
.pipe(gulp.dest('../../arvo/app/publish/js/'));
|
||||
});
|
||||
|
||||
gulp.task('rename-tile-min', function() {
|
||||
return gulp.src('../../arvo/app/publish/js/tile-min.js')
|
||||
.pipe(rename('tile.js'))
|
||||
.pipe(gulp.dest('../../arvo/app/publish/js/'));
|
||||
});
|
||||
|
||||
gulp.task('clean-min', function() {
|
||||
return del(['../../arvo/app/publish/js/index-min.js', '../../arvo/app/publish/js/tile-min.js'], {force: true})
|
||||
});
|
||||
|
||||
gulp.task('urbit-copy', function () {
|
||||
let ret = gulp.src('../../arvo/**/*');
|
||||
|
||||
urbitrc.URBIT_PIERS.forEach(function(pier) {
|
||||
ret = ret.pipe(gulp.dest(pier));
|
||||
});
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
gulp.task('js-bundle-dev', gulp.series('jsx-transform', 'js-imports'));
|
||||
gulp.task('tile-js-bundle-dev', gulp.series('tile-jsx-transform', 'tile-js-imports'));
|
||||
gulp.task('js-bundle-prod', gulp.series('jsx-transform', 'js-imports-prod', 'js-minify'))
|
||||
gulp.task('tile-js-bundle-prod',
|
||||
gulp.series('tile-jsx-transform', 'tile-js-imports', 'tile-js-minify'));
|
||||
|
||||
gulp.task('bundle-dev',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-dev',
|
||||
'tile-js-bundle-dev'
|
||||
),
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('bundle-prod',
|
||||
gulp.series(
|
||||
gulp.parallel(
|
||||
'css-bundle',
|
||||
'js-bundle-prod',
|
||||
'tile-js-bundle-prod',
|
||||
),
|
||||
'rename-index-min',
|
||||
'rename-tile-min',
|
||||
'clean-min',
|
||||
'urbit-copy'
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task('default', gulp.series('bundle-dev'));
|
||||
|
||||
gulp.task('watch', gulp.series('default', function() {
|
||||
gulp.watch('tile/**/*.js', gulp.parallel('tile-js-bundle-dev'));
|
||||
|
||||
gulp.watch('src/**/*.js', gulp.parallel('js-bundle-dev'));
|
||||
gulp.watch('src/**/*.css', gulp.parallel('css-bundle'));
|
||||
|
||||
gulp.watch('../../arvo/**/*', gulp.parallel('urbit-copy'));
|
||||
}));
|
6354
pkg/interface/src/oldApps/publish/package-lock.json
generated
6354
pkg/interface/src/oldApps/publish/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,47 +0,0 @@
|
||||
{
|
||||
"name": "urbit-apps",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-replace": "^2.3.0",
|
||||
"@sucrase/gulp-plugin": "^2.0.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-better-rollup": "^4.0.1",
|
||||
"gulp-cssimport": "^7.0.0",
|
||||
"gulp-minify": "^3.1.0",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"rollup": "^1.6.0",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"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",
|
||||
"sucrase": "^3.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"codemirror": "^5.51.0",
|
||||
"del": "^5.1.0",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.20.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"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",
|
||||
"urbit-ob": "^5.0.0",
|
||||
"urbit-sigil-js": "^1.3.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"natives": "1.1.3"
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-Bold.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url("https://media.urbit.org/fonts/Inter-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-extralight.woff");
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-light.woff");
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-regular.woff");
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-medium.woff");
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-semibold.woff");
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Source Code Pro";
|
||||
src: url("https://storage.googleapis.com/media.urbit.org/fonts/scp-bold.woff");
|
||||
font-weight: 700;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,40 +0,0 @@
|
||||
|
||||
|
||||
.spinner-pending {
|
||||
position: relative;
|
||||
content: "";
|
||||
border-radius: 100%;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
|
||||
background-color: rgba(255,255,255,1);
|
||||
}
|
||||
|
||||
.spinner-pending::after {
|
||||
content: "";
|
||||
background-color: rgba(128,128,128,1);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
clip: rect(0, 16px, 16px, 8px);
|
||||
|
||||
animation: spin 1s cubic-bezier(0.745, 0.045, 0.355, 1.000) infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {transform:rotate(0deg)}
|
||||
25% {transform:rotate(90deg)}
|
||||
50% {transform:rotate(180deg)}
|
||||
75% {transform:rotate(270deg)}
|
||||
100% {transform:rotate(360deg)}
|
||||
}
|
||||
|
||||
|
||||
.spinner-nostart {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 100%;
|
||||
content:'';
|
||||
background-color: black;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
@import 'css/indigo-static.css';
|
||||
@import 'css/fonts.css';
|
||||
@import 'css/custom.css';
|
||||
@import 'css/spinner.css';
|
||||
|
||||
@import '../node_modules/codemirror/lib/codemirror.css';
|
||||
@import '../node_modules/codemirror/theme/material.css';
|
@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Root } from '/components/root';
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
import { subscription } from "/subscription";
|
||||
|
||||
api.setAuthTokens({
|
||||
ship: window.ship
|
||||
});
|
||||
|
||||
subscription.start();
|
||||
|
||||
ReactDOM.render((
|
||||
<Root />
|
||||
), document.querySelectorAll("#root")[0]);
|
@ -1,26 +0,0 @@
|
||||
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,231 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
|
||||
export class GroupFilter extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
open: false,
|
||||
selected: [],
|
||||
groups: [],
|
||||
searchTerm: "",
|
||||
results: []
|
||||
}
|
||||
this.toggleOpen = this.toggleOpen.bind(this);
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
this.groupIndex = this.groupIndex.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
this.addGroup = this.addGroup.bind(this);
|
||||
this.deleteGroup = this.deleteGroup.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('mousedown', this.handleClickOutside);
|
||||
this.groupIndex();
|
||||
let selected = localStorage.getItem("urbit-selectedGroups");
|
||||
if (selected) {
|
||||
this.setState({selected: JSON.parse(selected)}, (() => {
|
||||
window.api.setSelected(this.state.selected);
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('mousedown', this.handleClickOutside);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps !== this.props) {
|
||||
this.groupIndex();
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOutside(evt) {
|
||||
if ((this.dropdown && !this.dropdown.contains(evt.target))
|
||||
&& (this.toggleButton && !this.toggleButton.contains(evt.target))) {
|
||||
this.setState({ open: false });
|
||||
}
|
||||
}
|
||||
|
||||
toggleOpen() {
|
||||
this.setState({open: !this.state.open});
|
||||
}
|
||||
|
||||
groupIndex() {
|
||||
const { props, state } = this;
|
||||
let index = [];
|
||||
let associations = !!props.associations ? props.associations.contacts : {};
|
||||
index = Object.keys(associations).map((each) => {
|
||||
let eachGroup = [];
|
||||
eachGroup.push(each);
|
||||
let name = each;
|
||||
if (associations[each].metadata) {
|
||||
name = (associations[each].metadata.title !== "")
|
||||
? associations[each].metadata.title : name;
|
||||
}
|
||||
eachGroup.push(name);
|
||||
return eachGroup;
|
||||
});
|
||||
this.setState({groups: index})
|
||||
}
|
||||
|
||||
search(evt) {
|
||||
this.setState({searchTerm: evt.target.value});
|
||||
let term = evt.target.value.toLowerCase();
|
||||
|
||||
if (term.length < 3) {
|
||||
return this.setState({results: []})
|
||||
}
|
||||
|
||||
let groupMatches = [];
|
||||
groupMatches = this.state.groups.filter(e => {
|
||||
return (e[0].includes(term) || e[1].includes(term));
|
||||
});
|
||||
this.setState({results: groupMatches});
|
||||
}
|
||||
|
||||
addGroup(group) {
|
||||
let selected = this.state.selected;
|
||||
if (!(group in selected)) {
|
||||
selected.push(group);
|
||||
}
|
||||
this.setState({
|
||||
searchTerm: "",
|
||||
selected: selected,
|
||||
results: []
|
||||
}, (() => {
|
||||
window.api.setSelected(this.state.selected);
|
||||
localStorage.setItem("urbit-selectedGroups", JSON.stringify(this.state.selected));
|
||||
}))
|
||||
}
|
||||
|
||||
deleteGroup(group) {
|
||||
let selected = this.state.selected;
|
||||
selected = selected.filter(e => {
|
||||
return e !== group;
|
||||
});
|
||||
this.setState({selected: selected}, (() => {
|
||||
window.api.setSelected(this.state.selected);
|
||||
localStorage.setItem("urbit-selectedGroups", JSON.stringify(this.state.selected));
|
||||
}))
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let currentGroup = "All Groups";
|
||||
|
||||
if (state.selected.length > 0) {
|
||||
let titles = state.selected.map((each) => {
|
||||
return each[1];
|
||||
})
|
||||
currentGroup = titles.join(" + ");
|
||||
}
|
||||
|
||||
let buttonOpened = (state.open)
|
||||
? "bg-gray5 bg-gray1-d white-d" : "hover-bg-gray5 hover-bg-gray1-d white-d";
|
||||
|
||||
let dropdownClass = (state.open)
|
||||
? "absolute db z-2 bg-white bg-gray0-d white-d ba b--gray3 b--gray1-d"
|
||||
: "dn";
|
||||
|
||||
let inviteCount = (props.invites && Object.keys(props.invites).length > 0)
|
||||
? <template className="dib fr">
|
||||
<p className="dib bg-green2 bg-gray2-d white fw6 ph1 br1 v-mid" style={{ marginBottom: 2 }}>
|
||||
{Object.keys(props.invites).length}
|
||||
</p>
|
||||
<span className="dib v-mid ml1">
|
||||
<img
|
||||
className="v-mid"
|
||||
src="/~landscape/img/Chevron.png"
|
||||
style={{ height: 16, width: 16, paddingBottom: 1 }}
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
: <template className="dib fr" style={{paddingTop: 1}}>
|
||||
<span className="dib v-top ml1">
|
||||
<img className="v-mid"
|
||||
src="/~landscape/img/Chevron.png"
|
||||
style={{ height: 16, width: 16, paddingBottom: 1 }}
|
||||
/>
|
||||
</span>
|
||||
</template>;
|
||||
|
||||
let selectedGroups = <div/>
|
||||
let searchResults = <div/>
|
||||
|
||||
if (state.results.length > 0) {
|
||||
let groupResults = state.results.map((group => {
|
||||
return(
|
||||
<li
|
||||
key={group[0]}
|
||||
className="tl list white-d f9 pv2 ph3 pointer hover-bg-gray4 hover-bg-gray1-d inter" onClick={() => this.addGroup(group)}>
|
||||
<span className="mix-blend-diff white">{(group[1]) ? group[1] : group[0]}</span>
|
||||
</li>
|
||||
)
|
||||
}))
|
||||
searchResults = (
|
||||
<div className={"tl absolute bg-white bg-gray0-d white-d pv3 z-1 w-100 ba b--gray4 b--white-d overflow-y-scroll"} style={{maxWidth: "15.67rem", maxHeight: "8rem"}}>
|
||||
<p className="f9 tl gray2 ph3 pb2">Groups</p>
|
||||
{groupResults}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (state.selected.length > 0) {
|
||||
let allSelected = this.state.selected.map((each) => {
|
||||
let name = each[1];
|
||||
return(
|
||||
<span
|
||||
key={each[0]}
|
||||
className={"f9 inter black pa2 bg-gray5 bg-gray1-d " +
|
||||
"ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default"}
|
||||
>
|
||||
{name}
|
||||
<span
|
||||
className="white-d ml3 mono pointer"
|
||||
onClick={e => this.deleteGroup(each)}>
|
||||
x
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
selectedGroups = (
|
||||
<div className={
|
||||
"f9 gray2 bb bl br b--gray3 b--gray2-d bg-gray0-d " +
|
||||
"white-d pa3 db w-100 inter bg-gray5 lh-solid tl"
|
||||
}>
|
||||
{allSelected}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ml1 dib">
|
||||
<div className={buttonOpened}
|
||||
onClick={() => this.toggleOpen()}
|
||||
ref={(el) => this.toggleButton = el}>
|
||||
<p className="dib f9 pointer pv1 ph2 mw5 truncate v-mid">{currentGroup}</p>
|
||||
</div>
|
||||
<div className={dropdownClass}
|
||||
style={{ maxHeight: "24rem", width: 285 }}
|
||||
ref={(el) => { this.dropdown = el }}>
|
||||
<p className="tc bb b--gray3 b--gray1-d gray3 pv4 f9">Group Select and Filter</p>
|
||||
<a href="/~groups" className="ma4 bg-gray5 bg-gray1-d f9 tl pa1 br1 db no-underline" style={{paddingLeft: "6.5px", paddingRight: "6.5px"}}>Manage all Groups
|
||||
{inviteCount}
|
||||
</a>
|
||||
<p className="pt4 gray3 f9 tl mh4">Filter Groups</p>
|
||||
<div className="relative w-100 ph4 pt2 pb4">
|
||||
<input className="ba b--gray3 white-d bg-gray0-d inter w-100 f9 pa2" style={{boxSizing: "border-box"}} placeholder="Group name..."
|
||||
onChange={this.search}
|
||||
value={state.searchTerm}
|
||||
/>
|
||||
{searchResults}
|
||||
{selectedGroups}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupFilter;
|
@ -1,40 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import { GroupFilter } from "./group-filter";
|
||||
import { Sigil } from "/components/lib/icons/sigil";
|
||||
|
||||
export class HeaderBar extends Component {
|
||||
render() {
|
||||
let popout = window.location.href.includes("popout/")
|
||||
? "dn" : "dn db-m db-l db-xl";
|
||||
|
||||
let invites = (this.props.invites && this.props.invites["/contacts"])
|
||||
? this.props.invites["/contacts"]
|
||||
: {};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"bg-white bg-gray0-d w-100 justify-between relative tc pt3 " + popout
|
||||
}
|
||||
style={{ height: 45 }}>
|
||||
<div className="fl lh-copy absolute left-0" style={{ top: 8 }}>
|
||||
<a href="/~groups/me" className="dib v-mid">
|
||||
<Sigil
|
||||
ship={"~" + window.ship}
|
||||
classes="v-mid mix-blend-diff"
|
||||
size={16}
|
||||
color={"#000000"}
|
||||
/>
|
||||
</a>
|
||||
<GroupFilter invites={invites} associations={this.props.associations} />
|
||||
<span className="dib f9 v-mid gray2 ml1 mr1 c-default inter">/</span>
|
||||
<a
|
||||
className="dib f9 v-mid inter ml2"
|
||||
href="/"
|
||||
style={{ top: 14 }}>
|
||||
⟵</a> <p className="dib f9 v-mid inter ml2 white-d">Publishing</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { api } from '../../../api';
|
||||
|
||||
export class SidebarSwitcher extends Component {
|
||||
render() {
|
||||
|
||||
let popoutSwitcher = this.props.popout
|
||||
? "dn-m dn-l dn-xl"
|
||||
: "dib-m dib-l dib-xl";
|
||||
|
||||
return (
|
||||
<div className={"absolute left-1 top-1 " + popoutSwitcher}>
|
||||
<a
|
||||
className="pointer flex-shrink-0"
|
||||
onClick={() => {
|
||||
api.sidebarToggle();
|
||||
}}>
|
||||
<img
|
||||
className={`pr3 dn ` + popoutSwitcher}
|
||||
src={
|
||||
this.props.sidebarShown
|
||||
? "/~link/img/SwitcherOpen.png"
|
||||
: "/~link/img/SwitcherClosed.png"
|
||||
}
|
||||
height="16"
|
||||
width="16"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SidebarSwitcher
|
@ -1,25 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Spinner extends Component {
|
||||
render() {
|
||||
|
||||
let classes = !!this.props.classes ? this.props.classes : "";
|
||||
let text = !!this.props.text ? this.props.text : "";
|
||||
let awaiting = !!this.props.awaiting ? this.props.awaiting : false;
|
||||
|
||||
if (awaiting) {
|
||||
return (
|
||||
<div className={classes + " z-2 bg-white bg-gray0-d white-d"}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~publish/Spinner.png"
|
||||
width={16}
|
||||
height={16} />
|
||||
<p className="dib f9 ml2 v-mid">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { sigil, reactRenderer } from 'urbit-sigil-js';
|
||||
|
||||
export class Sigil extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const classes = props.classes || '';
|
||||
|
||||
const rgb = {
|
||||
r: parseInt(props.color.slice(1, 3), 16),
|
||||
g: parseInt(props.color.slice(3, 5), 16),
|
||||
b: parseInt(props.color.slice(5, 7), 16)
|
||||
};
|
||||
const brightness = ((299 * rgb.r) + (587 * rgb.g) + (114 * rgb.b)) / 1000;
|
||||
const whiteBrightness = 255;
|
||||
|
||||
let foreground = 'white';
|
||||
|
||||
if ((whiteBrightness - brightness) < 50) {
|
||||
foreground = 'black';
|
||||
}
|
||||
|
||||
if (props.ship.length > 14) {
|
||||
return (
|
||||
<div
|
||||
className={'bg-black dib ' + classes}
|
||||
style={{ width: props.size, height: props.size }}
|
||||
></div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className={'dib ' + classes}
|
||||
style={{ backgroundColor: props.color }}
|
||||
>
|
||||
{sigil({
|
||||
patp: props.ship,
|
||||
renderer: reactRenderer,
|
||||
size: props.size,
|
||||
colors: [props.color, foreground]
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,496 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import _ from 'lodash';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import urbitOb from "urbit-ob";
|
||||
import { Sigil } from "../lib/icons/sigil";
|
||||
|
||||
export class InviteSearch extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
groups: [],
|
||||
peers: [],
|
||||
contacts: new Map(),
|
||||
searchValue: "",
|
||||
searchResults: {
|
||||
groups: [],
|
||||
ships: []
|
||||
},
|
||||
selected: null,
|
||||
inviteError: false
|
||||
};
|
||||
this.search = this.search.bind(this);
|
||||
|
||||
this.textarea = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.peerUpdate();
|
||||
this.bindShortcuts();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps !== this.props) {
|
||||
this.peerUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
peerUpdate() {
|
||||
let groups = Array.from(Object.keys(this.props.contacts));
|
||||
groups = groups.filter(e => !e.startsWith("/~/"))
|
||||
.map(e => {
|
||||
let eachGroup = [];
|
||||
eachGroup.push(e);
|
||||
if (this.props.associations) {
|
||||
let name = e;
|
||||
if (e in this.props.associations) {
|
||||
name = (this.props.associations[e].metadata.title !== "")
|
||||
? this.props.associations[e].metadata.title : e;
|
||||
}
|
||||
eachGroup.push(name);
|
||||
}
|
||||
return Array.from(eachGroup);
|
||||
});
|
||||
|
||||
let peers = [],
|
||||
peerSet = new Set(),
|
||||
contacts = new Map();
|
||||
Object.keys(this.props.groups).map(group => {
|
||||
if (this.props.groups[group].size > 0) {
|
||||
let groupEntries = this.props.groups[group].values();
|
||||
for (let member of groupEntries) {
|
||||
peerSet.add(member);
|
||||
}
|
||||
}
|
||||
if (this.props.contacts[group]) {
|
||||
let groupEntries = this.props.groups[group].values();
|
||||
for (let member of groupEntries) {
|
||||
if (this.props.contacts[group][member]) {
|
||||
if (contacts.has(member)) {
|
||||
contacts
|
||||
.get(member)
|
||||
.push(this.props.contacts[group][member].nickname);
|
||||
} else {
|
||||
contacts.set(member, [
|
||||
this.props.contacts[group][member].nickname
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
peers = Array.from(peerSet);
|
||||
|
||||
this.setState({ groups: groups, peers: peers, contacts: contacts });
|
||||
}
|
||||
|
||||
search(event) {
|
||||
let searchTerm = event.target.value.toLowerCase().replace("~", "");
|
||||
|
||||
this.setState({ searchValue: event.target.value });
|
||||
|
||||
if (searchTerm.length < 1) {
|
||||
this.setState({ searchResults: { groups: [], ships: [] } });
|
||||
}
|
||||
|
||||
if (searchTerm.length > 0) {
|
||||
if (this.state.inviteError === true) {
|
||||
this.setState({ inviteError: false });
|
||||
}
|
||||
|
||||
let groupMatches = [];
|
||||
if (this.props.groupResults) {
|
||||
groupMatches = this.state.groups.filter(e => {
|
||||
return e[0].includes(searchTerm) || e[1].toLowerCase().includes(searchTerm);
|
||||
});
|
||||
}
|
||||
|
||||
let shipMatches = [];
|
||||
if (this.props.shipResults) {
|
||||
shipMatches = this.state.peers.filter(e => {
|
||||
return e.includes(searchTerm) && !this.props.invites.ships.includes(e);
|
||||
});
|
||||
|
||||
for (let contact of this.state.contacts.keys()) {
|
||||
let thisContact = this.state.contacts.get(contact);
|
||||
let match = thisContact.filter(e => {
|
||||
return e.toLowerCase().includes(searchTerm);
|
||||
});
|
||||
if (match.length > 0) {
|
||||
if (!(contact in shipMatches)) {
|
||||
shipMatches.push(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isValid = true;
|
||||
if (!urbitOb.isValidPatp("~" + searchTerm)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (isValid && shipMatches.findIndex(s => s === searchTerm) < 0) {
|
||||
shipMatches.unshift(searchTerm);
|
||||
}
|
||||
}
|
||||
|
||||
let { selected } = this.state;
|
||||
let groupIdx = groupMatches.findIndex(([path]) => path === selected);
|
||||
let shipIdx = shipMatches.findIndex(ship => ship === selected);
|
||||
let staleSelection = groupIdx < 0 && shipIdx < 0;
|
||||
if(!selected || staleSelection) {
|
||||
const newSelection = _.get(groupMatches, '[0][0]') || shipMatches[0];
|
||||
this.setState({ selected: newSelection })
|
||||
}
|
||||
|
||||
if(searchTerm.length < 3) {
|
||||
groupMatches = groupMatches.filter(([, name]) =>
|
||||
name.toLowerCase().split(' ').some(s => s.startsWith(searchTerm))
|
||||
).sort((a,b) => a[1].length - b[1].length);
|
||||
|
||||
shipMatches = shipMatches.slice(0,3);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
searchResults: { groups: groupMatches, ships: shipMatches }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bindShortcuts() {
|
||||
let mousetrap = Mousetrap(this.textarea.current);
|
||||
mousetrap.bind(['down', 'tab'], e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.nextSelection();
|
||||
});
|
||||
|
||||
mousetrap.bind(['up', 'shift+tab'], e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.nextSelection('backward');
|
||||
});
|
||||
|
||||
mousetrap.bind('enter', e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const { selected } = this.state;
|
||||
if(selected.startsWith('/')) {
|
||||
this.addGroup(selected)
|
||||
} else {
|
||||
this.addShip(selected);
|
||||
}
|
||||
this.setState({ selected: null })
|
||||
})
|
||||
}
|
||||
nextSelection(backward = false) {
|
||||
let { selected, searchResults } = this.state;
|
||||
const { ships, groups } = searchResults;
|
||||
if(!selected) {
|
||||
return;
|
||||
}
|
||||
let groupIdx = groups.findIndex(([path]) => path === selected);
|
||||
let shipIdx = ships.findIndex(ship => ship === selected);
|
||||
if(groupIdx >= 0) {
|
||||
backward ? groupIdx-- : groupIdx++;
|
||||
let selected = _.get(groups,[groupIdx], '[0]');
|
||||
if(groupIdx === groups.length) {
|
||||
selected = ships.length === 0
|
||||
? groups[0][0]
|
||||
: ships[0];
|
||||
|
||||
}
|
||||
if(groupIdx < 0) {
|
||||
selected = ships.length === 0
|
||||
? groups[groups.length - 1][0]
|
||||
: ships[ships.length - 1];
|
||||
}
|
||||
this.setState({ selected });
|
||||
return;
|
||||
}
|
||||
if(shipIdx >= 0) {
|
||||
backward ? shipIdx-- : shipIdx++;
|
||||
let selected = ships[shipIdx];
|
||||
if(shipIdx === ships.length) {
|
||||
selected = groups.length === 0
|
||||
? ships[0]
|
||||
: groups[0][0];
|
||||
}
|
||||
|
||||
if(shipIdx < 0) {
|
||||
selected = groups.length === 0
|
||||
? ships[ships.length - 1]
|
||||
: groups[groups.length - 1][0];
|
||||
}
|
||||
|
||||
this.setState({ selected });
|
||||
}
|
||||
|
||||
}
|
||||
deleteGroup() {
|
||||
let { ships } = this.props.invites;
|
||||
this.setState({
|
||||
searchValue: "",
|
||||
searchResults: { groups: [], ships: [] }
|
||||
});
|
||||
this.props.setInvite({ groups: [], ships: ships });
|
||||
}
|
||||
|
||||
deleteShip(ship) {
|
||||
let { groups, ships } = this.props.invites;
|
||||
this.setState({
|
||||
searchValue: "",
|
||||
searchResults: { groups: [], ships: [] }
|
||||
});
|
||||
ships = ships.filter(e => {
|
||||
return e !== ship;
|
||||
});
|
||||
this.props.setInvite({ groups: groups, ships: ships });
|
||||
}
|
||||
|
||||
addGroup(group) {
|
||||
this.setState({
|
||||
searchValue: "",
|
||||
searchResults: { groups: [], ships: [] }
|
||||
});
|
||||
this.props.setInvite({ groups: [group], ships: [] });
|
||||
}
|
||||
|
||||
addShip(ship) {
|
||||
let { groups, ships } = this.props.invites;
|
||||
this.setState({
|
||||
searchValue: "",
|
||||
searchResults: { groups: [], ships: [] }
|
||||
});
|
||||
if (!ships.includes(ship)) {
|
||||
ships.push(ship);
|
||||
}
|
||||
if (groups.length > 0) {
|
||||
return false;
|
||||
}
|
||||
this.props.setInvite({ groups: groups, ships: ships });
|
||||
}
|
||||
|
||||
submitShipToAdd(ship) {
|
||||
let searchTerm = ship
|
||||
.toLowerCase()
|
||||
.replace("~", "")
|
||||
.trim();
|
||||
let isValid = true;
|
||||
if (!urbitOb.isValidPatp("~" + searchTerm)) {
|
||||
isValid = false;
|
||||
}
|
||||
if (!isValid) {
|
||||
this.setState({ inviteError: true, searchValue: "" });
|
||||
} else if (isValid) {
|
||||
this.addShip(searchTerm);
|
||||
this.setState({ searchValue: "" });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let searchDisabled = false;
|
||||
if (props.invites.groups) {
|
||||
if (props.invites.groups.length > 0) {
|
||||
searchDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
let participants = <div />;
|
||||
let searchResults = <div />;
|
||||
|
||||
let placeholder = '';
|
||||
if (props.shipResults) {
|
||||
placeholder = 'ships';
|
||||
}
|
||||
if (props.groupResults) {
|
||||
if (placeholder.length > 0) {
|
||||
placeholder = placeholder + ' or ';
|
||||
}
|
||||
placeholder = placeholder + 'existing groups';
|
||||
}
|
||||
placeholder = 'Search for ' + placeholder;
|
||||
|
||||
let invErrElem = <span />;
|
||||
if (state.inviteError) {
|
||||
invErrElem = (
|
||||
<span className="f9 inter red2 db pt2">
|
||||
Invited ships must be validly formatted ship names.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
state.searchResults.groups.length > 0 ||
|
||||
state.searchResults.ships.length > 0
|
||||
) {
|
||||
let groupHeader =
|
||||
state.searchResults.groups.length > 0 ? (
|
||||
<p className="f9 gray2 ph3 pb2">Groups</p>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
|
||||
let shipHeader =
|
||||
state.searchResults.ships.length > 0 ? (
|
||||
<p className="f9 gray2 pv2 ph3">Ships</p>
|
||||
) : (
|
||||
""
|
||||
);
|
||||
|
||||
let groupResults = state.searchResults.groups.map(group => {
|
||||
return (
|
||||
<li
|
||||
key={group[0]}
|
||||
className={
|
||||
"list white-d f8 pv2 ph3 pointer" +
|
||||
" hover-bg-gray4 hover-bg-gray1-d " +
|
||||
(group[1] ? "inter" : "mono") +
|
||||
( group[0] === state.selected ? ' bg-gray1-d bg-gray4' : '')
|
||||
}
|
||||
onClick={() => this.addGroup(group[0])}>
|
||||
<span className="mix-blend-diff white">
|
||||
{group[1] ? group[1] : group[0]}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
let shipResults = state.searchResults.ships.map(ship => {
|
||||
let nicknames = this.state.contacts.has(ship)
|
||||
? this.state.contacts
|
||||
.get(ship)
|
||||
.filter(e => {
|
||||
return !(e === "");
|
||||
})
|
||||
.join(", ")
|
||||
: "";
|
||||
return (
|
||||
<li
|
||||
key={ship}
|
||||
className={
|
||||
"list mono white-d f8 pv1 ph3 pointer" +
|
||||
" hover-bg-gray4 hover-bg-gray1-d relative" +
|
||||
( ship === state.selected ? ' bg-gray1-d bg-gray4' : '')
|
||||
}
|
||||
onClick={e => this.addShip(ship)}>
|
||||
<Sigil
|
||||
ship={"~" + ship}
|
||||
size={24}
|
||||
color="#000000"
|
||||
classes="mix-blend-diff v-mid"
|
||||
/>
|
||||
<span className="v-mid ml2 mw5 truncate dib mix-blend-diff white">
|
||||
{"~" + ship}
|
||||
</span>
|
||||
<span className="absolute right-1 di truncate mw4 inter f9 pt1 mix-blend-diff white">
|
||||
{nicknames}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
searchResults = (
|
||||
<div
|
||||
className={
|
||||
"absolute bg-white bg-gray0-d white-d" +
|
||||
" pv3 z-1 w-100 mt1 ba b--white-d overflow-y-scroll mh-16"
|
||||
}>
|
||||
{groupHeader}
|
||||
{groupResults}
|
||||
{shipHeader}
|
||||
{shipResults}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let groupInvites = props.invites.groups || [];
|
||||
let shipInvites = props.invites.ships || [];
|
||||
|
||||
if (groupInvites.length > 0 || shipInvites.length > 0) {
|
||||
let groups = groupInvites.map(group => {
|
||||
return (
|
||||
<span
|
||||
key={group}
|
||||
className={
|
||||
"f9 mono black pa2 bg-gray5 bg-gray1-d" +
|
||||
" ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default"
|
||||
}>
|
||||
{group}
|
||||
<span
|
||||
className="white-d ml3 mono pointer"
|
||||
onClick={e => this.deleteGroup(group)}>
|
||||
x
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
let ships = shipInvites.map(ship => {
|
||||
return (
|
||||
<span
|
||||
key={ship}
|
||||
className={
|
||||
"f9 mono black pa2 bg-gray5 bg-gray1-d" +
|
||||
" ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default"
|
||||
}>
|
||||
{"~" + ship}
|
||||
<span
|
||||
className="white-d ml3 mono pointer"
|
||||
onClick={e => this.deleteShip(ship)}>
|
||||
x
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
participants = (
|
||||
<div
|
||||
className={
|
||||
"f9 gray2 bb bl br b--gray3 b--gray2-d bg-gray0-d " +
|
||||
"white-d pa3 db w-100 inter"
|
||||
}>
|
||||
<span className="db gray2">Participants</span>
|
||||
{groups} {ships}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<img
|
||||
src="/~publish/search.png"
|
||||
className="absolute invert-d"
|
||||
style={{
|
||||
height: 16,
|
||||
width: 16,
|
||||
top: 14,
|
||||
left: 12
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
ref={this.textarea}
|
||||
className={
|
||||
"f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 w-100" +
|
||||
" db focus-b--black focus-b--white-d"
|
||||
}
|
||||
placeholder={placeholder}
|
||||
disabled={searchDisabled}
|
||||
rows={1}
|
||||
spellCheck={false}
|
||||
style={{
|
||||
resize: "none",
|
||||
paddingLeft: 36
|
||||
}}
|
||||
onChange={this.search}
|
||||
value={state.searchValue}
|
||||
/>
|
||||
{searchResults}
|
||||
{participants}
|
||||
{invErrElem}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteSearch;
|
@ -1,27 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
|
||||
export class NotebookItem extends Component {
|
||||
render() {
|
||||
let { props } = this;
|
||||
|
||||
let selectedClass = (props.selected) ? "bg-gray5 bg-gray1-d c-default" : "pointer hover-bg-gray5 hover-bg-gray1-d";
|
||||
|
||||
let unread = (props.unreadCount > 0)
|
||||
? <p className="dib f9 fr"><span className="dib white bg-gray3 bg-gray2-d fw6 br1" style={{ padding: "1px 5px" }}>
|
||||
{props.unreadCount}
|
||||
</span></p> : <span/>;
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={"/~publish/notebook/" + props.path}>
|
||||
<div className={"w-100 v-mid f9 ph4 pv1 " + selectedClass}>
|
||||
<p className="dib f9">{props.title}</p>
|
||||
{unread}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NotebookItem
|
@ -1,270 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { BrowserRouter, Route, Link } from "react-router-dom";
|
||||
import _ from 'lodash';
|
||||
import { store } from '/store';
|
||||
import { api } from '/api';
|
||||
import { Skeleton } from '/components/skeleton';
|
||||
import { NewScreen } from '/components/lib/new';
|
||||
import { JoinScreen } from '/components/lib/join';
|
||||
import { Notebook } from '/components/lib/notebook';
|
||||
import { Note } from '/components/lib/note';
|
||||
import { NewPost } from '/components/lib/new-post';
|
||||
import { EditPost } from '/components/lib/edit-post';
|
||||
|
||||
|
||||
export class Root extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.unreadTotal = 0;
|
||||
this.state = store.state;
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//preload spinner asset
|
||||
new Image().src = "/~publish/Spinner.png";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let contacts = !!state.contacts ? state.contacts : {};
|
||||
let associations = !!state.associations ? state.associations : {contacts: {}}
|
||||
let selectedGroups = !!state.selectedGroups ? state.selectedGroups : [];
|
||||
|
||||
const unreadTotal = _.chain(state.notebooks)
|
||||
.values()
|
||||
.map(_.values)
|
||||
.flatten() // flatten into array of notebooks
|
||||
.map('num-unread')
|
||||
.reduce((acc, count) => acc + count, 0)
|
||||
.value();
|
||||
|
||||
if(this.unreadTotal !== unreadTotal) {
|
||||
document.title = unreadTotal > 0 ? `Publish - (${unreadTotal})` : 'Publish';
|
||||
this.unreadTotal = unreadTotal;
|
||||
}
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<Route exact path="/~publish"
|
||||
render={ (props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={false}
|
||||
active={"sidebar"}
|
||||
rightPanelHide={true}
|
||||
sidebarShown={true}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}>
|
||||
<div className={`h-100 w-100 overflow-x-hidden flex flex-column
|
||||
bg-white bg-gray0-d dn db-ns`}>
|
||||
<div className="pl3 pr3 pt2 dt pb3 w-100 h-100">
|
||||
<p className="f9 pt3 gray2 w-100 h-100 dtc v-mid tc">
|
||||
Select or create a notebook to begin.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~publish/new"
|
||||
render={ (props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={false}
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}>
|
||||
<NewScreen
|
||||
associations={associations.contacts}
|
||||
notebooks={state.notebooks}
|
||||
groups={state.groups}
|
||||
contacts={contacts}
|
||||
api={api}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
)
|
||||
}}/>
|
||||
<Route exact path="/~publish/join/:ship?/:notebook?"
|
||||
render={ (props) => {
|
||||
let ship = props.match.params.ship || "";
|
||||
let notebook = props.match.params.notebook || "";
|
||||
return (
|
||||
<Skeleton
|
||||
popout={false}
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}>
|
||||
<JoinScreen
|
||||
notebooks={state.notebooks}
|
||||
ship={ship}
|
||||
notebook={notebook}
|
||||
{...props} />
|
||||
</Skeleton>
|
||||
)
|
||||
}}/>
|
||||
<Route exact path="/~publish/:popout?/notebook/:ship/:notebook/:view?"
|
||||
render={ (props) => {
|
||||
let view = (props.match.params.view)
|
||||
? props.match.params.view
|
||||
: "posts";
|
||||
|
||||
let popout = !!props.match.params.popout || false;
|
||||
|
||||
let ship = props.match.params.ship || "";
|
||||
let notebook = props.match.params.notebook || "";
|
||||
|
||||
let path = `${ship}/${notebook}`;
|
||||
|
||||
let bookGroupPath =
|
||||
state.notebooks[ship][notebook]["subscribers-group-path"];
|
||||
|
||||
let notebookContacts = (bookGroupPath in contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
|
||||
if (view === "new") {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
path={path}>
|
||||
<NewPost
|
||||
notebooks={state.notebooks}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
selectedGroups={selectedGroups}
|
||||
path={path}>
|
||||
<Notebook
|
||||
notebooks={state.notebooks}
|
||||
view={view}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
groups={state.groups}
|
||||
contacts={contacts}
|
||||
notebookContacts={notebookContacts}
|
||||
associations={associations.contacts}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
permissions={state.permissions}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}
|
||||
}}/>
|
||||
<Route exact path="/~publish/:popout?/note/:ship/:notebook/:note/:edit?"
|
||||
render={ (props) => {
|
||||
let ship = props.match.params.ship || "";
|
||||
let notebook = props.match.params.notebook || "";
|
||||
let path = `${ship}/${notebook}`
|
||||
let note = props.match.params.note || "";
|
||||
|
||||
let popout = !!props.match.params.popout || false;
|
||||
|
||||
let bookGroupPath =
|
||||
state.notebooks[ship][notebook]["subscribers-group-path"];
|
||||
let notebookContacts = (bookGroupPath in state.contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
|
||||
let edit = !!props.match.params.edit || false;
|
||||
|
||||
if (edit) {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
selectedGroups={selectedGroups}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
path={path}>
|
||||
<EditPost
|
||||
notebooks={state.notebooks}
|
||||
book={notebook}
|
||||
note={note}
|
||||
ship={ship}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
{...props}/>
|
||||
</Skeleton>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<Skeleton
|
||||
popout={popout}
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
selectedGroups={selectedGroups}
|
||||
contacts={contacts}
|
||||
path={path}>
|
||||
<Note
|
||||
notebooks={state.notebooks}
|
||||
book={notebook}
|
||||
groups={state.groups}
|
||||
contacts={notebookContacts}
|
||||
ship={ship}
|
||||
note={note}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
}
|
||||
}}/>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Root;
|
@ -1,49 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { HeaderBar } from './lib/header-bar';
|
||||
import { Sidebar } from './lib/sidebar';
|
||||
|
||||
export class Skeleton extends Component {
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let rightPanelHide = props.rightPanelHide
|
||||
? "dn-s" : "";
|
||||
|
||||
let popout = !!props.popout
|
||||
? props.popout : false;
|
||||
|
||||
let popoutWindow = (popout)
|
||||
? "" : "h-100-m-40-ns ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl"
|
||||
|
||||
let popoutBorder = (popout)
|
||||
? "": "ba-m ba-l ba-xl b--gray4 b--gray1-d br1"
|
||||
|
||||
return (
|
||||
<div className={"absolute h-100 w-100 " + popoutWindow}>
|
||||
<HeaderBar
|
||||
invites={props.invites}
|
||||
associations={props.associations} />
|
||||
<div className={`cf w-100 h-100 flex ` + popoutBorder}>
|
||||
<Sidebar
|
||||
popout={popout}
|
||||
sidebarShown={props.sidebarShown}
|
||||
active={props.active}
|
||||
notebooks={props.notebooks}
|
||||
contacts={props.contacts}
|
||||
path={props.path}
|
||||
invites={props.invites}
|
||||
associations={props.associations}
|
||||
selectedGroups={props.selectedGroups}
|
||||
/>
|
||||
<div className={"h-100 w-100 relative white-d flex-auto " + rightPanelHide} style={{
|
||||
flexGrow: 1,
|
||||
}}>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Skeleton;
|
@ -1,115 +0,0 @@
|
||||
export function stringToSymbol(str) {
|
||||
let result = '';
|
||||
for (var i=0; i<str.length; i++){
|
||||
var n = str.charCodeAt(i);
|
||||
if (( (n >= 97) && (n <= 122) ) ||
|
||||
( (n >= 48) && (n <= 57) ))
|
||||
{
|
||||
result += str[i];
|
||||
} else if ( (n >= 65) && (n <= 90) )
|
||||
{
|
||||
result += String.fromCharCode(n + 32);
|
||||
} else {
|
||||
result += '-';
|
||||
}
|
||||
}
|
||||
result = result.replace(/^[\-\d]+|\-+/g, '-');
|
||||
result = result.replace(/^\-+|\-+$/g, '');
|
||||
if (result === ''){
|
||||
return dateToDa(new Date());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function dateToDa(d, mil) {
|
||||
var fil = function(n) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
`${(d.getUTCMonth() + 1)}.` +
|
||||
`${fil(d.getUTCDate())}..` +
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
);
|
||||
}
|
||||
|
||||
export function uxToHex(ux) {
|
||||
if (ux.length > 2 && ux.substr(0, 2) === '0x') {
|
||||
let value = ux.substr(2).replace('.', '').padStart(6, '0');
|
||||
return value;
|
||||
}
|
||||
|
||||
let value = ux.replace('.', '').padStart(6, '0');
|
||||
return value;
|
||||
}
|
||||
|
||||
export function writeText(str) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
var range = document.createRange();
|
||||
range.selectNodeContents(document.body);
|
||||
document.getSelection().addRange(range);
|
||||
|
||||
var success = false;
|
||||
function listener(e) {
|
||||
e.clipboardData.setData("text/plain", str);
|
||||
e.preventDefault();
|
||||
success = true;
|
||||
}
|
||||
document.addEventListener("copy", listener);
|
||||
document.execCommand("copy");
|
||||
document.removeEventListener("copy", listener);
|
||||
|
||||
document.getSelection().removeAllRanges();
|
||||
|
||||
success ? resolve() : reject();
|
||||
}).catch(function (error) {
|
||||
console.error(error);
|
||||
});;
|
||||
};
|
||||
|
||||
|
||||
// trim patps to match dojo, chat-cli
|
||||
export function cite(ship) {
|
||||
let patp = ship, shortened = "";
|
||||
if (patp.startsWith("~")) {
|
||||
patp = patp.substr(1);
|
||||
}
|
||||
// comet
|
||||
if (patp.length === 56) {
|
||||
shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56);
|
||||
return shortened;
|
||||
}
|
||||
// moon
|
||||
if (patp.length === 27) {
|
||||
shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27);
|
||||
return shortened;
|
||||
}
|
||||
return `~${patp}`;
|
||||
}
|
||||
|
||||
export function alphabetiseAssociations(associations) {
|
||||
let result = {};
|
||||
Object.keys(associations).sort((a, b) => {
|
||||
let aName = a.substr(1);
|
||||
let bName = b.substr(1);
|
||||
if (associations[a].metadata && associations[a].metadata.title) {
|
||||
aName = associations[a].metadata.title !== ""
|
||||
? associations[a].metadata.title
|
||||
: a.substr(1);
|
||||
}
|
||||
if (associations[b].metadata && associations[b].metadata.title) {
|
||||
bName = associations[b].metadata.title !== ""
|
||||
? associations[b].metadata.title
|
||||
: b.substr(1);
|
||||
}
|
||||
return aName.toLowerCase().localeCompare(bName.toLowerCase());
|
||||
}).map((each) => {
|
||||
result[each] = associations[each];
|
||||
})
|
||||
return result;
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export class MetadataReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'metadata-update', false);
|
||||
if (data) {
|
||||
this.associations(data, state);
|
||||
this.add(data, state);
|
||||
this.update(data, state);
|
||||
this.remove(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
associations(json, state) {
|
||||
let data = _.get(json, 'associations', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
Object.keys(data).map((channel) => {
|
||||
let channelObj = data[channel];
|
||||
let app = data[channel]["app-name"];
|
||||
if (!(app in metadata)) {
|
||||
metadata[app] = {};
|
||||
}
|
||||
metadata[app][channelObj["app-path"]] = channelObj;
|
||||
})
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
let data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let app = data["app-name"];
|
||||
if (!(app in metadata)) {
|
||||
metadata[app] = {};
|
||||
}
|
||||
metadata[app][data["app-path"]] = data;
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
update(json, state) {
|
||||
let data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let app = data["app-name"];
|
||||
metadata[app][data["app-path"]] = data;
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
let data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let app = data["app-name"];
|
||||
if (!(app in metadata)) {
|
||||
return false;
|
||||
}
|
||||
delete metadata[app][data["app-path"]];
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import { InitialReducer } from '/reducers/initial';
|
||||
import { PrimaryReducer } from '/reducers/primary';
|
||||
import { ResponseReducer } from '/reducers/response';
|
||||
import { GroupReducer } from '/reducers/group';
|
||||
import { InviteReducer } from '/reducers/invite';
|
||||
import { PermissionReducer } from '/reducers/permission';
|
||||
import { MetadataReducer } from '/reducers/metadata';
|
||||
|
||||
class Store {
|
||||
constructor() {
|
||||
this.state = {
|
||||
notebooks: {},
|
||||
groups: {},
|
||||
contacts: {},
|
||||
associations: {
|
||||
contacts: {}
|
||||
},
|
||||
permissions: {},
|
||||
invites: {},
|
||||
selectedGroups: [],
|
||||
sidebarShown: true
|
||||
}
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
this.primaryReducer = new PrimaryReducer();
|
||||
this.responseReducer = new ResponseReducer();
|
||||
this.groupReducer = new GroupReducer();
|
||||
this.inviteReducer = new InviteReducer();
|
||||
this.permissionReducer = new PermissionReducer();
|
||||
this.metadataReducer = new MetadataReducer();
|
||||
this.setState = () => {};
|
||||
|
||||
this.initialReducer.reduce(window.injectedState, this.state);
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
handleEvent(evt) {
|
||||
if (evt.from && evt.from.path === '/all') {
|
||||
this.groupReducer.reduce(evt.data, this.state);
|
||||
this.permissionReducer.reduce(evt.data, this.state);
|
||||
}
|
||||
else if (evt.from && evt.from.path === '/app-name/contacts') {
|
||||
this.metadataReducer.reduce(evt.data, this.state);
|
||||
}
|
||||
else if (evt.from && evt.from.path === '/primary'){
|
||||
this.primaryReducer.reduce(evt.data, this.state);
|
||||
this.inviteReducer.reduce(evt.data, this.state);
|
||||
} else if (evt.type) {
|
||||
this.responseReducer.reduce(evt, this.state);
|
||||
}
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
export let store = new Store();
|
||||
window.store = store;
|
@ -1,47 +0,0 @@
|
||||
import { api } from '/api';
|
||||
import { store } from '/store';
|
||||
|
||||
|
||||
export class Subscription {
|
||||
start() {
|
||||
if (api.authTokens) {
|
||||
this.initializePublish();
|
||||
} else {
|
||||
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
|
||||
}
|
||||
}
|
||||
|
||||
initializePublish() {
|
||||
api.bind(`/primary`, "PUT", api.authTokens.ship, 'publish',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
api.bind('/all', 'PUT', api.authTokens.ship, 'group-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
api.bind('/primary', 'PUT', api.authTokens.ship, 'contact-view',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
api.bind('/primary', 'PUT', api.authTokens.ship, 'invite-view',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
api.bind('/all', 'PUT', api.authTokens.ship, 'permission-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
api.bind('/app-name/contacts', 'PUT', api.authTokens.ship, 'metadata-store',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
|
||||
handleEvent(diff) {
|
||||
store.handleEvent(diff);
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
console.error(err);
|
||||
api.bind(`/primary`, "PUT", api.authTokens.ship, 'publish',
|
||||
this.handleEvent.bind(this),
|
||||
this.handleError.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
export let subscription = new Subscription();
|
Loading…
Reference in New Issue
Block a user