mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-19 04:41:37 +03:00
Merge branch 'master' of github.com:urbit/interface
This commit is contained in:
commit
63fca9a2a2
1
.gitignore
vendored
1
.gitignore
vendored
@ -71,3 +71,4 @@ apps/*/dist/
|
||||
# vim swap files
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
|
47
apps/publish/package-lock.json
generated
47
apps/publish/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
112
apps/publish/src/js/components/blog.js
Normal file
112
apps/publish/src/js/components/blog.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
278
apps/publish/src/js/components/header.js
Normal file
278
apps/publish/src/js/components/header.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
68
apps/publish/src/js/components/post-body.js
Normal file
68
apps/publish/src/js/components/post-body.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
66
apps/publish/src/js/components/post-preview.js
Normal file
66
apps/publish/src/js/components/post-preview.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
41
apps/publish/src/js/components/post-snippet.js
Normal file
41
apps/publish/src/js/components/post-snippet.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
172
apps/publish/src/js/components/post.js
Normal file
172
apps/publish/src/js/components/post.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
72
apps/publish/src/js/components/recent-preview.js
Normal file
72
apps/publish/src/js/components/recent-preview.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
168
apps/publish/src/js/components/recent.js
Normal file
168
apps/publish/src/js/components/recent.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
// }
|
||||
// }
|
@ -1,6 +1,7 @@
|
||||
class Store {
|
||||
constructor() {
|
||||
this.collections = window.injectedState;
|
||||
this.state = window.injectedState;
|
||||
console.log("store.state", this.state);
|
||||
this.setState = () => {};
|
||||
}
|
||||
|
||||
|
@ -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
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user