diff --git a/apps/launch/src/css/custom.css b/apps/launch/src/css/custom.css index 31e0d87f4..32b5d6150 100644 --- a/apps/launch/src/css/custom.css +++ b/apps/launch/src/css/custom.css @@ -60,3 +60,11 @@ h2 { .bg-v-light-gray { background-color: #f9f9f9; } + +.gray-30 { + color: #B1B2B3; +} + +.green-medium { + color: #2ED196; +} diff --git a/apps/publish/gulpfile.js b/apps/publish/gulpfile.js index 2b5fa8763..bda196a08 100644 --- a/apps/publish/gulpfile.js +++ b/apps/publish/gulpfile.js @@ -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')); diff --git a/apps/publish/src/css/custom.css b/apps/publish/src/css/custom.css index eea3ba1fe..461c05665 100644 --- a/apps/publish/src/css/custom.css +++ b/apps/publish/src/css/custom.css @@ -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 { diff --git a/apps/publish/src/js/components/comments.js b/apps/publish/src/js/components/comments.js new file mode 100644 index 000000000..966474413 --- /dev/null +++ b/apps/publish/src/js/components/comments.js @@ -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 (); + }); + return ( +
+

+ {this.props.comments.length} + + Comments + +

+

+ - Hide Comments +

+ + + + +
+ {comments} +
+
+ ); + } else { + return ( +
+

+ {this.props.comments.length} + + Comments + +

+

+ + Show Comments +

+
+ ); + } + } +} diff --git a/apps/publish/src/js/components/lib/comment-box.js b/apps/publish/src/js/components/lib/comment-box.js new file mode 100644 index 000000000..5eeb8c94c --- /dev/null +++ b/apps/publish/src/js/components/lib/comment-box.js @@ -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 ( +
+
+ +
+
+ +

+ -> Post +

+
+
+ ); + } else { + return ( +
+
+ +
+
+ +

+ -> Post +

+
+
+ ); + } + } +} diff --git a/apps/publish/src/js/components/lib/comment.js b/apps/publish/src/js/components/lib/comment.js new file mode 100644 index 000000000..21d82a9ca --- /dev/null +++ b/apps/publish/src/js/components/lib/comment.js @@ -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 (

{line}

); + }); + + let date = moment(this.props.date).fromNow(); + + return ( +
+
+ +
+
+
+

{this.props.ship}

+

{date}

+
+
+ {body} +
+
+
+ ); + } +} diff --git a/apps/publish/src/js/components/lib/icons/sigil.js b/apps/publish/src/js/components/lib/icons/sigil.js index 0569dfd42..9084a3626 100644 --- a/apps/publish/src/js/components/lib/icons/sigil.js +++ b/apps/publish/src/js/components/lib/icons/sigil.js @@ -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 (
+ style={{ flexBasis: 35, padding: 4}}> { sealDict.getSeal(this.props.ship, this.props.size, prefix) } @@ -16,3 +17,4 @@ export class Sigil extends Component { ); } } + diff --git a/apps/publish/src/js/components/lib/seal-dict.js b/apps/publish/src/js/components/lib/seal-dict.js index 7c8acdd17..d4a4d85ac 100644 --- a/apps/publish/src/js/components/lib/seal-dict.js +++ b/apps/publish/src/js/components/lib/seal-dict.js @@ -5,56 +5,72 @@ import _ from 'lodash'; const ReactSVGComponents = { svg: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) }, circle: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) }, rect: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) }, path: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) }, g: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) }, polygon: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) }, line: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) }, polyline: p => { return ( - + { _.map(_.get(p, 'children', []), child => ReactSVGComponents[child.tag](child)) } ) diff --git a/apps/publish/src/js/components/new-blog.js b/apps/publish/src/js/components/new-blog.js index 3ecc867cd..2b5caef6c 100644 --- a/apps/publish/src/js/components/new-blog.js +++ b/apps/publish/src/js/components/new-blog.js @@ -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) { diff --git a/apps/publish/src/js/components/new-post.js b/apps/publish/src/js/components/new-post.js index 3dd809885..c15ade82a 100644 --- a/apps/publish/src/js/components/new-post.js +++ b/apps/publish/src/js/components/new-post.js @@ -12,7 +12,7 @@ class SideTab extends Component { super(props) } - render(props) { + render() { if (this.props.enabled){ return (
@@ -315,18 +335,12 @@ export class Post extends Component {

- -
-

- {this.state.comments.length} - - Comments - -

-

- + Show Comments -

-
+
); @@ -375,18 +389,12 @@ export class Post extends Component {

- -
-

- {this.state.comments.length} - - Comments - -

-

- + Show Comments -

-
+ ); diff --git a/apps/publish/src/js/components/pubs.js b/apps/publish/src/js/components/pubs.js index e17b1897a..470c4a3c2 100644 --- a/apps/publish/src/js/components/pubs.js +++ b/apps/publish/src/js/components/pubs.js @@ -37,7 +37,7 @@ export class Pubs extends Component {
- {data.title} +  {data.title}

@@ -62,7 +62,7 @@ export class Pubs extends Component {

- Title +  Title

Host diff --git a/apps/publish/src/js/components/root.js b/apps/publish/src/js/components/root.js index 55513b144..d2079a8dd 100644 --- a/apps/publish/src/js/components/root.js +++ b/apps/publish/src/js/components/root.js @@ -45,7 +45,7 @@ export class Root extends Component { return ( + } /> ); diff --git a/apps/publish/src/js/components/skeleton.js b/apps/publish/src/js/components/skeleton.js index cdc428dd1..ab5d2fde6 100644 --- a/apps/publish/src/js/components/skeleton.js +++ b/apps/publish/src/js/components/skeleton.js @@ -9,15 +9,16 @@ export class Skeleton extends Component { constructor(props){ super(props); } - //Header {...this.props}/> render() { return (

-
+
{this.props.children}
+
+
); } diff --git a/apps/publish/src/js/components/subs.js b/apps/publish/src/js/components/subs.js index 71394ee1b..6fd2bf6c6 100644 --- a/apps/publish/src/js/components/subs.js +++ b/apps/publish/src/js/components/subs.js @@ -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 ( -
-
- - {data.title} - + if (data.type === 'regular') { + return ( +
+
+ +  {data.title} + +
+

+ {data.host} +

+

+ {data.lastUpdated} +

+

+ Unsubscribe +

-

- {data.host} -

-

- {data.lastUpdated} -

-
- ); + ); + } else if (data.type === 'invite') { + return ( +
+
+ +  •  + Invite to + {data.title} + +
+

+ {data.host} +

+

+

+

+ + Accept + +     + + Reject + +

+
+ ); + } }); @@ -66,7 +149,7 @@ export class Subs extends Component {

- Title +  Title

Host @@ -74,6 +157,8 @@ export class Subs extends Component {

Last Updated

+

+

{blogs} diff --git a/apps/publish/src/js/reducers/rumor.js b/apps/publish/src/js/reducers/rumor.js new file mode 100644 index 000000000..2adf40182 --- /dev/null +++ b/apps/publish/src/js/reducers/rumor.js @@ -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= 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= 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= 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= 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 {}; } @@ -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); } diff --git a/apps/publish/tile/tile.js b/apps/publish/tile/tile.js new file mode 100644 index 000000000..230ed0cef --- /dev/null +++ b/apps/publish/tile/tile.js @@ -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( +

+ {this.props.data.invites}  + {text} +

+ ); + } + if (this.props.data.new > 0) { + let text = (this.props.data.new == 1) + ? "New Post" + : "New Posts" + info.push( +

+ {this.props.data.new}  + {text} +

+ ); + } + + return ( + + ); + } +} + +window.writeTile = PublishTile; diff --git a/apps/publish/urbit/app/write.hoon b/apps/publish/urbit/app/write.hoon index 43c22e425..baebc6e33 100644 --- a/apps/publish/urbit/app/write.hoon +++ b/apps/publish/urbit/app/write.hoon @@ -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 diff --git a/apps/publish/urbit/app/write/img/tile.png b/apps/publish/urbit/app/write/img/tile.png new file mode 100644 index 000000000..ce9bc4162 Binary files /dev/null and b/apps/publish/urbit/app/write/img/tile.png differ diff --git a/apps/publish/urbit/mar/write/action.hoon b/apps/publish/urbit/mar/write/action.hoon index d571e6dda..c75c1624e 100644 --- a/apps/publish/urbit/mar/write/action.hoon +++ b/apps/publish/urbit/mar/write/action.hoon @@ -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) + == + :: -- -- -- diff --git a/apps/publish/urbit/mar/write/rumor.hoon b/apps/publish/urbit/mar/write/rumor.hoon index ef4dcbcf6..a5435f8b7 100644 --- a/apps/publish/urbit/mar/write/rumor.hoon +++ b/apps/publish/urbit/mar/write/rumor.hoon @@ -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)] == + :: == :: -- diff --git a/apps/publish/urbit/mar/write/update.hoon b/apps/publish/urbit/mar/write/update.hoon new file mode 100644 index 000000000..eaf562e08 --- /dev/null +++ b/apps/publish/urbit/mar/write/update.hoon @@ -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] + == + :: + == + :: + -- +-- diff --git a/apps/publish/urbit/ren/write/comments.hoon b/apps/publish/urbit/ren/write/comments.hoon index 1175f147b..0595d69fa 100644 --- a/apps/publish/urbit/ren/write/comments.hoon +++ b/apps/publish/urbit/ren/write/comments.hoon @@ -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/ diff --git a/apps/publish/urbit/sur/write.hoon b/apps/publish/urbit/sur/write.hoon index 95631a279..81af230b8 100644 --- a/apps/publish/urbit/sur/write.hoon +++ b/apps/publish/urbit/sur/write.hoon @@ -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] + == --