Merge branch 'master' of github.com:urbit/interface

This commit is contained in:
Logan Allen 2019-06-04 14:18:43 -07:00
commit 63fca9a2a2
30 changed files with 2000 additions and 891 deletions

1
.gitignore vendored
View File

@ -71,3 +71,4 @@ apps/*/dist/
# vim swap files
*.swo
*.swp
*.swn

View File

@ -1985,7 +1985,8 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -2009,13 +2010,15 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2032,19 +2035,22 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -2175,7 +2181,8 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -2189,6 +2196,7 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -2205,6 +2213,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -2213,13 +2222,15 @@
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz",
"integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -2240,6 +2251,7 @@
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2328,7 +2340,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -2342,6 +2355,7 @@
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -2437,7 +2451,8 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -2479,6 +2494,7 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -2500,6 +2516,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -2548,13 +2565,15 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
"dev": true
"dev": true,
"optional": true
}
}
},
@ -4487,9 +4506,9 @@
}
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"mousetrap": {
"version": "1.6.2",

View File

@ -28,7 +28,7 @@
"dependencies": {
"classnames": "^2.2.6",
"lodash": "^4.17.11",
"moment": "^2.20.1",
"moment": "^2.24.0",
"mousetrap": "^1.6.1",
"react": "^16.5.2",
"react-custom-scrollbars": "^4.2.1",

View File

@ -4,6 +4,11 @@ p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
font-family: Inter, sans-serif;
}
a {
color: inherit;
text-decoration: inherit;
}
textarea, select, input, button { outline: none; }
h2 {
@ -34,6 +39,11 @@ h2 {
font-family: "Source Code Pro", monospace;
}
.label-small {
font-size: 12px;
line-height: 24px;
}
.body-regular-400 {
font-size: 16px;
line-height: 24px;
@ -52,3 +62,42 @@ h2 {
.bg-v-light-gray {
background-color: #f9f9f9;
}
.gray-50 {
color: #7F7F7F;
}
.gray-30 {
color: #B1B2B3;
}
.w-336 {
width: 336px;
}
.w-688 {
width: 688px;
}
.w-680 {
width: 680px;
}
.h-80 {
height: 80px;
}
.publish {
margin-left: 16px;
margin-top: 16px;
margin-bottom: 8px;
padding: 0;
}
.b-gray-30 {
border-color: #B1B2B3;
}

View File

@ -0,0 +1,112 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { PostPreview } from '/components/post-preview';
export class Blog extends Component {
constructor(props){
super(props)
}
buildPosts(){
let blogId = this.props.blogId;
let ship = this.props.ship;
let blog = this.retrieveColl(blogId, ship);
console.log("buildposts", blog);
let pinProps = blog.order.pin.map((post) => {
return this.buildPostPreviewProps(post, blogId, ship, true);
});
let unpinProps = blog.order.unpin.map((post) => {
return this.buildPostPreviewProps(post, blogId, ship, false);
});
return pinProps.concat(unpinProps);
}
buildPostPreviewProps(post, coll, who, pinned){
let pos = this.retrievePost(post, coll, who);
let col = this.retrieveColl(coll, who);
let com = this.retrieveComments(post, coll, who);
return {
postTitle: pos.info.title,
postName: post,
postBody: pos.body,
numComments: com.length,
collectionTitle: col.title,
collectionName: coll,
author: who,
date: pos.info["date-created"],
pinned: pinned,
}
}
retrievePost(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].post;
} else {
return this.props.subs[who][coll].posts[post].post;
}
}
retrieveComments(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].comments;
} else {
return this.props.subs[who][coll].posts[post].comments;
}
}
retrieveColl(coll, who) {
if (who === window.ship) {
return this.props.pubs[coll];
} else {
return this.props.subs[who][coll];
}
}
render() {
let blog = this.retrieveColl(this.props.blogId, this.props.ship);
let postProps = this.buildPosts();
let posts = postProps.map((post) => {
return (
<PostPreview
post={post}
/>
);
});
let host = blog.info.owner;
let contributers = host + " and X others"; // XX backend work
let subscribers = "~bitpyx-dildus and X others"; // XX backend work
return (
<div className="flex-col">
<h2>{blog.info.title}</h2>
<div className="flex">
<div style={{flexBasis: 350}}>
<p>Host</p>
<p>{host}</p>
</div>
<div style={{flexBasis: 350}}>
<p>Contributors</p>
<p>{contributers}</p>
</div>
<div style={{flexBasis: 350}}>
<p>Subscribers</p>
<p>{subscribers}</p>
</div>
</div>
<div className="flex flex-wrap">
{posts}
</div>
</div>
);
}
}

View File

@ -1,164 +0,0 @@
import React, { Component } from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
import classnames from 'classnames';
import _ from 'lodash';
import { Message } from '/components/lib/message';
import { ChatHeader } from '/components/lib/chat-header';
import { ChatInput } from '/components/lib/chat-input';
import { prettyShip, getMessageContent, isDMStation } from '/lib/util';
export class ChatScreen extends Component {
constructor(props) {
super(props);
this.state = {
station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station,
host: props.match.params.ship,
message: "",
pendingMessages: [],
numPeople: 0
};
this.onScrollStop = this.onScrollStop.bind(this);
this.buildMessage = this.buildMessage.bind(this);
this.setPendingMessage = this.setPendingMessage.bind(this);
this.scrollbarRef = React.createRef();
}
componentDidMount() {
if (isDMStation(this.state.station)) {
let cir = this.state.station.split("/")[1];
this.props.api.hall({
newdm: {
sis: cir.split(".")
}
})
}
this.scrollIfLocked();
this.updateNumPeople();
}
componentDidUpdate(prevProps, prevState) {
const { props } = this;
if ((props.match.params.ship != prevProps.match.params.ship) ||
(props.match.params.station != prevProps.match.params.station)) {
this.setState({
station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station,
host: props.match.params.ship,
message: "",
pendingMessages: [],
numPeople: 0
});
}
this.updateNumPeople();
this.updateNumMessagesLoaded(prevProps, prevState);
}
updateNumPeople() {
let conf = this.props.configs[this.state.station] || {};
let sis = _.get(conf, 'con.sis');
let numPeople = !!sis ? sis.length : 0;
if (numPeople !== this.state.numPeople) {
this.setState({ numPeople });
}
}
updateNumMessagesLoaded(prevProps, prevState) {
let station = prevProps.store.messages.stations[this.state.station] || [];
let numMessages = station.length;
if (numMessages > prevState.numMessages && this.scrollbarRef.current) {
this.setState({
numMessages: numMessages
});
this.scrollIfLocked();
}
}
scrollIfLocked() {
if (this.state.scrollLocked && this.scrollbarRef.current) {
this.scrollbarRef.current.scrollToBottom();
}
}
onScrollStop() {
let scroll = this.scrollbarRef.current.getValues();
this.setState({
scrollLocked: (scroll.top === 1)
});
if (scroll.top === 0) {
this.requestChatBatch();
}
}
setPendingMessage(message) {
this.setState({
pendingMessages: this.state.pendingMessages.concat({...message, pending: true})
});
}
buildMessage(msg) {
let details = msg.printship ? null : getMessageContent(msg);
if (msg.printship) {
return (
<a
className="vanilla hoverline text-600 text-mono"
href={prettyShip(msg.aut)[1]}>
{prettyShip(`~${msg.aut}`)[0]}
</a>
);
}
return (
<Message msg={msg} details={details} />
);
}
render() {
let messages = this.props.store.messages.stations[this.state.station] || [];
messages = [...messages, ...this.state.pendingMessages];
let chatMessages = messages.map(this.buildMessage);
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
<div style={{ flexBasis:72 }}>
<ChatHeader title={this.state.circle} numPeople={this.state.numPeople} />
</div>
<div style={{ flexGrow: 1 }}>
<Scrollbars
ref={this.scrollbarRef}
renderTrackHorizontal={props => <div style={{display: "none"}}/>}
onScrollStop={this.onScrollStop}
renderView={props => <div {...props} />}
style={{ height: '100%' }}
autoHide>
{chatMessages}
</Scrollbars>
</div>
<div style={{ flexBasis:112 }}>
<ChatInput
api={this.props.api}
store={this.props.store}
station={this.state.station}
circle={this.state.circle}
scrollbarRef={this.scrollbarRef}
setPendingMessage={this.setPendingMessage}
placeholder='Message...' />
</div>
</div>
)
}
}

View File

@ -1,27 +0,0 @@
import React, { Component } from 'react';
import classnames from 'classnames';
export class CollectionItem extends Component {
onClick() {
const { props } = this;
console.log("collection-item clicked!");
// props.history.push(props.url);
}
render() {
const { props } = this;
let selectedCss = !!props.selected ? 'bg-light-gray' : 'bg-white pointer';
return (
<div className={'pa3 ' + selectedCss} onClick={this.onClick.bind(this)}>
<div className='w-100 v-mid'>
<h3 className='w-60 dib sans-serif'>{props.title}</h3>
<p className='w-40 tr dib sans-serif gray'>{props.datetime}</p>
</div>
<p className='pt2 sans-serif gray'>{props.description}</p>
</div>
)
}
}

View File

@ -1,27 +0,0 @@
import React, { Component } from 'react';
import classnames from 'classnames';
export class CollectionList extends Component {
render() {
console.log("collection-list.props", this.props);
let listItems = this.props.list.map((coll) => {
return (
<p className="w-100">
{coll.data.info.title}
</p>
);
});
console.log(listItems);
return (
<div className="w-100">
{listItems}
</div>
);
}
}

View File

@ -0,0 +1,278 @@
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
export class Header extends Component {
constructor(props) {
super(props);
}
retrievePost(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].post;
} else {
return this.props.subs[who][coll].posts[post].post;
}
}
retrieveComments(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].comments;
} else {
return this.props.subs[who][coll].posts[post].comments;
}
}
retrieveColl(coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].info;
} else {
return this.props.subs[who][coll].info;
}
}
render() {
return (
<div className="cf w-100">
<div className="fl w-100">
<p className="fl body-large b gray-50 publish">Publish</p>
<p className="label-regular b fr">+ Create</p>
</div>
<Route exact path="/~publish/recent"
render={ (props) => {
return (
<div className="fl w-100 flex">
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb" style={{ flexBasis: 148 }}>
<Link to="/~publish/recent">
<p className="fl w-100 h2 label-regular">
Recent
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb b-gray-30" style={{ flexBasis: 148 }}>
<Link to="/~publish/subs">
<p className="fl w-100 h2 label-regular gray-30">
Subscriptions
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb b-gray-30" style={{ flexBasis: 148 }}>
<Link to="/~publish/pubs">
<p className="fl w-100 h2 label-regular gray-30">
My Blogs
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ flexGrow: 1}}>
</div>
</div>
);
}} />
<Route exact path="/~publish/subs"
render={ (props) => {
return (
<div className="fl w-100 flex">
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb b-gray-30" style={{ flexBasis: 148 }}>
<Link to="/~publish/recent">
<p className="fl w-100 h2 label-regular gray-30">
Recent
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb" style={{ flexBasis: 148 }}>
<Link to="/~publish/subs">
<p className="fl w-100 h2 label-regular">
Subscriptions
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb b-gray-30" style={{ flexBasis: 148 }}>
<Link to="/~publish/pubs">
<p className="fl w-100 h2 label-regular gray-30">
My Blogs
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ flexGrow: 1}}>
</div>
</div>
);
}} />
<Route exact path="/~publish/pubs"
render={ (props) => {
return (
<div className="fl w-100 flex">
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb b-gray-30" style={{ flexBasis: 148 }}>
<Link to="/~publish/recent">
<p className="fl w-100 h2 label-regular gray-30">
Recent
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb b-gray-30" style={{ flexBasis: 148 }}>
<Link to="/~publish/subs">
<p className="fl w-100 h2 label-regular gray-30">
Subscriptions
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ width: 16 }}>
</div>
<div className="fl bb" style={{ flexBasis: 148 }}>
<Link to="/~publish/pubs">
<p className="fl w-100 h2 label-regular">
My Blogs
</p>
</Link>
</div>
<div className="fl bb b-gray-30" style={{ flexGrow: 1}}>
</div>
</div>
);
}} />
<Route exact path="/~publish/:ship/:blog"
render={ (props) => {
let ship = props.match.params.ship.slice(1);
let blogId = props.match.params.blog;
let blogLink = `/~publish/~${ship}/${blogId}`;
let blog = this.retrieveColl(blogId, ship);
let blogName = blog.title;
return (
<div className="fl w-100 flex b-gray-30 bb">
<Link to="/~publish/recent">
<p className="fl gray-30 label-regular" style={{ marginLeft: 16}}>
Home
</p>
</Link>
<p className="fl gray-30 label-regular">
->
</p>
<Link to={blogLink}>
<p className="fl label-regular">
{blogName}
</p>
</Link>
</div>
);
}} />
<Route exact path="/~publish/:ship/:blog/:post"
render={ (props) => {
let ship = props.match.params.ship.slice(1);
let blogId = props.match.params.blog;
let postId = props.match.params.post;
let blogLink = `/~publish/~${ship}/${blogId}`;
let blog = this.retrieveColl(blogId, ship);
let blogName = blog.title;
let postLink = `/~publish/~${ship}/${blogId}/${postId}`;
let post = this.retrievePost(postId, blogId, ship);
let postName = post.info.title;
return (
<div className="fl w-100 flex b-gray-30 bb">
<Link to="/~publish/recent">
<p className="fl gray-30 label-regular" style={{ marginLeft: 16}}>
Home
</p>
</Link>
<p className="fl gray-30 label-regular">
->
</p>
<Link to={blogLink}>
<p className="fl gray-30 label-regular">
{blogName}
</p>
</Link>
<p className="fl gray-30 label-regular">
->
</p>
<Link to={postLink}>
<p className="fl label-regular">
{postName}
</p>
</Link>
</div>
);
}} />
<Route exact path="/~publish/new"
render={ (props) => {
return (
<div className="fl w-100 flex b-gray-30 bb">
<Link to="/~publish/recent">
<p className="fl gray-30 label-regular" style={{ marginLeft: 16}}>
Home
</p>
</Link>
<p className="fl gray-30 label-regular">
->
</p>
<p className="fl label-regular">
New
</p>
</div>
);
}} />
<Route exact path="/~publish/:ship/:blog/new"
render={ (props) => {
let ship = props.match.params.ship.slice(1);
let blogId = props.match.params.blog;
let blogLink = `/~publish/~${ship}/${blogId}`;
let blog = this.retrieveColl(blogId, ship);
let blogName = blog.title;
return (
<div className="fl w-100 flex b-gray-30 bb">
<Link to="/~publish/recent">
<p className="fl gray-30 label-regular" style={{ marginLeft: 16}}>
Home
</p>
</Link>
<p className="fl gray-30 label-regular">
->
</p>
<Link to={blogLink}>
<p className="fl gray-30 label-regular">
{blogName}
</p>
</Link>
<p className="fl gray-30 label-regular">
->
</p>
<p className="fl label-regular">
New Post
</p>
</div>
);
}} />
</div>
);
}
}

View File

@ -1,26 +0,0 @@
import React, { Component } from 'react';
import _ from 'lodash';
import moment from 'moment';
import Mousetrap from 'mousetrap';
import classnames from 'classnames';
import { Sigil } from '/components/lib/icons/sigil';
import { isUrl, uuid, isDMStation } from '/lib/util';
export class ChatHeader extends Component {
render() {
return (
<div className="w-100 pa2 mb3 cf">
<div className="fl">
<p className="f3 sans-serif">{this.props.title}</p>
<div>
<p className="dib mid-gray mr2 sans-serif">{this.props.numPeople} Participants</p>
</div>
</div>
<div className="fr">
</div>
</div>
);
}
}

View File

@ -1,131 +0,0 @@
import React, { Component } from 'react';
import _ from 'lodash';
import moment from 'moment';
import Mousetrap from 'mousetrap';
import classnames from 'classnames';
import { Sigil } from '/components/lib/icons/sigil';
import { isUrl, uuid, isDMStation } from '/lib/util';
export class ChatInput extends Component {
/*
Props:
- station
- api
- store
- circle
- placeholder
- setPendingMessage
- scrollbarRef
*/
constructor(props) {
super(props);
this.state = {
message: ""
};
this.textareaRef = React.createRef();
this.messageSubmit = this.messageSubmit.bind(this);
this.messageChange = this.messageChange.bind(this);
}
componentDidMount() {
this.bindShortcuts();
}
bindShortcuts() {
Mousetrap(this.textareaRef.current).bind('enter', e => {
e.preventDefault();
e.stopPropagation();
this.messageSubmit(e);
});
}
messageChange(event) {
this.setState({message: event.target.value});
}
messageSubmit() {
let aud, sep;
let wen = Date.now();
let uid = uuid();
let aut = window.ship;
let config = this.props.store.configs[this.state.station];
if (isDMStation(this.props.station)) {
aud = this.props.station
.split("/")[1]
.split(".")
.map((mem) => `~${mem}/${this.props.circle}`);
} else {
aud = [this.props.station];
}
if (isUrl(this.state.message)) {
sep = {
url: this.state.message
}
} else {
sep = {
lin: {
msg: this.state.message,
pat: false
}
}
}
let message = {
uid,
aut,
wen,
aud,
sep,
};
this.props.api.hall({
convey: [message]
});
this.props.setPendingMessage(message);
console.log('ending message submit');
this.setState({
message: ""
});
// TODO: Push to end of event queue to let pendingMessages render before scrolling
// There's probably a better way to do this
setTimeout(() => {
if (this.props.scrollbarRef.current) this.props.scrollbarRef.current.scrollToBottom();
})
}
render() {
return (
<div className="w-100 pa2 mb3 cf flex">
<div className="fl mr2" style={{ flexBasis: 48 }}>
<Sigil ship={window.ship} size={44} />
</div>
<div className="fr" style={{ flexGrow: 1, border: "2px solid #e6e6e6" }}>
<textarea className="w-100 h-100 bn sans-serif pa2"
style={{
resize: "none"
}}
ref={this.textareaRef}
placeholder={this.props.placeholder}
value={this.state.message}
onChange={this.messageChange} />
</div>
</div>
);
}
}

View File

@ -1,40 +0,0 @@
import React, { Component } from 'react';
import { getStationDetails } from '/services';
export class ChatList extends Component {
componentDidMount() {
let path = `/public`;
this.props.api.bind(path, "PUT", this.props.hostship.slice(1));
}
renderChats() {
if (this.props.store.public[this.props.hostship]) {
const chats = this.props.store.public[this.props.hostship].map((cir) => {
const deets = getStationDetails(cir)
if (deets.type == "stream-chat" || deets.type == "stream-dm") {
return (
<div className="mt-2 text-500">
<a href={`/~chat/{cir}`}>{cir}</a>
</div>
)
} else {
return null;
}
});
return chats;
} else {
return null;
}
}
render() {
const chats = this.renderChats();
return (
<div>
<div className="text-600 mt-8">Chats</div>
{chats}
</div>
);
}
}

View File

@ -1,69 +0,0 @@
import React, { Component } from 'react';
import { isDMStation, getMessageContent } from '/lib/util';
import { Sigil } from '/components/lib/icons/sigil';
import classnames from 'classnames';
import moment from 'moment';
export class Message extends Component {
buildPostTitle(messageDetails) {
if (messageDetails.postUrl) {
return (
<a className="pr-12 text-600 underline"
href={messageDetails.postUrl}>
{messageDetails.postTitle}
</a>
)
} else {
return null;
}
}
renderContent(type) {
if (type === "text") {
return this.buildPostTitle(this.props.details);
} else if (type === "url") {
if (/(jpg|img|png|gif|tiff|jpeg|JPG|IMG|PNG|TIFF)$/.exec(this.props.details.content)) {
return (
<img src={this.props.details.content} style={{width:"30%"}}></img>
)
} else {
return (
<a href={this.props.details.content} target="_blank">{this.props.details.content}</a>
)
}
} else if (type === "exp") {
return (
<div className="text-body">
<div className="text-mono">{this.props.details.content}</div>
<pre className="text-mono mt-0">{this.props.details.res}</pre>
</div>
)
} else if (['new item', 'edited item'].includes(type)) {
return <span className="text-body" dangerouslySetInnerHTML={{__html: this.props.details.snip}}></span>
} else if (type === "lin") {
return (
<p className="sans-serif">{this.props.details.content}</p>
);
} else {
return <span className="text-mono">{'<unknown message type>'}</span>;
}
}
render() {
return (
<div className="w-100 pa2 mb3 cf flex">
<div className="fl mr2">
<Sigil ship={this.props.msg.aut} size={44} />
</div>
<div className="fr" style={{ flexGrow: 1 }}>
<div className="mb2">
<p className="sans-serif gray dib mr2">~{this.props.msg.aut}</p>
<p className="sans-serif gray dib">{moment.unix(this.props.msg.wen).format('hh:mm')}</p>
</div>
{this.renderContent(this.props.details.type)}
</div>
</div>
);
}
}

View File

@ -0,0 +1,68 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { PostPreview } from '/components/post-preview';
import moment from 'moment';
import { Link } from 'react-router-dom';
export class PostBody extends Component {
constructor(props){
super(props)
}
renderA(what, node, attr) {
let aStyle = { textDecorationLine: "underline" };
let children = what.map((item, key) => {
if (typeof(item) === 'string') {
return item;
} else {
let newAttr = Object.assign({style: aStyle, key: key}, item.ga);
return this.parseContent(item.c, item.gn, newAttr);
}
});
const element = React.createElement(node, Object.assign({style: aStyle}, attr), children);
return element;
}
renderIMG(what, node, attr) {
let imgStyle = {
width: "100%",
height: "auto"
};
let newAttr = Object.assign({style: imgStyle}, attr);
const element = React.createElement(node, newAttr);
return element;
}
renderDefault(what, node, attr) {
let children = what.map((item, key) => {
if (typeof(item) === 'string') {
return item;
} else {
let newAttr = Object.assign({key: key}, item.ga);
return this.parseContent(item.c, item.gn, newAttr);
}
});
const element = React.createElement(node, attr, children);
return element;
}
parseContent(what, node, attr) {
switch (node) {
case "a":
return this.renderA(what, node, attr);
case "img":
return this.renderIMG(what, node, attr);
default:
return this.renderDefault(what, node, attr);
}
}
render() {
let page = this.parseContent(this.props.body.c, this.props.body.gn, this.props.body.ga);
return page;
}
}

View File

@ -0,0 +1,66 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { dateToDa } from '/lib/util';
import moment from 'moment';
import { Link } from 'react-router-dom';
import { PostSnippet } from '/components/post-snippet';
export class PostPreview extends Component {
constructor(props) {
super(props);
moment.updateLocale('en', {
relativeTime: {
past: function(input) {
return input === 'just now'
? input
: input + ' ago'
},
s : 'just now',
future : 'in %s',
m : '1m',
mm : '%dm',
h : '1h',
hh : '%dh',
d : '1d',
dd : '%dd',
M : '1 month',
MM : '%d months',
y : '1 year',
yy : '%d years',
}
});
}
render() {
let comments = this.props.post.numComments == 1
? '1 comment'
: `${this.props.post.numComments} comments`
let date = moment(this.props.post.date).fromNow();
let authorDate = `~${this.props.post.author}${date}`
let collLink = "/~publish/~" +
this.props.post.author + "/" +
this.props.post.collectionName;
let postLink = collLink + "/" + this.props.post.postName;
// let postTitle =
return (
<div className="w-336 ma2">
<Link to={postLink}>
<p className="body-large b">
{this.props.post.postTitle}
</p>
<PostSnippet
body={this.props.post.postBody}
/>
</Link>
<p className="label-small gray-50">
{authorDate}
</p>
</div>
);
}
}

View File

@ -0,0 +1,41 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { PostPreview } from '/components/post-preview';
import moment from 'moment';
import { Link } from 'react-router-dom';
export class PostSnippet extends Component {
constructor(props){
super(props)
}
render() {
let elem = this.props.body.c.find((elem) => {
return elem.gn === "p" &&
typeof(elem.c[0]) === "string";
});
let string = elem.c[0];
let words = string.split(" ");
let snip = new String(string);
if (words.length > 0 && words[0].length > 280) {
snip = words[0].slice(0, 280).trim() + " [...]";
} else if (snip.length > 280){
while (snip.length > 280) {
snip = snip.split(" ").slice(0, -1).join(" ");
}
snip += " [...]";
}
return (
<p className="body-regular-400">
{snip}
</p>
);
}
}

View File

@ -0,0 +1,172 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { PostPreview } from '/components/post-preview';
import moment from 'moment';
import { Link } from 'react-router-dom';
import { PostBody } from '/components/post-body';
export class Post extends Component {
constructor(props){
super(props)
moment.updateLocale('en', {
relativeTime: {
past: function(input) {
return input === 'just now'
? input
: input + ' ago'
},
s : 'just now',
future : 'in %s',
m : '1m',
mm : '%dm',
h : '1h',
hh : '%dh',
d : '1d',
dd : '%dd',
M : '1 month',
MM : '%d months',
y : '1 year',
yy : '%d years',
}
});
}
buildPosts(){
let blogId = this.props.blogId;
let ship = this.props.ship;
let blog = this.retrieveColl(blogId, ship);
console.log("buildposts", blog);
let pinProps = blog.order.pin.map((post) => {
return this.buildPostPreviewProps(post, blogId, ship, true);
});
let unpinProps = blog.order.unpin.map((post) => {
return this.buildPostPreviewProps(post, blogId, ship, false);
});
return pinProps.concat(unpinProps);
}
buildPostPreviewProps(post, coll, who, pinned){
let pos = this.retrievePost(post, coll, who);
let col = this.retrieveColl(coll, who);
let com = this.retrieveComments(post, coll, who);
return {
postTitle: pos.info.title,
postName: post,
postSnippet: "body snippet",
numComments: com.length,
collectionTitle: col.title,
collectionName: coll,
author: who,
date: pos.info["date-created"],
pinned: pinned,
}
}
retrievePost(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].post;
} else {
return this.props.subs[who][coll].posts[post].post;
}
}
retrieveComments(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].comments;
} else {
return this.props.subs[who][coll].posts[post].comments;
}
}
retrieveColl(coll, who) {
if (who === window.ship) {
return this.props.pubs[coll];
} else {
return this.props.subs[who][coll];
}
}
render() {
let ship = this.props.ship;
let blog = this.retrieveColl(this.props.blogId, this.props.ship);
let post = this.retrievePost(this.props.postId, this.props.blogId, this.props.ship);
let comments = this.retrieveComments(this.props.postId, this.props.blogId, this.props.ship);
let blogLink = `/~publish/~${this.props.ship}/${this.props.blogId}`;
let blogLinkText = `<- Back to ${blog.info.title}`;
let editLink = `/~publish/~${this.props.ship}/${this.props.blogId}/${this.props.postId}/edit`;
let date = moment(post.info["date-created"]).fromNow();
let authorDate = `${post.info.creator}${date}`;
// change unpin to concatenation of pinned and unpinned
let morePosts = blog.order.unpin.slice(0,10).map((pid) => {
let p = this.retrievePost(pid, this.props.blogId, this.props.ship);
let color = (pid == this.props.postId) ? "black" : "gray-50";
let postLink = `/~publish/~${this.props.ship}/${this.props.blogId}/${pid}`;
return (
<Link to={postLink} className="label-regular">
<p className={color}>{p.info.title}</p>
</Link>
);
});
return (
<div className="w-688 flex-col center mt4">
<Link to={blogLink}>
<p className="body-regular">
{blogLinkText}
</p>
</Link>
<h2>{post.info.title}</h2>
<div className="mb4">
<p className="fl label-small gray-50">{authorDate}</p>
<Link to={editLink}>
<p className="label-regular gray-50 fr">Edit</p>
</Link>
</div>
<div className="cb">
<PostBody
body={post.body}
/>
</div>
<hr className="gray-50 w-680"/>
<div className="cb mt3 mb4">
<p className="gray-50 body-large b">
{comments.length}
<span className="black">
Comments
</span>
</p>
<p className="cl body-regular">
Show Comments
</p>
</div>
<hr className="gray-50 w-680"/>
<div className="cb flex-col">
<p className="label-regular b mb1">{blog.info.title}</p>
<p className="label-regular gray-30 mb2">Hosted by {blog.info.owner}</p>
{morePosts}
</div>
</div>
);
}
}

View File

@ -0,0 +1,72 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { dateToDa } from '/lib/util';
import moment from 'moment';
import { Link } from 'react-router-dom';
import { PostSnippet } from '/components/post-snippet';
export class RecentPreview extends Component {
constructor(props) {
super(props);
moment.updateLocale('en', {
relativeTime: {
past: function(input) {
return input === 'just now'
? input
: input + ' ago'
},
s : 'just now',
future : 'in %s',
m : '1m',
mm : '%dm',
h : '1h',
hh : '%dh',
d : '1d',
dd : '%dd',
M : '1 month',
MM : '%d months',
y : '1 year',
yy : '%d years',
}
});
}
render() {
let comments = this.props.post.numComments == 1
? '1 comment'
: `${this.props.post.numComments} comments`
let date = moment(this.props.post.date).fromNow();
let authorDate = `~${this.props.post.author}${date}`
let collLink = "/~publish/~" +
this.props.post.author + "/" +
this.props.post.collectionName;
let postLink = collLink + "/" + this.props.post.postName;
return (
<div className="w-336 ma2">
<Link to={postLink}>
<p className="body-large b">
{this.props.post.postTitle}
</p>
<PostSnippet
body={this.props.post.postBody}
/>
</Link>
<p className="label-small gray-50">
{comments}
</p>
<Link to={collLink}>
<p className="body-regular gray-50">
{this.props.post.collectionTitle}
</p>
</Link>
<p className="label-small gray-50">
{authorDate}
</p>
</div>
);
}
}

View File

@ -0,0 +1,168 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { RecentPreview } from '/components/recent-preview';
export class Recent extends Component {
constructor(props){
super(props)
}
buildRecent() {
var recent = [];
var group = {
date: new Date(),
posts: [],
};
for (var i=0; i<this.props.latest.length; i++) {
let index = this.props.latest[i];
let post = this.retrievePost(index.post, index.coll, index.who);
let postDate = new Date(post.info["date-created"]);
let postProps = this.buildPostPreviewProps(index.post, index.coll, index.who);
if (group.posts.length == 0) {
group = {
date: this.roundDay(postDate),
posts: [postProps],
}
if (i == (this.props.latest.length - 1)) {
recent.push(Object.assign({}, group));
}
} else if ( this.sameDay(group.date, postDate) ) {
group.posts.push(postProps) ;
} else {
recent.push(Object.assign({}, group));
group = {
date: this.roundDay(postDate),
posts: [postProps],
}
if (i == (this.props.latest.length - 1)) {
recent.push(Object.assign({}, group));
}
}
}
return recent;
}
buildPostPreviewProps(post, coll, who){
let pos = this.retrievePost(post, coll, who);
let col = this.retrieveColl(coll, who);
let com = this.retrieveComments(post, coll, who);
return {
postTitle: pos.info.title,
postName: post,
postBody: pos.body,
numComments: com.length,
collectionTitle: col.title,
collectionName: coll,
author: who,
date: pos.info["date-created"]
}
}
retrievePost(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].post;
} else {
return this.props.subs[who][coll].posts[post].post;
}
}
retrieveComments(post, coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].posts[post].comments;
} else {
return this.props.subs[who][coll].posts[post].comments;
}
}
retrieveColl(coll, who) {
if (who === window.ship) {
return this.props.pubs[coll].info;
} else {
return this.props.subs[who][coll].info;
}
}
roundDay(d) {
let result = new Date(d.getTime());
result.setHours(0);
result.setMinutes(0);
result.setSeconds(0);
result.setMilliseconds(0);
return result
}
sameDay(d1, d2) {
return d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate() &&
d1.getFullYear() === d2.getFullYear();
}
dateLabel(d) {
let today = new Date();
let yesterday = new Date(today.getTime() - (1000*60*60*24));
if (this.sameDay(d, today)) {
return "Today";
} else if (this.sameDay(d, yesterday)) {
return "Yesterday";
} else if ( d.getFullYear() === today.getFullYear() ) {
let month = d.toLocaleString('en-us', {month: 'long'});
let day = d.getDate();
return month + ' ' + day;
} else {
let month = d.toLocaleString('en-us', {month: 'long'});
let day = d.getDate();
let year = d.getFullYear();
return month + ' ' + day + ' ' + year;
}
}
render() {
let recent = this.buildRecent();
let body = recent.map((group) => {
let posts = group.posts.map((post) => {
return (
<RecentPreview
post={post}
/>
);
});
let date = this.dateLabel(group.date);
return (
<div>
<div className="w-100 h-80">
<h2 className="gray-50">
{date}
</h2>
</div>
<div className="flex flex-wrap">
{posts}
</div>
</div>
);
});
return (
<div className="flex-col">
{body}
</div>
);
}
}

View File

@ -6,14 +6,15 @@ import _ from 'lodash';
import { api } from '/api';
import { store } from '/store';
import { Skeleton } from '/components/skeleton';
import { Sidebar } from '/components/sidebar';
import { CollectionList } from '/components/collection-list';
import { Recent } from '/components/recent';
import { Header } from '/components/header';
import { Blog } from '/components/blog';
import { Post } from '/components/post';
export class Root extends Component {
constructor(props) {
super(props);
this.state = store.collections;
this.state = store.state;
console.log("root.state", this.state);
@ -22,49 +23,70 @@ export class Root extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route exact path="/~publish"
render={ (props) => {
return (
<div className="cf h-100 w-100 absolute">
<div className="fl w-100 h3">
<h1>Publish</h1>
<div className="fl w-100">
<BrowserRouter>
<Header {...this.state} />
<Route exact path="/~publish/recent"
render={ (props) => {
return (
<div className="fl w-100">
<Recent
{...this.state}
/>
</div>
<div className="fl flex w-100 h-100">
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
<p className="fl w-100 h2 bb">
Latest
</p>
</div>
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
<p className="fl w-100 h2 bb">
Subs
</p>
<CollectionList
list={this.state.subs}
/>
</div>
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
<p className="fl w-100 h2 bb">
Pubs
</p>
<CollectionList
list={this.state.pubs}
/>
</div>
<div className="fl h-100 overflow-x-hidden" style={{ flexBasis: 400 }}>
<p className="fl w-100 h2 bb">
Create Button? idk
</p>
</div>
);
}} />
<Route exact path="/~publish/subs"
render={ (props) => {
return (
<div className="fl w-100">
<Recent
{...this.state}
/>
</div>
</div>
);
}} />
</div>
</BrowserRouter>
)
);
}} />
<Route exact path="/~publish/pubs"
render={ (props) => {
return (
<div className="fl w-100">
<Recent
{...this.state}
/>
</div>
);
}} />
<Route exact path="/~publish/:ship/:blog"
render={ (props) => {
return (
<div className="fl w-100">
<Blog
blogId = {props.match.params.blog}
ship = {props.match.params.ship.slice(1)}
{...this.state}
/>
</div>
);
}} />
<Route exact path="/~publish/:ship/:blog/:post"
render={ (props) => {
return (
<div className="fl w-100">
<Post
blogId = {props.match.params.blog}
postId = {props.match.params.post}
ship = {props.match.params.ship.slice(1)}
{...this.state}
/>
</div>
);
}} />
</BrowserRouter>
</div>
);
}
}

View File

@ -1,26 +0,0 @@
import React, { Component } from 'react';
import classnames from 'classnames';
export class SidebarItem extends Component {
onClick() {
const { props } = this;
props.history.push('/~chat/' + props.title);
}
render() {
const { props } = this;
let selectedCss = !!props.selected ? 'bg-light-gray' : 'bg-white pointer';
return (
<div className={'pa3 ' + selectedCss} onClick={this.onClick.bind(this)}>
<div className='w-100 v-mid'>
<h3 className='w-60 dib sans-serif'>{props.title}</h3>
<p className='w-40 tr dib sans-serif gray'>{props.datetime}</p>
</div>
<p className='pt2 sans-serif gray'>{props.description}</p>
</div>
)
}
}

View File

@ -1,58 +0,0 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { Scrollbars } from 'react-custom-scrollbars';
import moment from 'moment';
import { getMessageContent } from '/lib/util';
import { SidebarItem } from '/components/sidebar-item';
export class Sidebar extends Component {
render() {
const { props } = this;
let station = props.match.params.ship + '/' + props.match.params.station;
let sidebarItems = props.circles.map((cir) => {
let msg = props.messagePreviews[cir];
let parsed = getMessageContent(msg);
let wen = moment.unix(msg.wen / 1000).from(moment.utc());
return (
<CollectionItem
title={cir}
description={parsed.content}
datetime={wen}
selected={station === cir}
history={props.history}
/>
);
});
return (
<div className="h-100 w-100 overflow-x-hidden flex flex-column">
<div className="pl3 pr3 pt2 pb2 cf">
<h2 className="dib lh-title sans-serif w-50 f2">Publish</h2>
<a className="dib tr lh-title sans-serif w-50 f4 underline">+ New</a>
</div>
<div className='mt2 pl3 pr3 mb2 w-100'>
<div>My Collections</div>
</div>
<div style={{ flexGrow: 1 }}>
<Scrollbars
ref={this.scrollbarRef}
renderTrackHorizontal={props => <div style={{display: "none"}}/>}
onScrollStop={this.onScrollStop}
renderView={props => <div {...props} />}
style={{ height: '100%' }}
autoHide>
{sidebarItems}
</Scrollbars>
</div>
<div className='mt2 pl3 pr3 mb2 w-100'>
<div>My Subscriptions</div>
</div>
</div>
)
}
}

View File

@ -1,19 +0,0 @@
import React, { Component } from 'react';
import classnames from 'classnames';
export class Skeleton extends Component {
render() {
return (
<div className="cf h-100 w-100 absolute flex">
<div className="fl h-100 br overflow-x-hidden" style={{ flexBasis: 320 }}>
{this.props.sidebar}
</div>
<div className="h-100 fr" style={{ flexGrow: 1 }}>
{this.props.children}
</div>
</div>
);
}
}

View File

@ -1,54 +0,0 @@
// let newSep = {
// sep: {
// inv: {
// inv: true,
// cir: "~zod/null"
// }
// },
// wen: (new Date()).getTime()
// };
// import { isDMStation, getMessageContent } from '/lib/util';
// import _ from 'lodash';
//
// export class DmsReducer {
// reduce(reports, store) {
// reports.forEach((rep) => {
// switch (rep.type) {
// case "circles":
// if (_.isArray(rep.data)) {
// let newStations = rep.data.filter(station => isDMStation(`${rep.from.ship}/${station}`));
// store.dms.stations = _.uniq([...store.dms.stations, ...newStations]);
// store.dms.stored = true;
// } else if (rep.data.cir) {
// if (rep.data.add) {
// store.dms.stations = _.uniq([...store.dms.stations, rep.data.cir]);
// } else {
// store.dms.stations = _.filter(store.dms.stations, s => s !== rep.data.cir);
// }
// }
// break;
//
// case "circle.gram":
// this.addStationsFromInvites([rep.data], store);
// break;
//
// case "circle.nes":
// this.addStationsFromInvites(rep.data, store);
// break;
// }
// });
// }
//
// addStationFromInvite(msgs, store) {
// let inviteStations = [];
// msgs.forEach(msg => {
// let msgContent = getMessageContent(msg);
// if (msgContent.type === "inv" && isDMStation(msgContent.content.sta)) {
// inviteStations.push(msgContent.content.sta);
// }
// });
//
// store.dms.stations = [...store.dms.stations, ...inviteStations];
// }
// }

View File

@ -1,6 +1,7 @@
class Store {
constructor() {
this.collections = window.injectedState;
this.state = window.injectedState;
console.log("store.state", this.state);
this.setState = () => {};
}

View File

@ -224,8 +224,8 @@
(~(get by subs.sat) who.del col.del)
=/ new=collection
?~ old
[dat.del ~ ~]
[dat.del pos.u.old com.u.old]
[dat.del ~ ~ ~ ~]
[dat.del pos.u.old com.u.old order.u.old]
=? pubs.sat =(our.bol who.del)
(~(put by pubs.sat) col.del new)
=? subs.sat !=(our.bol who.del)
@ -239,13 +239,14 @@
(~(get by subs.sat) who.del col.del)
=/ new=collection
?~ old
[[%.n ~] (my [pos.del dat.del] ~) ~]
[col.u.old (~(put by pos.u.old) pos.del dat.del) com.u.old]
[[%.n ~] (my [pos.del dat.del] ~) ~ ~ ~]
[col.u.old (~(put by pos.u.old) pos.del dat.del) com.u.old order.u.old]
=? pubs.sat =(our.bol who.del)
(~(put by pubs.sat) col.del new)
=? subs.sat !=(our.bol who.del)
(~(put by subs.sat) [who.del col.del] new)
=. da-this (da-insert who.del col.del pos.del)
=? da-this ?=(~ old)
(da-insert who.del col.del pos.del)
(da-emil (affection del))
::
%comments
@ -255,8 +256,8 @@
(~(get by subs.sat) who.del col.del)
=/ new=collection
?~ old
[[%.n ~] ~ (my [pos.del dat.del] ~)]
[col.u.old pos.u.old (~(put by com.u.old) pos.del dat.del)]
[[%.n ~] ~ (my [pos.del dat.del] ~) ~ ~]
[col.u.old pos.u.old (~(put by com.u.old) pos.del dat.del) order.u.old]
=? pubs.sat =(our.bol who.del)
(~(put by pubs.sat) col.del new)
=? subs.sat !=(our.bol who.del)
@ -291,7 +292,7 @@
(~(put in unread.sat) who coll post)
:: insertion sort into latest
::
=/ new-date=@da (need (get-date-for-index who coll post))
=/ new-date=@da date-created:(need (get-post-by-index who coll post))
=/ pre=(list [@p @tas @tas]) ~
=/ suf=(list [@p @tas @tas]) latest.sat
@ -299,13 +300,45 @@
|-
?~ suf
(weld pre [who coll post]~)
=/ i-date=@da (need (get-date-for-index i.suf))
=/ i-date=@da date-created:(need (get-post-by-index i.suf))
?: (gte new-date i-date)
(weld pre [[who coll post] suf])
%= $
suf t.suf
pre (snoc pre i.suf)
==
:: insertion sort into order
::
=/ new-post=post-info (need (get-post-by-index who coll post))
=/ col=collection (need (get-coll-by-index who coll))
::
=/ pre=(list @tas) ~
=/ suf=(list @tas)
?: pinned.new-post
pin.order.col
unpin.order.col
::
=/ new-list=(list @tas)
|-
?~ suf
(snoc pre post)
=/ i-date=@da date-created:(need (get-post-by-index who coll i.suf))
?: (gte date-created.new-post i-date)
(weld pre [post suf])
%= $
suf t.suf
pre (snoc pre i.suf)
==
::
=. order.col
?: pinned.new-post
[new-list unpin.order.col]
[pin.order.col new-list]
::
=? pubs.sat =(our.bol who)
(~(put by pubs.sat) coll col)
=? subs.sat !=(our.bol who)
(~(put by subs.sat) [who coll] col)
da-this
--
:: +bake: apply delta
@ -345,9 +378,9 @@
::
==
::
++ get-date-for-index
++ get-post-by-index
|= [who=@p coll=@tas post=@tas]
^- (unit @da)
^- (unit post-info)
=/ col=(unit collection)
?: =(our.bol who)
(~(get by pubs.sat) coll)
@ -357,7 +390,14 @@
(~(get by pos.u.col) post)
?~ pos ~
?: ?=(%.n -.u.pos) ~
[~ date-created.-.p.u.pos]
[~ -.p.u.pos]
::
++ get-coll-by-index
|= [who=@p coll=@tas]
^- (unit collection)
?: =(our.bol who)
(~(get by pubs.sat) coll)
(~(get by subs.sat) coll)
::
++ made
|= [wir=wire wen=@da mad=made-result:ford]
@ -389,7 +429,7 @@
(bake [%collection our.bol col dat])
:: 1st part of multi-part, store partial delta and don't process it
::
=/ del=delta [%total our.bol col dat ~ ~]
=/ del=delta [%total our.bol col dat ~ ~ ~ ~]
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
[~ this]
::
@ -404,6 +444,7 @@
dat
pos.dat.u.partial.u.awa
com.dat.u.partial.u.awa
[~ ~]
==
=. awaiting.sat (~(del by awaiting.sat) col)
(bake del)
@ -417,6 +458,7 @@
dat
pos.dat.u.partial.u.awa
com.dat.u.partial.u.awa
[~ ~]
==
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
[~ this]
@ -446,7 +488,7 @@
(bake [%post our.bol col pos dat])
:: 1st part of multi-part, store partial delta and don't process it
::
=/ del=delta [%total our.bol col [%.n ~] (my [pos dat] ~) ~]
=/ del=delta [%total our.bol col [%.n ~] (my [pos dat] ~) ~ ~ ~]
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
[~ this]
::
@ -461,6 +503,7 @@
col.dat.u.partial.u.awa
(~(put by pos.dat.u.partial.u.awa) pos dat)
com.dat.u.partial.u.awa
[~ ~]
==
=. awaiting.sat (~(del by awaiting.sat) col)
(bake del)
@ -474,6 +517,7 @@
col.dat.u.partial.u.awa
(~(put by pos.dat.u.partial.u.awa) pos dat)
com.dat.u.partial.u.awa
[~ ~]
==
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
[~ this]
@ -503,7 +547,7 @@
(bake [%comments our.bol col pos dat])
:: 1st part of multi-part, store partial delta and don't process it
::
=/ del=delta [%total our.bol col [%.n ~] ~ (my [pos dat] ~)]
=/ del=delta [%total our.bol col [%.n ~] ~ (my [pos dat] ~) ~ ~]
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
[~ this]
::
@ -518,6 +562,7 @@
col.dat.u.partial.u.awa
pos.dat.u.partial.u.awa
(~(put by com.dat.u.partial.u.awa) pos dat)
[~ ~]
==
=. awaiting.sat (~(del by awaiting.sat) col)
(bake del)
@ -531,6 +576,7 @@
col.dat.u.partial.u.awa
pos.dat.u.partial.u.awa
(~(put by com.dat.u.partial.u.awa) pos dat)
[~ ~]
==
=. awaiting.sat (~(put by awaiting.sat) col builds.u.awa `del)
[~ this]
@ -759,7 +805,7 @@
::
%unsubscribe
=/ new-latest=(list [@p @tas @tas])
%+ skip latest.sat
%+ skim latest.sat
|= [who=@p coll=@tas post=@tas]
?& =(who our.bol)
=(coll coll.act)
@ -767,7 +813,7 @@
::
=/ new-unread=(set [@p @tas @tas])
%- sy
%+ skip ~(tap in unread.sat)
%+ skim ~(tap in unread.sat)
|= [who=@p coll=@tas post=@tas]
?& =(who our.bol)
=(coll coll.act)
@ -808,44 +854,58 @@
[[[~ %js] [%'~publish' %index ~]] ~]
:_ this
[ost.bol %http-response (js-response:app js)]~
::
:: home page; redirect to recent
::
[[~ [%'~publish' ~]] ~]
=/ hym=manx (index (state-to-json sat))
:_ this
[ost.bol %http-response (manx-response:app hym)]~
[ost.bol %http-response (redirect:app '/~publish/recent')]~
:: recent page
::
::
[[~ [%'~publish' @t ~]] ~]
=/ who=(unit ship) (rush i.t.site.request-line ;~(pfix sig fed:ag))
?~ who
:_ this
[ost.bol %http-response not-found:app]~
=/ hym=manx
;div: {<u.who>} root page
[[~ [%'~publish' %recent ~]] ~]
=/ hym=manx (index (state-to-json sat))
:_ this
[ost.bol %http-response (manx-response:app hym)]~
:: subscriptions
::
:: forum view
[[~ [%'~publish' %subs ~]] ~]
=/ hym=manx (index (state-to-json sat))
:_ this
[ost.bol %http-response (manx-response:app hym)]~
:: published
::
[[~ [%'~publish' %pubs ~]] ~]
=/ hym=manx (index (state-to-json sat))
:_ this
[ost.bol %http-response (manx-response:app hym)]~
:: blog
::
[[~ [%'~publish' @t @t ~]] ~]
=/ who=(unit ship) (rush i.t.site.request-line ;~(pfix sig fed:ag))
=/ coll=@tas i.t.t.site.request-line
:: ?~ who
:_ this
[ost.bol %http-response not-found:app]~
::
:: post view
=/ who=(unit @p) (slaw %p i.t.site.request-line)
=/ blog=@tas i.t.t.site.request-line
=/ hym=manx (index (state-to-json sat))
:_ this
[ost.bol %http-response (manx-response:app hym)]~
:: blog post
::
[[~ [%'~publish' @t @t @t ~]] ~]
=/ who=(unit ship) (rush i.t.site.request-line ;~(pfix sig fed:ag))
=/ coll=@tas i.t.t.site.request-line
=/ post=@tas i.t.t.t.site.request-line
:: ?~ who
:_ this
[ost.bol %http-response not-found:app]~
:: local request
=/ who=(unit @p) (slaw %p i.t.site.request-line)
=/ blog=@tas i.t.t.site.request-line
=/ post=@tas i.t.t.t.site.request-line
::
?~ who [[ost.bol %http-response not-found:app]~ this]
=/ col=(unit collection)
?: =(u.who our.bol)
(~(get by pubs.sat) blog)
(~(get by subs.sat) u.who blog)
?~ col [[ost.bol %http-response not-found:app]~ this]
=/ pos (~(get by pos.u.col) post)
?~ pos [[ost.bol %http-response not-found:app]~ this]
=/ hym=manx (index (state-to-json sat))
:_ this
[ost.bol %http-response (manx-response:app hym)]~
::
==
::

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -125,18 +125,27 @@
^- json
%- pairs:enjs:format
:~ info+(collection-build-to-json col.col)
::
:+ %posts
%a
%+ turn ~(tap in ~(key by pos.col))
|= post=@tas
^- json
%o
%+ roll ~(tap in ~(key by pos.col))
|= [post=@tas out=(map @t json)]
=/ post-build (~(got by pos.col) post)
=/ comm-build (~(got by com.col) post)
%+ ~(put by out)
post
%- pairs:enjs:format
:~ name+s+post
post+(post-build-to-json post-build)
:~ post+(post-build-to-json post-build)
comments+(comment-build-to-json comm-build)
==
::
:- %order
%- pairs:enjs:format
:~ pin+a+(turn pin.order.col |=(s=@tas [%s s]))
unpin+a+(turn unpin.order.col |=(s=@tas [%s s]))
==
==
::
++ state-to-json
@ -144,25 +153,31 @@
^- json
%- pairs:enjs:format
:~ :+ %pubs
%a
%+ turn ~(tap by pubs.sat)
|= [nom=@tas col=collection]
^- json
%- pairs:enjs:format
:~ [%coll s+nom]
[%data (total-build-to-json col)]
==
%o
%+ roll ~(tap by pubs.sat)
|= [[nom=@tas col=collection] out=(map @t json)]
%+ ~(put by out)
nom
(total-build-to-json col)
::
:+ %subs
%a
%+ turn ~(tap by subs.sat)
|= [[who=@p nom=@tas] col=collection]
^- json
%- pairs:enjs:format
:~ [%coll s+nom]
[%who (ship:enjs:format who)]
[%data (total-build-to-json col)]
==
%o
%- ~(rep by subs.sat)
|= $: [[who=@p nom=@tas] col=collection]
out=(map @t [%o (map @t json)])
==
=/ shp=@t (rsh 3 1 (scot %p who))
?: (~(has by out) shp)
%+ ~(put by out)
shp
:- %o
%+ ~(put by +:(~(got by out) shp))
nom
(total-build-to-json col)
%+ ~(put by out)
shp
:- %o
(my [nom (total-build-to-json col)] ~)
::
:+ %latest
%a

View File

@ -104,6 +104,7 @@
$: col=(each collection-info tang)
pos=(map @tas (each [post-info manx] tang))
com=(map @tas (each (list [comment-info manx]) tang))
order=[pin=(list @tas) unpin=(list @tas)]
==
::
+$ state