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 (
+
- 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]
+ ==
--