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

This commit is contained in:
Logan Allen 2019-06-21 16:09:10 -07:00
commit 65e4a03f7e
26 changed files with 1086 additions and 375 deletions

View File

@ -60,3 +60,11 @@ h2 {
.bg-v-light-gray {
background-color: #f9f9f9;
}
.gray-30 {
color: #B1B2B3;
}
.green-medium {
color: #2ED196;
}

View File

@ -40,6 +40,14 @@ gulp.task('jsx-transform', function(cb) {
.pipe(gulp.dest('dist'));
});
gulp.task('tile-jsx-transform', function(cb) {
return gulp.src('tile/**/*.js')
.pipe(sucrase({
transforms: ['jsx']
}))
.pipe(gulp.dest('dist'));
});
gulp.task('js-imports', function(cb) {
return gulp.src('dist/index.js')
.pipe(rollup({
@ -72,6 +80,35 @@ gulp.task('js-imports', function(cb) {
.on('end', cb);
});
gulp.task('tile-js-imports', function(cb) {
return gulp.src('dist/tile.js')
.pipe(rollup({
plugins: [
commonjs({
namedExports: {
'node_modules/react/index.js': [ 'Component' ],
}
}),
rootImport({
root: `${__dirname}/dist/js`,
useEntry: 'prepend',
extensions: '.js'
}),
json(),
globals(),
builtins(),
resolve()
]
}, 'umd'))
.on('error', function(e){
console.log(e);
cb();
})
.pipe(gulp.dest('./urbit/app/write/js/'))
.on('end', cb);
});
gulp.task('js-minify', function () {
return gulp.src('./urbit/app/write/js/index.js')
.pipe(minify())
@ -101,13 +138,15 @@ gulp.task('urbit-copy', function () {
});
gulp.task('js-bundle-dev', gulp.series('jsx-transform', 'js-imports'));
gulp.task('tile-js-bundle-dev', gulp.series('tile-jsx-transform', 'tile-js-imports'));
gulp.task('js-bundle-prod', gulp.series('jsx-transform', 'js-imports', 'js-minify', 'js-cachebust'))
gulp.task('bundle-dev',
gulp.series(
gulp.parallel(
'css-bundle',
'js-bundle-dev'
'js-bundle-dev',
'tile-js-bundle-dev'
),
'urbit-copy'
)
@ -124,7 +163,10 @@ gulp.task('bundle-prod',
);
gulp.task('default', gulp.series('bundle-dev'));
gulp.task('watch', gulp.series('default', function() {
gulp.watch('tile/**/*.js', gulp.parallel('tile-js-bundle-dev'));
gulp.watch('src/**/*.js', gulp.parallel('js-bundle-dev'));
gulp.watch('src/**/*.css', gulp.parallel('css-bundle'));

View File

@ -95,6 +95,18 @@ h4 {
color: #B1B2B3;
}
.green {
color: #2AA779;
}
.green-medium {
color: #2ED196;
}
.red {
color: #EE5432;
}
.w-336 {
width: 336px;
}
@ -182,7 +194,11 @@ h4 {
.h-inner {
height: calc(100% - 124px);
top: 124px;
top: 48px;
}
.h-footer {
height: 76px;
}
::placeholder {

View File

@ -0,0 +1,110 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { Comment } from '/components/lib/comment';
import { CommentBox } from '/components/lib/comment-box';
import { Sigil } from '/components/lib/icons/sigil';
export class Comments extends Component {
constructor(props){
super(props);
this.state = {
show: false,
commentBody: '',
awaiting: false,
}
this.toggleDisplay = this.toggleDisplay.bind(this);
this.commentChange = this.commentChange.bind(this);
this.postComment = this.postComment.bind(this);
}
commentChange(evt) {
this.setState({commentBody: evt.target.value});
}
toggleDisplay() {
this.setState({show: !this.state.show});
}
postComment() {
let comment = {
"new-comment": {
who: this.props.ship,
coll: this.props.blogId,
name: this.props.postId,
content: this.state.commentBody,
}
};
console.log("post comment", comment);
this.setState({
awaiting: {
ship: this.props.ship,
blogId: this.props.blogId,
postId: this.props.postId,
}
});
this.props.api.action("write", "write-action", comment);
}
componentDidUpdate(prevProps, prevState) {
if (this.state.awaiting) {
if (prevProps.comments != this.props.comments) {
this.setState({awaiting: false, commentBody: ''});
}
}
}
render(){
if (this.state.show) {
let our = `~${window.ship}`;
let comments = this.props.comments.map((comment, i) => {
let commentProps = {
ship: comment.info.creator,
date: comment.info["date-created"],
body: comment.body,
};
return (<Comment {...commentProps} key={i} />);
});
return (
<div className="cb mt3 mb4">
<p className="gray-50 body-large b">
{this.props.comments.length}
<span className="black">
Comments
</span>
</p>
<p className="cl body-regular pointer" onClick={this.toggleDisplay}>
- Hide Comments
</p>
<CommentBox our={our}
action={this.commentChange}
enabled={!(Boolean(this.state.awaiting))}
post={this.postComment}/>
<div className="flex-col" style={{marginTop: 32}}>
{comments}
</div>
</div>
);
} else {
return (
<div className="cb mt3 mb4">
<p className="gray-50 body-large b">
{this.props.comments.length}
<span className="black">
Comments
</span>
</p>
<p className="cl body-regular pointer" onClick={this.toggleDisplay}>
+ Show Comments
</p>
</div>
);
}
}
}

View File

@ -0,0 +1,64 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { Sigil } from '/components/lib/icons/sigil';
export class CommentBox extends Component {
constructor(props){
super(props);
}
componentDidUpdate(prevProps, prevState) {
if (!prevProps.enabled && this.props.enabled) {
if (this.textarea) {
this.textarea.value = '';
}
}
}
render() {
if (this.props.enabled) {
return (
<div className="cb w-100 flex"
style={{paddingBottom: 8, marginTop: 32}}>
<div className="fl" style={{marginRight: 10}}>
<Sigil ship={this.props.our} size={36}/>
</div>
<div className="flex-col w-100">
<textarea className="body-regular-400 w-100"
ref={(el) => {this.textarea = el}}
style={{resize: "none"}}
type="text"
name="commentBody"
defaultValue=''
onChange={this.props.action}>
</textarea>
<p className="body-regular pointer" onClick={this.props.post}>
-> Post
</p>
</div>
</div>
);
} else {
return (
<div className="cb w-100 flex"
style={{paddingBottom: 8, marginTop: 32}}>
<div className="fl" style={{marginRight: 10}}>
<Sigil ship={this.props.our} size={36}/>
</div>
<div className="flex-col w-100">
<textarea className="body-regular-400 w-100"
ref={(el) => {this.textarea = el}}
style={{resize: "none"}}
type="text"
name="commentBody"
disabled={true}>
</textarea>
<p className="body-regular gray-50">
-> Post
</p>
</div>
</div>
);
}
}
}

View File

@ -0,0 +1,58 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { Sigil } from '/components/lib/icons/sigil';
import moment from 'moment';
export class Comment 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 body = this.props.body.split("\n").slice(0, -1).map((line, i) =>{
return (<p key={i}>{line}</p>);
});
let date = moment(this.props.date).fromNow();
return (
<div className="cb w-100 flex" style={{paddingBottom: 16}}>
<div className="fl" style={{marginRight: 10}}>
<Sigil ship={this.props.ship} size={36} />
</div>
<div className="flex-col fl">
<div className="label-small-mono gray-50">
<p className="fl" style={{width: 107}}>{this.props.ship}</p>
<p className="fl">{date}</p>
</div>
<div className="cb body-regular-400">
{body}
</div>
</div>
</div>
);
}
}

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { sealDict } from '/components/lib/seal-dict';
export class Sigil extends Component {
render() {
let prefix = this.props.prefix ? JSON.parse(this.props.prefix) : false;
@ -8,7 +9,7 @@ export class Sigil extends Component {
return (
<div
className="bg-black"
style={{ flexBasis: 48, padding: 4, paddingBottom: 0 }}>
style={{ flexBasis: 35, padding: 4}}>
{
sealDict.getSeal(this.props.ship, this.props.size, prefix)
}
@ -16,3 +17,4 @@ export class Sigil extends Component {
);
}
}

View File

@ -5,56 +5,72 @@ import _ from 'lodash';
const ReactSVGComponents = {
svg: p => {
return (
<svg {...p.attr} version={'1.1'} xmlns={'http://www.w3.org/2000/svg'}>
<svg key={Math.random()}
version={'1.1'}
xmlns={'http://www.w3.org/2000/svg'}
{...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</svg>
)
},
circle: p => {
return (
<circle {...p.attr}>
<circle
key={Math.random()} {...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</circle>
)
},
rect: p => {
return (
<rect {...p.attr}>
<rect
key={Math.random()}
{...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</rect>
)
},
path: p => {
return (
<path {...p.attr}>
<path
key={Math.random()}
{...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</path>
)
},
g: p => {
return (
<g {...p.attr}>
<g
key={Math.random()}
{...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</g>
)
},
polygon: p => {
return (
<polygon {...p.attr}>
<polygon
key={Math.random()}
{...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</polygon>
)
},
line: p => {
return (
<line {...p.attr}>
<line
key={Math.random()}
{...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</line>
)
},
polyline: p => {
return (
<polyline {...p.attr}>
<polyline
key={Math.random()}
{...p.attr}>
{ _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) }
</polyline>
)

View File

@ -74,7 +74,7 @@ export class NewBlog extends Component {
}
}
let data = {
let makeBlog = {
"new-collection" : {
name: blogId,
title: blogTitle,
@ -84,11 +84,20 @@ export class NewBlog extends Component {
},
};
let sendInvites = {
invite: {
coll: blogId,
title: blogTitle,
who: this.state.collaborators,
}
}
this.setState({
awaiting: blogId
});
this.props.api.action("write", "write-action", data);
this.props.api.action("write", "write-action", makeBlog);
this.props.api.action("write", "write-action", sendInvites);
}
componentDidUpdate(prevProps, prevState) {

View File

@ -12,7 +12,7 @@ class SideTab extends Component {
super(props)
}
render(props) {
render() {
if (this.props.enabled){
return (
<div className="w1 z-2"

View File

@ -4,6 +4,7 @@ import { PostPreview } from '/components/post-preview';
import moment from 'moment';
import { Link } from 'react-router-dom';
import { PostBody } from '/components/post-body';
import { Comments } from '/components/comments';
import { PathControl } from '/components/lib/path-control';
import _ from 'lodash';
@ -92,7 +93,6 @@ export class Post extends Component {
},
};
this.setState({
awaitingEdit: {
ship: this.state.ship,
@ -134,14 +134,23 @@ export class Post extends Component {
],
});
let read = {
read: {
who: ship,
coll: blogId,
post: postId,
}
};
this.props.api.action("write", "write-action", read);
} else {
this.setState({
awaitingLoad: {
ship: ship,
blogId: blogId,
postId: postId,
temporary: true,
},
temporary: true,
});
this.props.api.bind(`/collection/${blogId}`, "PUT", ship, "write",
this.handleEvent.bind(this),
@ -171,11 +180,7 @@ export class Post extends Component {
}
}
//
handleEvent(diff) {
console.log("handle event", diff);
if (diff.data.total) {
let blog = diff.data.total.data;
let post = blog.posts[this.state.postId].post;
@ -198,62 +203,80 @@ export class Post extends Component {
{ text: post.info.title, url: postUrl },
],
});
} else if (diff.data.collection) {
let newBlog = this.state.blog;
newBlog.info = diff.data.collection.data;
this.setState({
blog: newBlog,
});
} else if (diff.data.post) {
this.setState({
post: diff.data.post.data,
});
} else if (diff.data.comments) {
this.setState({
comments: diff.data.comments.data,
});
} else if (diff.data.remove) {
// XX TODO Handle this properly
}
}
handleError() {
console.log("handle error");
}
componentDidUpdate(prevProps, prevState) {
if (this.state.awaitingEdit) {
let ship = this.state.awaitingEdit.ship;
let blogId = this.state.awaitingEdit.blogId;
let postId = this.state.awaitingEdit.postId;
let ship = this.props.ship;
let blogId = this.props.blogId;
let postId = this.props.postId;
if (this.state.awaitingEdit.ship == window.ship) {
let oldPost = prevState.post;
let oldPost = prevState.post;
let oldComments = prevState.comments;
let oldBlog = prevState.blog;
let post = _.get(this.props,
`pubs[${blogId}].posts[${postId}].post`, false);
let post;
let comments;
let blog;
if ((post.info.title != oldPost.info.title) ||
(post.raw != oldPost.raw)) {
if (ship === window.ship) {
blog = _.get(this.props, `pubs[${blogId}]`, false);
post = _.get(blog, `posts[${postId}].post`, false);
comments = _.get(blog, `posts[${postId}].comments`, false);
} else {
blog = _.get(this.props, `subs[${ship}][${blogId}]`, false);
post = _.get(blog, `posts[${postId}].post`, false);
comments = _.get(blog, `posts[${postId}].comments`, false);
}
this.setState({
mode: 'view',
titleOriginal: post.info.title,
bodyOriginal: post.raw,
title: post.info.title,
body: post.raw,
awaitingEdit: false,
post: post,
});
}
} else {
let oldPost = prevState.post;
if (this.state.awaitingEdit &&
((post.info.title != oldPost.info.title) ||
(post.raw != oldPost.raw))) {
let post = _.get(this.props,
`subs[${ship}][${blogId}].posts[${postId}].post`, false);
if ((post.info.title != oldPost.info.title) ||
(post.raw != oldPost.raw)) {
this.setState({
mode: 'view',
titleOriginal: post.info.title,
bodyOriginal: post.raw,
title: post.info.title,
body: post.raw,
awaitingEdit: false,
post: post,
});
}
this.setState({
mode: 'view',
titleOriginal: post.info.title,
bodyOriginal: post.raw,
title: post.info.title,
body: post.raw,
awaitingEdit: false,
post: post,
});
}
if (!this.state.temporary){
if (oldPost != post) {
this.setState({post: post});
}
if (oldComments != comments) {
this.setState({comments: comments});
}
if (oldBlog != blog) {
this.setState({blog: blog});
}
}
}
titleChange(evt){
this.setState({title: evt.target.value});
}
@ -263,9 +286,6 @@ export class Post extends Component {
}
render() {
console.log("post", this.props);
if (this.state.awaitingLoad) {
return (
<div>
@ -315,18 +335,12 @@ export class Post extends Component {
<hr className="gray-50 w-680"/>
<hr className="gray-50 w-680"/>
<div className="cb mt3 mb4">
<p className="gray-50 body-large b">
{this.state.comments.length}
<span className="black">
Comments
</span>
</p>
<p className="cl body-regular">
+ Show Comments
</p>
</div>
<Comments comments={this.state.comments}
api={this.props.api}
ship={this.props.ship}
blogId={this.props.blogId}
postId={this.props.postId}
/>
</div>
</div>
);
@ -375,18 +389,12 @@ export class Post extends Component {
<hr className="gray-50 w-680"/>
<hr className="gray-50 w-680"/>
<div className="cb mt3 mb4">
<p className="gray-50 body-large b">
{this.state.comments.length}
<span className="black">
Comments
</span>
</p>
<p className="cl body-regular">
+ Show Comments
</p>
</div>
<Comments comments={this.state.comments}
api={this.props.api}
ship={this.props.ship}
blogId={this.props.blogId}
postId={this.props.postId}
/>
</div>
</div>
);

View File

@ -37,7 +37,7 @@ export class Pubs extends Component {
<div className={cls} key={i}>
<div className="fl body-regular-400" style={{flexBasis: 336}}>
<Link to={data.url}>
{data.title}
{data.title}
</Link>
</div>
<p className="fl body-regular-400" style={{flexBasis:336}}>
@ -62,7 +62,7 @@ export class Pubs extends Component {
</div>
<div className="w-100 flex">
<p className="fl gray-50 body-regular-400" style={{flexBasis:336}}>
Title
Title
</p>
<p className="fl gray-50 body-regular-400" style={{flexBasis:336}}>
Host

View File

@ -45,7 +45,7 @@ export class Root extends Component {
return (
<Skeleton
children={
<Subs {...this.state} />
<Subs {...this.state} api={api}/>
}
/>
);

View File

@ -9,15 +9,16 @@ export class Skeleton extends Component {
constructor(props){
super(props);
}
//Header {...this.props}/>
render() {
return (
<div className="h-100 w-100 absolute">
<HeaderBar/>
<div className="h-inner overflow-y-scroll">
<div className="h-inner">
{this.props.children}
</div>
<div className="h-footer">
</div>
</div>
);
}

View File

@ -9,25 +9,73 @@ const HM = withRouter(HeaderMenu);
export class Subs extends Component {
constructor(props) {
super(props)
this.accept = this.accept.bind(this);
this.reject = this.reject.bind(this);
this.unsubscribe = this.unsubscribe.bind(this);
}
buildBlogData() {
let invites = this.props.invites.map((inv) => {
return {
type: 'invite',
url: `/~publish/~${inv.who}/${inv.coll}`,
host: `~${inv.who}`,
title: inv.title,
blogId: inv.coll,
};
})
let data = Object.keys(this.props.subs).map((ship) => {
let perShip = Object.keys(this.props.subs[ship]).map((blogId) => {
let blog = this.props.subs[ship][blogId];
return {
type: 'regular',
url: `/~publish/${blog.info.owner}/${blogId}`,
title: blog.info.title,
host: blog.info.owner,
lastUpdated: "idk"
lastUpdated: "idk",
blogId: blogId,
}
});
return perShip;
});
let merged = data.flat();
return merged;
return invites.concat(merged);
}
accept(host, blogId) {
console.log("accepted invitation", host, blogId);
let subscribe = {
subscribe: {
who: host.slice(1),
coll: blogId,
}
};
this.props.api.action("write", "write-action", subscribe);
}
reject(host, blogId) {
console.log("rejected invitation", host, blogId);
let reject = {
"reject-invite": {
who: host.slice(1),
coll: blogId,
}
};
this.props.api.action("write", "write-action", reject);
}
unsubscribe(host, blogId) {
console.log("unsubscribe", host, blogId);
let unsubscribe = {
unsubscribe: {
who: host.slice(1),
coll: blogId,
}
};
this.props.api.action("write", "write-action", unsubscribe);
}
render() {
let blogData = this.buildBlogData();
@ -37,21 +85,56 @@ export class Subs extends Component {
? "bg-v-light-gray"
: "bg-white";
let cls = "w-100 flex " + bg;
return (
<div className={cls} key={i}>
<div className="fl body-regular-400" style={{flexBasis: 336}}>
<Link to={data.url}>
{data.title}
</Link>
if (data.type === 'regular') {
return (
<div className={cls} key={i}>
<div className="fl body-regular-400" style={{flexBasis: 336}}>
<Link to={data.url}>
{data.title}
</Link>
</div>
<p className="fl body-regular-400" style={{flexBasis:336}}>
{data.host}
</p>
<p className="fl body-regular-400" style={{flexBasis:336}}>
{data.lastUpdated}
</p>
<p className="fl body-regular-400 pointer"
style={{flexBasis:336}}
onClick={this.unsubscribe.bind(this, data.host, data.blogId)}>
Unsubscribe
</p>
</div>
<p className="fl body-regular-400" style={{flexBasis:336}}>
{data.host}
</p>
<p className="fl body-regular-400" style={{flexBasis:336}}>
{data.lastUpdated}
</p>
</div>
);
);
} else if (data.type === 'invite') {
return (
<div className={cls} key={i}>
<div className="fl body-regular-400" style={{flexBasis: 336}}>
<Link to={data.url}>
<span className="body-large green-medium"></span>
<span className="body-regular-400">Invite to</span>
<span className="body-regular">{data.title}</span>
</Link>
</div>
<p className="fl body-regular-400" style={{flexBasis:336}}>
{data.host}
</p>
<p className="fl body-regular-400" style={{flexBasis:336}}>
</p>
<p className="fl body-regular-400" style={{flexBasis:336}}>
<span className="green underline pointer"
onClick={this.accept.bind(this, data.host, data.blogId)}>
Accept
</span>
<span></span>
<span className="red underline pointer"
onClick={this.reject.bind(this, data.host, data.blogId)}>
Reject
</span>
</p>
</div>
);
}
});
@ -66,7 +149,7 @@ export class Subs extends Component {
</div>
<div className="w-100 flex">
<p className="fl gray-50 body-regular-400" style={{flexBasis:336}}>
Title
Title
</p>
<p className="fl gray-50 body-regular-400" style={{flexBasis:336}}>
Host
@ -74,6 +157,8 @@ export class Subs extends Component {
<p className="fl gray-50 body-regular-400" style={{flexBasis:336}}>
Last Updated
</p>
<p className="fl gray-50 body-regular-400" style={{flexBasis:336}}>
</p>
</div>
{blogs}

View File

@ -0,0 +1,312 @@
export class RumorReducer {
reduce(json, state){
if (json.collection) {
this.reduceCollection(json.collection, state);
}
if (json.post) {
this.reducePost(json, state);
}
if (json.comments) {
this.reduceComments(json, state);
}
if (json.total) {
this.reduceTotal(json, state);
}
if (json.remove) {
this.reduceRemove(json.remove, state);
}
}
reduceRemove(json, state) {
if (json.who === window.ship) {
if (json.post) {
this.removePost(json, state);
delete state.pubs[json.coll].posts[json.post];
} else {
let postIds = Object.keys(state.pubs[json.coll].posts);
postIds.forEach((postId) => {
this.removePost({
who: json.who,
coll: json.coll,
post: postId,
}, state);
});
delete state.pubs[json.coll];
}
} else {
if (json.post) {
this.removePost(json, state);
delete state.subs[json.who][json.coll].posts[json.post];
} else {
let postIds = Object.keys(state.subs[json.who][json.coll].posts);
postIds.forEach((postId) => {
this.removePost({
who: json.who,
coll: json.coll,
post: postId,
}, state);
});
delete state.subs[json.who][json.coll];
}
}
}
removePost(json, state) {
this.removeLatest(json, state);
this.removeOrder(json, state);
this.removeUnread(json, state);
}
removeLatest(json, state) {
let idx = _.findIndex(state.latest, json);
_.pullAt(state.latest, [idx]);
}
removeUnread(json, state) {
let idx = _.findIndex(state.latest, json);
_.pullAt(state.latest, [idx]);
}
removeOrder(json, state) {
if (json.who === window.ship) {
if (state.pubs[json.coll]) {
let pinIdx = _.findIndex(state.pubs[json.coll].order.pin, json.post);
let unpinIdx = _.findIndex(state.pubs[json.coll].order.unpin, json.post);
if (pinIdx != -1) {
_.pullAt(state.pubs[json.coll].order.pin, [pinIdx]);
}
if (unpinIdx != -1) {
_.pullAt(state.pubs[json.coll].order.unpin, [unpinIdx]);
}
}
} else {
if (state.subs[json.who][json.coll]) {
let pinIdx =
_.findIndex(state.subs[json.who][json.coll].order.pin, json.post);
let unpinIdx =
_.findIndex(state.subs[json.who][json.coll].order.unpin, json.post);
if (pinIdx != -1) {
_.pullAt(state.subs[json.who][json.coll].order.pin, [pinIdx]);
}
if (unpinIdx != -1) {
_.pullAt(state.subs[json.who][json.coll].order.unpin, [unpinIdx]);
}
}
}
}
reduceCollection(json, state) {
if (json.who === window.ship) {
state.pubs[json.coll] = {
info: json.data,
order: { pin: [], unpin: [] },
posts: {},
}
} else {
state.subs[json.who][json.coll] = {
info: json.data,
order: { pin: [], unpin: [] },
posts: {},
}
}
}
reducePost(json, state) {
let who = json.post.who;
let coll = json.post.coll;
let post = json.post.post;
let data = json.post.data;
if (who === window.ship) {
if (state.pubs[coll].posts[post]) {
state.pubs[coll].posts[post].post = data;
} else {
state.pubs[coll].posts[post] = {
post: data,
comments: [],
};
}
} else {
if (state.subs[who][coll].posts[post]) {
state.subs[who][coll].posts[post].post = data;
} else {
state.subs[who][coll].posts[post] = {
post: data,
comments: [],
};
}
}
this.insertPost(json, state);
}
insertPost(json, state) {
this.insertLatest(json, state);
this.insertUnread(json, state);
this.insertOrder(json, state);
}
insertLatest(json, state) {
let newIndex = {
post: json.post.post,
coll: json.post.coll,
who: json.post.who,
}
let newDate = json.post.data.info["date-created"];
if (state.latest.length == 0) {
state.latest.push(newIndex);
return;
}
if (state.latest.indexOf(newIndex) != -1) {
return;
}
for (var i=0; i<state.latest.length; i++) {
let postId = state.latest[i].post;
let blogId = state.latest[i].coll;
let ship = state.latest[i].who;
if (newIndex.post == postId && newIndex.coll == blogId && newIndex.who == ship) {
break;
}
let idate = this.retrievePost(state, blogId, postId, ship).info["date-created"];
if (newDate >= idate) {
state.latest.splice(i, 0, newIndex);
break;
} else if (i == (state.latest.length - 1)) {
state.latest.push(newIndex);
break;
}
}
}
insertUnread(json, state) {
if (json.post.who != window.ship) {
state.unread.push({
post: json.post.post,
coll: json.post.coll,
who: json.post.who,
});
}
}
insertOrder(json, state) {
let blogId = json.post.coll;
let ship = json.post.who;
let blog = this.retrieveColl(state, blogId, ship);
let list = json.post.data.info.pinned
? blog.order.pin
: blog.order.unpin;
let newDate = json.post.data.info["date-created"];
if (list.length == 0) {
list.push(json.post.post);
}
if (list.indexOf(json.post.post) != -1) {
return;
}
for (var i=0; i<list.length; i++) {
let postId = list[i];
if (json.post.post === postId) {
break;
}
let idate = this.retrievePost(state, blogId, postId, ship).info["date-created"];
if (newDate >= idate) {
list.splice(i, 0, json.post.post);
break;
} else if (i == (state.latest.length - 1)) {
list.push(json.post.post);
break;
}
}
if (window.ship == ship) {
state.pubs[blogId].order = json.post.data.info.pinned
? {pin: list, unpin: blog.order.unpin}
: {pin: blog.order.pin, unpin: list};
} else {
state.subs[ship][blogId].order = json.post.data.info.pinned
? {pin: list, unpin: blog.order.unpin}
: {pin: blog.order.pin, unpin: list};
}
}
retrieveColl(state, coll, who) {
if (who === window.ship) {
return state.pubs[coll];
} else {
return state.subs[who][coll];
}
}
retrievePost(state, coll, post, who) {
if (who === window.ship) {
return state.pubs[coll].posts[post].post;
} else {
return state.subs[who][coll].posts[post].post;
}
}
reduceComments(json, state) {
let who = json.comments.who;
let coll = json.comments.coll;
let post = json.comments.post;
let data = json.comments.data;
if (who === window.ship) {
if (state.pubs[coll].posts[post]) {
state.pubs[coll].posts[post].comments = data;
} else {
state.pubs[coll].posts[post] = {
post: null,
comments: data,
};
}
} else {
if (state.subs[who][coll].posts[post]) {
state.subs[who][coll].posts[post].comments = data;
} else {
state.subs[who][coll].posts[post] = {
post: null,
comments: data,
};
}
}
}
reduceTotal(json, state) {
if (json.total.who == window.ship) {
state.pubs[json.total.coll] = json.total.data
} else {
if (state.subs[json.total.who]) {
state.subs[json.total.who][json.total.coll] = json.total.data;
} else {
state.subs[json.total.who] = {
[json.total.coll] : json.total.data
}
}
}
let posts = Object.keys(json.total.data.posts);
for (var i=0; i<posts.length; i++) {
let post = {
post: {
coll: json.total.coll,
post: posts[i],
who: json.total.who,
data: json.total.data.posts[posts[i]].post,
}
};
this.insertPost(post, state);
}
}
}

View File

@ -1,226 +1,24 @@
import _ from 'lodash';
export class UpdateReducer {
reduce(json, state){
console.log("update-reducer", json, state);
if (json.collection) {
this.reduceCollection(json, state);
}
if (json.post) {
this.reducePost(json, state);
}
if (json.comments) {
this.reduceComments(json, state);
}
if (json.total) {
this.reduceTotal(json, state);
if (json.invite) {
this.reduceInvite(json, state);
}
}
reduceCollection(json, state) {
if (json.collection.who === window.ship) {
state.pubs[json.collection.coll] = {
info: json.collection.data,
order: { pin: [], unpin: [] },
posts: {},
}
reduceInvite(json, state) {
let val = {
title: json.invite.title,
coll: json.invite.coll,
who: json.invite.who,
};
if (json.invite.add) {
state.invites.push(val);
} else {
state.subs[json.collection.who][json.collection.coll] = {
info: json.collection.data,
order: { pin: [], unpin: [] },
posts: {},
}
}
}
reducePost(json, state) {
let who = json.post.who;
let coll = json.post.coll;
let post = json.post.post;
let data = json.post.data;
if (who === window.ship) {
if (state.pubs[coll].posts[post]) {
state.pubs[coll].posts[post].post = data;
} else {
state.pubs[coll].posts[post] = {
post: data,
comments: [],
};
}
} else {
if (state.subs[who][coll].posts[post]) {
state.subs[who][coll].posts[post].post = data;
} else {
state.subs[who][coll].posts[post] = {
post: data,
comments: [],
};
}
}
this.insertPost(json, state);
}
insertPost(json, state) {
this.insertLatest(json, state);
this.insertUnread(json, state);
this.insertOrder(json, state);
}
insertLatest(json, state) {
let newIndex = {
post: json.post.post,
coll: json.post.coll,
who: json.post.who,
}
let newDate = json.post.data.info["date-created"];
if (state.latest.length == 0) {
state.latest.push(newIndex);
return;
}
if (state.latest.indexOf(newIndex) != -1) {
return;
}
for (var i=0; i<state.latest.length; i++) {
let postId = state.latest[i].post;
let blogId = state.latest[i].coll;
let ship = state.latest[i].who;
if (newIndex.post == postId && newIndex.coll == blogId && newIndex.who == ship) {
break;
}
let idate = this.retrievePost(state, blogId, postId, ship).info["date-created"];
if (newDate >= idate) {
state.latest.splice(i, 0, newIndex);
break;
} else if (i == (state.latest.length - 1)) {
state.latest.push(newIndex);
break;
}
}
}
insertUnread(json, state) {
if (json.post.who != window.ship) {
state.unread.push({
post: json.post.post,
coll: json.post.coll,
who: json.post.who,
});
}
}
insertOrder(json, state) {
let blogId = json.post.coll;
let ship = json.post.who;
let blog = this.retrieveColl(state, blogId, ship);
let list = json.post.data.info.pinned
? blog.order.pin
: blog.order.unpin;
let newDate = json.post.data.info["date-created"];
if (list.length == 0) {
list.push(json.post.post);
}
if (list.indexOf(json.post.post) != -1) {
return;
}
for (var i=0; i<list.length; i++) {
let postId = list[i];
if (json.post.post === postId) {
break;
}
let idate = this.retrievePost(state, blogId, postId, ship).info["date-created"];
if (newDate >= idate) {
list.splice(i, 0, json.post.post);
break;
} else if (i == (state.latest.length - 1)) {
list.push(json.post.post);
break;
}
}
if (window.ship == ship) {
state.pubs[blogId].order = json.post.data.info.pinned
? {pin: list, unpin: blog.order.unpin}
: {pin: blog.order.pin, unpin: list};
} else {
state.subs[ship][blogId].order = json.post.data.info.pinned
? {pin: list, unpin: blog.order.unpin}
: {pin: blog.order.pin, unpin: list};
}
}
retrieveColl(state, coll, who) {
if (who === window.ship) {
return state.pubs[coll];
} else {
return state.subs[who][coll];
}
}
retrievePost(state, coll, post, who) {
if (who === window.ship) {
return state.pubs[coll].posts[post].post;
} else {
return state.subs[who][coll].posts[post].post;
}
}
reduceComments(json, state) {
let who = json.comments.who;
let coll = json.comments.coll;
let post = json.comments.post;
let data = json.comments.data;
if (who === window.ship) {
if (state.pubs[coll].posts[post]) {
state.pubs[coll].posts[post].comments = data;
} else {
state.pubs[coll].posts[post] = {
post: null,
comments: data,
};
}
} else {
if (state.subs[who][coll].posts[post]) {
state.subs[who][coll].posts[post].comments = data;
} else {
state.subs[who][coll].posts[post] = {
post: null,
comments: data,
};
}
}
}
reduceTotal(json, state) {
if (json.total.who == window.ship) {
state.pubs[json.total.coll] = json.total.data
} else {
state.subs[json.total.who] = {
[json.total.coll] : json.total.data
}
}
let posts = Object.keys(json.total.data.posts);
for (var i=0; i<posts.length; i++) {
let post = {
post: {
coll: json.total.coll,
post: posts[i],
who: json.total.who,
data: json.total.data.posts[posts[i]].post,
}
};
this.insertPost(post, state);
let idx = _.findIndex(state.invites, val)
_.pullAt(state.invites, [idx]);
}
}
}

View File

@ -1,4 +1,5 @@
import { UpdateReducer } from '/reducers/update';
import { RumorReducer } from '/reducers/rumor';
class Store {
constructor() {
@ -8,6 +9,7 @@ class Store {
console.log("store.state", this.state);
this.updateReducer = new UpdateReducer();
this.rumorReducer = new RumorReducer();
this.setState = () => {};
}
@ -19,6 +21,7 @@ class Store {
handleEvent(data) {
console.log("store.handleEvent", data);
this.updateReducer.reduce(data.data, this.state);
this.rumorReducer.reduce(data.data, this.state);
this.setState(this.state);
}

60
apps/publish/tile/tile.js Normal file
View File

@ -0,0 +1,60 @@
import React, { Component } from 'react'
import classnames from 'classnames';
export default class PublishTile extends Component {
constructor(props){
super(props);
}
render(){
console.log("tile", this.props);
let info = [];
if (this.props.data.invites > 0) {
let text = (this.props.data.invites == 1)
? "Invite"
: "Invites"
info.push(
<p key={1}>
<span className="green-medium">{this.props.data.invites}</span>
{text}
</p>
);
}
if (this.props.data.new > 0) {
let text = (this.props.data.new == 1)
? "New Post"
: "New Posts"
info.push(
<p key={2}>
<span className="green-medium">{this.props.data.new}</span>
{text}
</p>
);
}
return (
<div className="w-100 h-100 relative" style={{background: "#1a1a1a"}}>
<a className="w-100 h-100 db no-underline" href="/~publish">
<p className="gray-30 label-regular b absolute"
style={{left: 8, top: 4}}>
Publish
</p>
<img
className="absolute"
style={{left: 60, top: 66}}
src="/~publish/tile.png"
width={113}
height={102} />
<div className="absolute w-100 flex-col body-regular white"
style={{verticalAlign: "bottom", bottom: 8, left: 8}}>
{info}
</div>
</a>
</div>
);
}
}
window.writeTile = PublishTile;

View File

@ -22,6 +22,17 @@
/~ ~
==
::
/= tile-js
/^ octs
/; as-octs:mimes:html
/| /: /===/app/write/js/tile /js/
/~ ~
==
::
/= images
/^ (map knot @)
/: /===/app/write/img /_ /png/
::
|%
::
+$ move [bone card]
@ -44,6 +55,7 @@
+$ poke
$% [%hall-action action:hall]
[%write-action action]
[%noun @tas path @t]
==
::
+$ diff
@ -51,6 +63,7 @@
[%json json]
[%write-collection collection]
[%write-rumor rumor]
[%write-update update]
==
::
--
@ -131,8 +144,13 @@
^- (quip move _this)
~& write-prep+act.bol
?~ old
:_ this
[ost.bol %connect / [~ /'~publish'] %write]~
:_ this(sat *state)
:~ [ost.bol %connect / [~ /'~publish'] %write]
:* ost.bol %poke /publish [our.bol %launch]
%noun %write /publishtile '/~publish/tile.js'
==
==
::
:: [~ this(sat *state)]
[~ this(sat (state u.old))]
::
@ -143,6 +161,9 @@
[~ this]
?+ a
[~ this]
::
%update-tile
[make-tile-moves this]
::
%test-build
=/ schema=schematic:ford
@ -309,7 +330,7 @@
::
=? unread.sat !=(who our.bol)
(~(put in unread.sat) who coll post)
da-this
(da-emil make-tile-moves)
::
++ da-insert-latest
|= [who=@p coll=@tas post=@tas]
@ -638,6 +659,7 @@
++ poke-write-action
|= act=action
^- (quip move _this)
~& poke+act
?- -.act
::
%new-collection
@ -789,15 +811,29 @@
|= who=@p
^- move
[ost.bol %poke /forward [who %write] %write-action new-act]
:- ~
this(invites.sat (~(put by invites.sat) [src.bol coll.act] title.act))
=. invites.sat (~(put by invites.sat) [src.bol coll.act] title.act)
:_ this
%+ welp make-tile-moves
::
%+ turn (prey:pubsub:userlib /primary bol)
|= [b=bone *]
[b %diff %write-update %invite %.y src.bol coll.act title.act]
::
:: %reject-invite: remove invite from list, acceptance is handled by
:: %subscribe action
::
%reject-invite
:- ~
this(invites.sat (~(del by invites.sat) [who.act coll.act]))
=/ title=(unit @t) (~(get by invites.sat) [who.act coll.act])
?~ title
[~ this]
=. invites.sat (~(del by invites.sat) [who.act coll.act])
:_ this
%+ welp make-tile-moves
::
%+ turn (prey:pubsub:userlib /primary bol)
|= [b=bone *]
^- move
[b %diff %write-update %invite %.n who.act coll.act u.title]
::
:: %serve:
::
@ -885,7 +921,8 @@
=(coll coll.act)
==
::
=/ new-unread=(set [@p @tas @tas])
=. unread.sat
^- (set [@p @tas @tas])
%- sy
%+ skip ~(tap in unread.sat)
|= [who=@p coll=@tas post=@tas]
@ -893,12 +930,13 @@
=(coll coll.act)
==
::
:- [[ost.bol %kill /collection/[coll.act] ~] kills]
:- %+ welp
make-tile-moves
[[ost.bol %kill /collection/[coll.act] ~] kills]
%= this
pubs.sat (~(del by pubs.sat) coll.act)
awaiting.sat (~(del by awaiting.sat) coll.act)
latest.sat new-latest
unread.sat new-unread
==
::
:: %subscribe: sub to a foreign blog; remove invites for that blog
@ -906,8 +944,18 @@
%subscribe
~& write-action+act
=/ wir=wire /collection/[coll.act]
:_ this(invites.sat (~(del by invites.sat) [who.act coll.act]))
[ost.bol %peer wir [who.act %write] wir]~
=/ title=(unit @t) (~(get by invites.sat) [who.act coll.act])
=. invites.sat (~(del by invites.sat) [who.act coll.act])
:_ this
;: welp
make-tile-moves
[ost.bol %peer wir [who.act %write] wir]~
?~ title ~
%+ turn (prey:pubsub:userlib /primary bol)
|= [b=bone *]
^- move
[b %diff %write-update %invite %.n who.act coll.act u.title]
==
::
:: %unsubscribe: unsub from a foreign blog, delete all state related to it
::
@ -919,7 +967,8 @@
=(coll coll.act)
==
::
=/ new-unread=(set [@p @tas @tas])
=. unread.sat
^- (set [@p @tas @tas])
%- sy
%+ skim ~(tap in unread.sat)
|= [who=@p coll=@tas post=@tas]
@ -927,12 +976,22 @@
=(coll coll.act)
==
=/ wir=wire /collection/[coll.act]
:- [ost.bol %pull wir [who.act %write] ~]~
%= this
:_ %= this
subs.sat (~(del by subs.sat) who.act coll.act)
latest.sat new-latest
unread.sat new-unread
==
:- [ost.bol %pull wir [who.act %write] ~]
%+ welp make-tile-moves
%+ turn (prey:pubsub:userlib /primary bol)
|= [b=bone *]
^- move
[b %diff %write-rumor %remove who.act coll.act ~]
::
:: %read: notify that we've seen a post
::
%read
=. unread.sat (~(del in unread.sat) who.act coll.act post.act)
[make-tile-moves this]
::
==
::
@ -952,6 +1011,16 @@
?+ request-line
:_ this
[ost.bol %http-response not-found:app]~
:: images
::
[[[~ %png] [%'~publish' @t ~]] ~]
=/ filename=@t i.t.site.request-line
=/ img=(unit @t) (~(get by images) filename)
?~ img
:_ this
[ost.bol %http-response not-found:app]~
:_ this
[ost.bol %http-response (png-response:app (as-octs:mimes:html u.img))]~
:: styling
::
[[[~ %css] [%'~publish' %index ~]] ~]
@ -962,6 +1031,11 @@
[[[~ %js] [%'~publish' %index ~]] ~]
:_ this
[ost.bol %http-response (js-response:app js)]~
:: tile js
::
[[[~ %js] [%'~publish' %tile ~]] ~]
:_ this
[ost.bol %http-response (js-response:app tile-js)]~
:: home page; redirect to recent
::
[[~ [%'~publish' ~]] ~]
@ -1039,6 +1113,26 @@
::
==
::
++ make-tile-moves
^- (list move)
%+ turn (prey:pubsub:userlib /publishtile bol)
|= [b=bone *]
^- move
[b %diff %json make-tile-json]
::
++ make-tile-json
^- json
%- pairs:enjs:format
:~ invites+(numb:enjs:format ~(wyt by invites.sat))
new+(numb:enjs:format ~(wyt in unread.sat))
==
::
++ peer-publishtile
|= wir=wire
^- (quip move _this)
:_ this
[ost.bol %diff %json make-tile-json]~
::
++ peer-primary
|= wir=wire
~& peer-primary+wir

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -42,6 +42,7 @@
subscribe+subscribe
unsubscribe+unsubscribe
::
read+read
==
::
++ new-collection
@ -76,15 +77,15 @@
::
++ delete-post
%- ot:dejs
coll+(su:dejs sym)
post+(su:dejs sym)
:~ coll+(su:dejs sym)
post+(su:dejs sym)
==
::
++ delete-comment
%- ot:dejs
coll+(su:dejs sym)
post+(su:dejs sym)
comment+(su:dejs sym)
:~ coll+(su:dejs sym)
post+(su:dejs sym)
comment+(su:dejs sym)
==
::
++ edit-collection
@ -144,34 +145,17 @@
%- ~(run in (sy x))
|=(w=@ [& w])
::
++ item-id
|= jon=^json
^- item-id:write
?> ?=(%a -.jon) :: must be array
?< ?=(~ +.jon) :: must have at least one item
?> ?=([%s @t] -.+.jon) :: first item must be string
=/ coll=@tas (slav %tas +.-.+.jon) :: get first item as @tas
?~ +.+.jon :: if only one item, return it
coll
?> ?=([%s @t] -.+.+.jon) :: second item must be string
=/ post=@tas (slav %tas +.-.+.+.jon) :: get second item as @tas
?~ +.+.+.jon :: if two items, return them
[coll post]
?> ?=([%s @t] -.+.+.+.jon) :: third item must be string
=/ comm=@tas (slav %tas +.-.+.+.+.jon) :: get third item as @tas
?> ?=(~ +.+.+.+.jon) :: no fourth item
[coll post comm]
::
++ invite
%- ot:dejs
:~ coll+(su:dejs sym)
title+so:dejs
who+(ar:dejs (su:dejs fed:ag))
==
::
++ reject-invite
%- ot:dejs
:~ coll+(su:dejs sym)
who+(su:dejs fed:ag)
:~ who+(su:dejs fed:ag)
coll+(su:dejs sym)
==
::
++ serve
@ -196,6 +180,13 @@
coll+(su:dejs sym)
==
::
++ read
%- ot:dejs
:~ who+(su:dejs fed:ag)
coll+(su:dejs sym)
post+(su:dejs sym)
==
::
--
--
--

View File

@ -43,11 +43,12 @@
==
::
%remove
=/ suf=(list [@tas json])
%- pairs
:~ [%coll s+col.rum]
:~ [%who (ship who.rum)]
[%coll s+col.rum]
[%post ?~(pos.rum ~ s+u.pos.rum)]
==
::
==
::
--

View File

@ -0,0 +1,26 @@
/- *write
|_ upd=update
++ grab
|%
++ noun update
--
++ grow
|%
++ noun upd
++ json
=, enjs:format
%+ frond -.upd
::
?- -.upd
%invite
%- pairs
:~ [%who (ship who.upd)]
[%add b+add.upd]
[%coll s+col.upd]
[%title s+title.upd]
==
::
==
::
--
--

View File

@ -11,12 +11,14 @@
~
==
^- (list [comment-info:write @t])
:: XX sort this list
%+ turn ~(tap by comments)
|= [fil=knot front=(map knot cord) content=wain ~]
^- [comment-info:write @t]
:- (front-to-comment-info:write front)
(of-wain:format (slag 8 content))
%+ sort
%+ turn ~(tap by comments)
|= [fil=knot front=(map knot cord) content=wain ~]
^- [comment-info:write @t]
:- (front-to-comment-info:write front)
(of-wain:format (slag 8 content))
|= [a=[com=comment-info:write @t] b=[com=comment-info:write @t]]
(lte date-created.com.a date-created.com.b)
::
/_
/. /&front&/udon/

View File

@ -44,7 +44,7 @@
==
::
[%invite coll=@tas title=@t who=(list ship)]
[%reject-invite coll=@tas who=ship]
[%reject-invite who=@p coll=@tas]
::
[%serve coll=@tas]
[%unserve coll=@tas]
@ -52,6 +52,7 @@
[%subscribe who=@p coll=@tas]
[%unsubscribe who=@p coll=@tas]
::
[%read who=@p coll=@tas post=@tas]
==
::
+$ collection-info
@ -118,4 +119,8 @@
[%total who=@p col=@tas dat=collection]
[%remove who=@p col=@tas pos=(unit @tas)]
==
::
+$ update
$% [%invite add=? who=@p col=@tas title=@t]
==
--