diff --git a/pkg/arvo/app/publish.hoon b/pkg/arvo/app/publish.hoon index 4e5fcfe81c..d0ca7df539 100644 --- a/pkg/arvo/app/publish.hoon +++ b/pkg/arvo/app/publish.hoon @@ -1,6 +1,7 @@ :: /- *publish, *group-store, + *group-hook, *permission-hook, *permission-group-hook, *permission-store, @@ -295,7 +296,9 @@ [~ this] =/ who=@p (slav %p i.t.wir) =/ book=@tas i.t.t.wir - [~ this(subs (~(del by subs) who book))] + =/ del [%del-book who book] + :_ this(subs (~(del by subs) who book)) + [%give %fact [/primary]~ %publish-primary-delta !>(del)]~ :: Resubscribe to any subscription we get kicked from. The case of actually :: getting banned from a notebook is handled by %watch-ack :: @@ -703,49 +706,31 @@ ++ handle-permission-update |= upd=permission-update ^- (quip card _state) - ?+ -.upd + ?. ?=(?(%remove %add) -.upd) [~ state] - :: - %remove - =/ book=(unit @tas) - %+ roll ~(tap by books) - |= [[nom=@tas book=notebook] out=(unit @tas)] - ?: =(path.upd subscribers.book) - `nom - out - ?~ book - [~ state] - :_ state - %- zing - %+ turn ~(tap in who.upd) - |= who=@p - ?: (allowed who %read u.book) - ~ + =/ book=(unit @tas) + %+ roll ~(tap by books) + |= [[nom=@tas book=notebook] out=(unit @tas)] + ?: =(path.upd subscribers.book) + `nom + out + ?~ book + [~ state] + :_ state + %- zing + %+ turn ~(tap in who.upd) + |= who=@p + ?. (allowed who %read u.book) [%give %kick [/notebook/[u.book]]~ `who]~ - :: - %add - =/ book=(unit @tas) - %+ roll ~(tap by books) - |= [[nom=@tas book=notebook] out=(unit @tas)] - ?: =(path.upd subscribers.book) - `nom - out - ?~ book - [~ state] - :_ state - %- zing - %+ turn ~(tap in who.upd) - |= who=@p - ?. (allowed who %read u.book) - ~ - =/ uid (sham %publish who u.book eny.bol) - =/ inv=invite - :* our.bol %publish /notebook/[u.book] who - (crip "invite for notebook {}/{(trip u.book)}") - == - =/ act=invite-action [%invite /publish uid inv] - [%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]~ - == + ?: ?=(%remove -.upd) + ~ + =/ uid (sham %publish who u.book eny.bol) + =/ inv=invite + :* our.bol %publish /notebook/[u.book] who + (crip "invite for notebook {}/{(trip u.book)}") + == + =/ act=invite-action [%invite /publish uid inv] + [%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]~ :: ++ handle-invite-update |= upd=invite-update @@ -870,6 +855,11 @@ ^- card [%pass / %agent [our.bol %group-store] %poke %group-action !>(act)] :: +++ group-hook-poke + |= act=group-hook-action + ^- card + [%pass / %agent [our.bol %group-hook] %poke %group-hook-action !>(act)] +:: ++ contact-view-create |= act=[%create path (set ship)] ^- card @@ -989,8 +979,9 @@ %- zing :~ [(group-poke [%bundle write-path])]~ [(group-poke [%bundle read-path])]~ + [(group-hook-poke [%add our.bol write-path])]~ + [(group-hook-poke [%add our.bol read-path])]~ [(group-poke [%add (sy our.bol ~) write-path])]~ - [(group-poke [%add (sy our.bol ~) read-path])]~ (create-security read-path write-path %journal) [(perm-hook-poke [%add-owned write-path write-path])]~ [(perm-hook-poke [%add-owned read-path read-path])]~ @@ -1148,11 +1139,22 @@ %del-book ?. (team:title our.bol src.bol) ~|("action not permitted" !!) - ?. (~(has by books) book.act) + =/ book=(unit notebook) (~(get by books) book.act) + ?~ book ~|("nonexistent notebook {}" !!) =/ pax=path /app/publish/notebooks/[book.act] - :_ state - [(delete-dir pax)]~ + ?> ?=(^ writers.u.book) + ?> ?=(^ subscribers.u.book) + =/ cards=(list card) + :~ (delete-dir pax) + (perm-hook-poke [%remove writers.u.book]) + (perm-hook-poke [%remove subscribers.u.book]) + == + =? cards =('~' i.writers.u.book) + [(group-poke [%unbundle writers.u.book]) cards] + =? cards =('~' i.subscribers.u.book) + [(group-poke [%unbundle subscribers.u.book]) cards] + [cards state] :: %del-note ?: &(=(src.bol our.bol) !=(our.bol who.act)) @@ -1279,7 +1281,17 @@ ?- -.del %add-book =. tile-num (add tile-num (get-unread data.del)) - (emit-updates-and-state host.del book.del data.del del sty) + ?: =(our.bol host.del) + (emit-updates-and-state host.del book.del data.del del sty) + =/ write-pax writers.data.del + =/ read-pax subscribers.data.del + =^ cards state + (emit-updates-and-state host.del book.del data.del del sty) + :_ state + :* (group-hook-poke [%add host.del write-pax]) + (group-hook-poke [%add host.del read-pax]) + cards + == :: %add-note =/ book=(unit notebook) @@ -1347,14 +1359,14 @@ (emit-updates-and-state host.del book.del u.book del sty) :: %del-book - =. tile-num - %+ sub tile-num - (get-unread (~(got by books) book.del)) ?: =(our.bol host.del) :_ sty(books (~(del by books.sty) book.del)) :~ [%give %fact [/notebook/[book.del]]~ %publish-notebook-delta !>(del)] [%give %fact [/primary]~ %publish-primary-delta !>(del)] == + =. tile-num + %+ sub tile-num + (get-unread (~(got by subs) host.del book.del)) =/ jon=json (frond:enjs:format %notifications (numb:enjs:format tile-num.sty)) :_ sty(subs (~(del by subs.sty) host.del book.del)) diff --git a/pkg/interface/publish/src/css/custom.css b/pkg/interface/publish/src/css/custom.css index 1611a65a4a..e543cad849 100644 --- a/pkg/interface/publish/src/css/custom.css +++ b/pkg/interface/publish/src/css/custom.css @@ -195,6 +195,15 @@ a { color: #7F7F7F; } +.options::after { + content: "⌃"; + transform: rotate(180deg); + position: absolute; + right: 4px; + top: 6px; + color: #7f7f7f; +} + [contenteditable]:focus { outline: 0px solid transparent; } diff --git a/pkg/interface/publish/src/js/components/lib/comments.js b/pkg/interface/publish/src/js/components/lib/comments.js index 721e033228..04b22b4961 100644 --- a/pkg/interface/publish/src/js/components/lib/comments.js +++ b/pkg/interface/publish/src/js/components/lib/comments.js @@ -32,6 +32,9 @@ export class Comments extends Component { } render() { + if (!this.props.enabled) { + return null; + } let commentArray = this.props.comments.map((com, i) => { return ( { + return ( + + ); + }); + + return ( +
{this.optsButton = el}}> + +
{this.optsList = el}} + style={{right:0, width:this.props.width, display: display}}> + {optionsList} +
+
+ ) + } +} + +export default Dropdown diff --git a/pkg/interface/publish/src/js/components/lib/new-post.js b/pkg/interface/publish/src/js/components/lib/new-post.js index ffb4109c85..e965599b5d 100644 --- a/pkg/interface/publish/src/js/components/lib/new-post.js +++ b/pkg/interface/publish/src/js/components/lib/new-post.js @@ -98,7 +98,7 @@ export class NewPost extends Component { popout={props.popout} /> + let subsComponent = (this.props.ship.slice(1) !== window.ship) + ? null + : + Subscribers + ; + + let settingsComponent = (this.props.ship.slice(1) !== window.ship) + ? null + : + Settings + ; + return (
About -
diff --git a/pkg/interface/publish/src/js/components/lib/settings.js b/pkg/interface/publish/src/js/components/lib/settings.js index 969ba9cdf3..a1db1ba343 100644 --- a/pkg/interface/publish/src/js/components/lib/settings.js +++ b/pkg/interface/publish/src/js/components/lib/settings.js @@ -1,41 +1,39 @@ import React, { Component } from 'react'; -//TODO Settings for owned notebooks export class Settings extends Component { - render() { - return ( -
-
- - Share a link to this notebook -
- - -
-
+ constructor(props){ + super(props) + this.deleteNotebook = this.deleteNotebook.bind(this); + } -
- - Change the name of this notebook -
- -
-
-
- - Change the name of this notebook -
- -
- - ) + ) + } else { + return null; + } } } diff --git a/pkg/interface/publish/src/js/components/lib/subscriber-item.js b/pkg/interface/publish/src/js/components/lib/subscriber-item.js deleted file mode 100644 index 612f166e14..0000000000 --- a/pkg/interface/publish/src/js/components/lib/subscriber-item.js +++ /dev/null @@ -1,15 +0,0 @@ -import React, { Component } from 'react' - -//TODO fill sigil/avatar + name from props - -export class SubscriberItem extends Component { - render() { - return ( -
- -
- ) - } -} - -export default SubscriberItem diff --git a/pkg/interface/publish/src/js/components/lib/subscribers.js b/pkg/interface/publish/src/js/components/lib/subscribers.js index 4e3b812191..2d36247b1e 100644 --- a/pkg/interface/publish/src/js/components/lib/subscribers.js +++ b/pkg/interface/publish/src/js/components/lib/subscribers.js @@ -1,53 +1,184 @@ import React, { Component } from 'react'; -import { SubscriberItem } from './subscriber-item'; - -//TODO map list of subscriber-items from props +import { Dropdown } from './dropdown'; export class Subscribers extends Component { + constructor(props){ + super(props); + this.redirect = this.redirect.bind(this); + this.addUser = this.addUser.bind(this); + this.removeUser = this.removeUser.bind(this); + } + + addUser(who, path) { + let action = { + add: { + members: [who], + path: path, + } + } + window.api.action("group-store", "group-action", action); + } + + removeUser(who, path) { + let action = { + remove: { + members: [who], + path: path, + } + } + window.api.action("group-store", "group-action", action); + } + + redirect(url) { + window.location.href = url; + } + render() { + let readPath = this.props.notebook["subscribers-group-path"] + let readPerms = (readPath) + ? this.props.permissions[readPath] + : null; + let writePath = this.props.notebook["writers-group-path"] + let writePerms = (writePath) + ? this.props.permissions[writePath] + : null; + + let writers = []; + if (writePerms && writePerms.kind === 'white') { + let withoutUs = new Set(writePerms.who) + withoutUs.delete(window.ship); + writers = Array.from(withoutUs).map((who, i) => { + let width = 0; + let options = []; + if (readPath === writePath) { + width = 258; + let url = `/~contacts${writePath}`; + options = [{ + cls: "tl pointer", + txt: "Manage this group in the contacts view", + action: () => {this.redirect(url)} + }]; + } else { + width = 157; + options = [{ + cls: "tl pointer", + txt: "Demote to subscriber", + action: () => {this.removeUser(`~${who}`, writePath)} + }]; + } + return ( +
+
{`~${who}`}
+ +
+ ) + }); + } + + if (writers.length === 0) { + writers = +
+ There are no participants on this notebook. +
+ } + + let subscribers = null; + if (readPath !== writePath) { + if (this.props.notebook.subscribers){ + let width = 162; + subscribers = this.props.notebook.subscribers.map((who, i) => { + let options = [ + { cls: "tl mb2 pointer", + txt: "Promote to participant", + action: () => {this.addUser(who, writePath)} + }, + { cls: "tl red2 pointer", + txt: "Ban", + action: () => {this.addUser(who, readPath)} + }, + ]; + return ( +
+
{who}
+ +
+ ) + }); + } + if (subscribers.length === 0) { + subscribers = +
+ There are no subscribers to this notebook. +
+ } + } + + let subsContainer = (readPath === writePath) + ? null + :
+
Subscribers (read access only)
+ {subscribers} +
; + + + let bannedContainer = null; + if (readPerms && readPerms.kind === 'black') { + let width = 72; + let banned = Array.from(readPerms.who).map((who, i) => { + let options = [{ + cls: "tl red2 pointer", + txt: "Unban", + action: () => {this.removeUser(`~${who}`, readPath)} + }]; + return ( +
+
{`~${who}`}
+ +
+ ) + }); + if (banned.length === 0) { + banned = +
+ There are no users banned from this notebook. +
+ } + bannedContainer = +
+
Banned
+ {banned} +
; + } + + return (
-
-
Host
-
-
-
~fabled-faster
-
Last active
+
+
Host
+
+
{this.props.host}
-
Options ⌃
-
-
-
Participants (read and write access)
-
There are no paticipants in this notebook.
-
-
-
~fabled-faster
-
Last active
+
+
+ Participants (read and write access)
-
Options ⌃
+ {writers}
-
-
-
Subscribers (read access only)
-
-
-
~fabled-faster
-
Last active
-
-
Options ⌃
-
-
-
-
Banned
-
-
-
~fabled-faster
-
Last active
-
-
Options ⌃
-
-
+ {subsContainer} + {bannedContainer}
) } diff --git a/pkg/interface/publish/src/js/components/root.js b/pkg/interface/publish/src/js/components/root.js index 3edbe1971c..02253cfa76 100644 --- a/pkg/interface/publish/src/js/components/root.js +++ b/pkg/interface/publish/src/js/components/root.js @@ -143,6 +143,7 @@ export class Root extends Component { contacts={notebookContacts} sidebarShown={state.sidebarShown} popout={popout} + permissions={state.permissions} {...props} /> diff --git a/pkg/interface/publish/src/js/reducers/permission.js b/pkg/interface/publish/src/js/reducers/permission.js new file mode 100644 index 0000000000..80af6f4c53 --- /dev/null +++ b/pkg/interface/publish/src/js/reducers/permission.js @@ -0,0 +1,58 @@ +import _ from 'lodash'; + +export class PermissionReducer { + reduce(json, state) { + let data = _.get(json, 'permission-initial', false); + if (data) { + for (let perm in data) { + state.permissions[perm] = { + who: new Set(data[perm].who), + kind: data[perm].kind + } + } + } + data = _.get(json, 'permission-update', false); + if (data) { + this.create(data, state); + this.delete(data, state); + this.add(data, state); + this.remove(data, state); + } + } + + create(json, state) { + let data = _.get(json, 'create', false); + if (data) { + state.permissions[data.path] = { + kind: data.kind, + who: new Set(data.who) + }; + } + } + + delete(json, state) { + let data = _.get(json, 'delete', false); + if (data) { + delete state.permissions[data.path]; + } + } + + add(json, state) { + let data = _.get(json, 'add', false); + if (data) { + for (let member of data.who) { + state.permissions[data.path].who.add(member); + } + } + } + + remove(json, state) { + let data = _.get(json, 'remove', false); + if (data) { + for (let member of data.who) { + state.permissions[data.path].who.delete(member); + } + } + } +} + diff --git a/pkg/interface/publish/src/js/reducers/response.js b/pkg/interface/publish/src/js/reducers/response.js index 64a9d8c021..ec9849d0d3 100644 --- a/pkg/interface/publish/src/js/reducers/response.js +++ b/pkg/interface/publish/src/js/reducers/response.js @@ -64,6 +64,14 @@ export class ResponseReducer { if (state.notebooks[json.host][json.notebook]) { state.notebooks[json.host][json.notebook]["notes-by-date"] = json.data.notebook["notes-by-date"]; + state.notebooks[json.host][json.notebook].subscribers = + json.data.notebook.subscribers; + state.notebooks[json.host][json.notebook].comments = + json.data.notebook.comments; + state.notebooks[json.host][json.notebook]["subscribers-group-path"] = + json.data.notebook["subscribers-group-path"]; + state.notebooks[json.host][json.notebook]["writers-group-path"] = + json.data.notebook["writers-group-path"]; if (state.notebooks[json.host][json.notebook].notes) { for (var key in json.data.notebook.notes) { let oldNote = state.notebooks[json.host][json.notebook].notes[key]; diff --git a/pkg/interface/publish/src/js/store.js b/pkg/interface/publish/src/js/store.js index 408aa65451..11569a176f 100644 --- a/pkg/interface/publish/src/js/store.js +++ b/pkg/interface/publish/src/js/store.js @@ -1,8 +1,9 @@ -import { InitialReducer } from '/reducers/initial'; -import { PrimaryReducer } from '/reducers/primary'; -import { ResponseReducer } from '/reducers/response'; -import { GroupReducer } from '/reducers/group'; -import { InviteReducer } from '/reducers/invite'; +import { InitialReducer } from '/reducers/initial'; +import { PrimaryReducer } from '/reducers/primary'; +import { ResponseReducer } from '/reducers/response'; +import { GroupReducer } from '/reducers/group'; +import { InviteReducer } from '/reducers/invite'; +import { PermissionReducer } from '/reducers/permission'; class Store { constructor() { @@ -16,11 +17,12 @@ class Store { sidebarShown: true } - this.initialReducer = new InitialReducer(); - this.primaryReducer = new PrimaryReducer(); - this.responseReducer = new ResponseReducer(); - this.groupReducer = new GroupReducer(); - this.inviteReducer = new InviteReducer(); + this.initialReducer = new InitialReducer(); + this.primaryReducer = new PrimaryReducer(); + this.responseReducer = new ResponseReducer(); + this.groupReducer = new GroupReducer(); + this.inviteReducer = new InviteReducer(); + this.permissionReducer = new PermissionReducer(); this.setState = () => {}; this.initialReducer.reduce(window.injectedState, this.state); @@ -33,6 +35,7 @@ class Store { handleEvent(evt) { if (evt.from && evt.from.path === '/all') { this.groupReducer.reduce(evt.data, this.state); + this.permissionReducer.reduce(evt.data, this.state); } else if (evt.from && evt.from.path === '/primary'){ this.primaryReducer.reduce(evt.data, this.state); diff --git a/pkg/interface/publish/src/js/subscription.js b/pkg/interface/publish/src/js/subscription.js index b882bfe279..2cd9223c59 100644 --- a/pkg/interface/publish/src/js/subscription.js +++ b/pkg/interface/publish/src/js/subscription.js @@ -24,6 +24,9 @@ export class Subscription { api.bind('/primary', 'PUT', api.authTokens.ship, 'invite-view', this.handleEvent.bind(this), this.handleError.bind(this)); + api.bind('/all', 'PUT', api.authTokens.ship, 'permission-store', + this.handleEvent.bind(this), + this.handleError.bind(this)); } handleEvent(diff) {