mirror of
https://github.com/urbit/shrub.git
synced 2024-12-18 15:55:00 +03:00
Merge pull request #2286 from urbit/ixv/publish-members-settings
OS1: added members and settings page to publish
This commit is contained in:
commit
d49987bcc7
@ -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 {<our.bol>}/{(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 {<our.bol>}/{(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 {<book.act>}" !!)
|
||||
=/ 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))
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 (
|
||||
<CommentItem
|
||||
|
73
pkg/interface/publish/src/js/components/lib/dropdown.js
Normal file
73
pkg/interface/publish/src/js/components/lib/dropdown.js
Normal file
@ -0,0 +1,73 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export class Dropdown extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.toggleDropdown = this.toggleDropdown.bind(this);
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
this.collapseAndDispatch = this.collapseAndDispatch.bind(this);
|
||||
this.state = {
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('mousedown', this.handleClickOutside);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('mousedown', this.handleClickOutside);
|
||||
}
|
||||
|
||||
handleClickOutside(evt) {
|
||||
if (this.optsList && !this.optsList.contains(evt.target) &&
|
||||
this.optsButton && !this.optsButton.contains(evt.target)) {
|
||||
this.setState({open: false});
|
||||
}
|
||||
}
|
||||
|
||||
toggleDropdown() {
|
||||
this.setState({open: !this.state.open});
|
||||
}
|
||||
|
||||
collapseAndDispatch(action){
|
||||
this.setState({open: false}, action);
|
||||
}
|
||||
|
||||
render() {
|
||||
let display = (this.state.open)
|
||||
? "block" : "none";
|
||||
|
||||
let optionsColor = (this.state.open)
|
||||
? '#e6e6e6' : 'white';
|
||||
|
||||
let optionsList = this.props.options.map((val, i) => {
|
||||
return (
|
||||
<button key={i} className={val.cls}
|
||||
onClick={() => this.collapseAndDispatch(val.action)}>
|
||||
{val.txt}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="options relative dib"
|
||||
ref={(el) => {this.optsButton = el}}>
|
||||
<button className="pr3 mb1 pointer br2 pa2 pr4"
|
||||
style={{backgroundColor: optionsColor}}
|
||||
onClick={this.toggleDropdown}>
|
||||
{this.props.buttonText}
|
||||
</button>
|
||||
<div className="absolute flex flex-column pa4 ba b--gray4 br2 z-1 bg-white"
|
||||
ref={(el) => {this.optsList = el}}
|
||||
style={{right:0, width:this.props.width, display: display}}>
|
||||
{optionsList}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Dropdown
|
@ -98,7 +98,7 @@ export class NewPost extends Component {
|
||||
popout={props.popout}
|
||||
/>
|
||||
<button
|
||||
className="v-mid w-100 mw7 tl pl4 h1"
|
||||
className={"v-mid w-100 mw7 tl h1 pl4"}
|
||||
disabled={!state.submit}
|
||||
style={submitStyle}
|
||||
onClick={this.postSubmit}>
|
||||
|
@ -15,7 +15,8 @@ export class NewScreen extends Component {
|
||||
groups: [],
|
||||
ships: []
|
||||
},
|
||||
createGroup: false
|
||||
createGroup: false,
|
||||
awaiting: false,
|
||||
};
|
||||
|
||||
this.idChange = this.idChange.bind(this);
|
||||
@ -27,9 +28,8 @@ export class NewScreen extends Component {
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props, state } = this;
|
||||
if (props.notebooks && (("~" + window.ship) in props.notebooks)) {
|
||||
let notebookId = stringToSymbol(state.idName)
|
||||
if (notebookId in props.notebooks["~" + window.ship]) {
|
||||
let notebook = `/~${window.ship}/${notebookId}`;
|
||||
if (state.awaiting in props.notebooks["~" + window.ship]) {
|
||||
let notebook = `/~${window.ship}/${state.awaiting}`;
|
||||
props.history.push("/~publish/notebook" + notebook);
|
||||
}
|
||||
}
|
||||
@ -92,7 +92,9 @@ export class NewScreen extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
props.api.action("publish", "publish-action", action);
|
||||
this.setState({awaiting: bookId}, () => {
|
||||
props.api.action("publish", "publish-action", action);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -5,10 +5,7 @@ import { Comments } from './comments';
|
||||
import { NoteNavigation } from './note-navigation';
|
||||
import moment from 'moment';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
//TODO ask for note if we don't have it
|
||||
//TODO initialise note if no state
|
||||
|
||||
//TODO if comments are disabled on the notebook, don't render comments
|
||||
export class Note extends Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
@ -38,6 +35,14 @@ export class Note extends Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let readAction = {
|
||||
read: {
|
||||
who: this.props.ship.slice(1),
|
||||
book: this.props.book,
|
||||
note: this.props.note,
|
||||
}
|
||||
}
|
||||
window.api.action("publish", "publish-action", readAction);
|
||||
window.api.fetchNote(this.props.ship, this.props.book, this.props.note);
|
||||
}
|
||||
|
||||
@ -189,7 +194,7 @@ export class Note extends Component {
|
||||
ship={props.ship}
|
||||
book={props.book}
|
||||
/>
|
||||
<Comments
|
||||
<Comments enabled={notebook.comments}
|
||||
ship={props.ship}
|
||||
book={props.book}
|
||||
note={props.note}
|
||||
|
@ -6,8 +6,6 @@ import { Subscribers } from './subscribers';
|
||||
import { Settings } from './settings';
|
||||
import Sidebar from './sidebar';
|
||||
|
||||
//TODO subcomponent logic for subscribers, settings
|
||||
|
||||
export class Notebook extends Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
@ -46,13 +44,15 @@ export class Notebook extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.props.notebooks[this.props.ship][this.props.book].notes) {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
if (!notebook.subscribers) {
|
||||
window.api.fetchNotebook(this.props.ship, this.props.book);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.notebooks[this.props.ship][this.props.book].notes) {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
if (notebook.notes) {
|
||||
this.onScroll();
|
||||
}
|
||||
}
|
||||
@ -82,9 +82,9 @@ export class Notebook extends Component {
|
||||
|
||||
let tabStyles = {
|
||||
posts: "bb b--gray4 gray2 pv4 ph2",
|
||||
about: "bb b--gray4 gray2 pv4 ph2"
|
||||
// subscribers: "bb b--gray4 gray2 pv4 ph2",
|
||||
// settings: "bb b--gray4 pr2 gray2 pv4 ph2",
|
||||
about: "bb b--gray4 gray2 pv4 ph2",
|
||||
subscribers: "bb b--gray4 gray2 pv4 ph2",
|
||||
settings: "bb b--gray4 pr2 gray2 pv4 ph2",
|
||||
};
|
||||
tabStyles[props.view] = "bb b--black black pv4 ph2";
|
||||
|
||||
@ -104,12 +104,22 @@ export class Notebook extends Component {
|
||||
case "about":
|
||||
inner = <p className="f8 lh-solid">{notebook.about}</p>
|
||||
break;
|
||||
// case "subscribers":
|
||||
// inner = <Subscribers/>
|
||||
// break;
|
||||
// case "settings":
|
||||
// inner = <Settings/>
|
||||
// break;
|
||||
case "subscribers":
|
||||
inner = <Subscribers
|
||||
host={this.props.ship}
|
||||
book={this.props.book}
|
||||
notebook={notebook}
|
||||
permissions={this.props.permissions}
|
||||
groups={this.props.groups}/>
|
||||
break;
|
||||
case "settings":
|
||||
inner = <Settings
|
||||
host={this.props.ship}
|
||||
book={this.props.book}
|
||||
notebook={notebook}
|
||||
groups={this.props.groups}
|
||||
history={this.props.history}/>
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -148,6 +158,18 @@ export class Notebook extends Component {
|
||||
Unsubscribe
|
||||
</button>
|
||||
|
||||
let subsComponent = (this.props.ship.slice(1) !== window.ship)
|
||||
? null
|
||||
: <Link to={subs} className={tabStyles.subscribers}>
|
||||
Subscribers
|
||||
</Link>;
|
||||
|
||||
let settingsComponent = (this.props.ship.slice(1) !== window.ship)
|
||||
? null
|
||||
: <Link to={settings} className={tabStyles.settings}>
|
||||
Settings
|
||||
</Link>;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="center mw6 f9 h-100"
|
||||
@ -200,8 +222,9 @@ export class Notebook extends Component {
|
||||
<Link to={about} className={tabStyles.about}>
|
||||
About
|
||||
</Link>
|
||||
<div
|
||||
className="bb b--gray4 gray2 pv4 ph2"
|
||||
{subsComponent}
|
||||
{settingsComponent}
|
||||
<div className="bb b--gray4 gray2 pv4 ph2"
|
||||
style={{ flexGrow: 1 }}></div>
|
||||
</div>
|
||||
|
||||
|
@ -1,41 +1,39 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
//TODO Settings for owned notebooks
|
||||
export class Settings extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-column mb8">
|
||||
<label for="name" className="f9">Share</label>
|
||||
<small id="name-desc" className="f9 mb2 gray3">Share a link to this notebook</small>
|
||||
<div className="flex">
|
||||
<input style={{flex: "1"}} id="name" placeholder="dopzod.arvo.network/4f5hsS" className="input-reset bt bl bb pa3 gray4" type="text" aria-describedby="name-desc"/>
|
||||
<button className="bt br bb pa3 b--gray4">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
constructor(props){
|
||||
super(props)
|
||||
this.deleteNotebook = this.deleteNotebook.bind(this);
|
||||
}
|
||||
|
||||
<div className="flex flex-column mb8">
|
||||
<label for="name" className="f9">Rename</label>
|
||||
<small id="name-desc" className="f9 mb2 gray3">Change the name of this notebook</small>
|
||||
<div className="flex">
|
||||
<input style={{flex: "1"}} id="name" placeholder="Notebook Name" className="input-reset ba pa3 gray4" type="text" aria-describedby="name-desc"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-column">
|
||||
<label for="name" className="f9">Export</label>
|
||||
<small id="name-desc" className="f9 mb2 gray3">Change the name of this notebook</small>
|
||||
<button className="bg-black white pa3">
|
||||
<div className="flex justify-between">
|
||||
<div>Export Notebook</div>
|
||||
<div>↓</div>
|
||||
</div>
|
||||
deleteNotebook(){
|
||||
let action = {
|
||||
"del-book": {
|
||||
book: this.props.book
|
||||
}
|
||||
}
|
||||
window.api.action("publish", "publish-action", action);
|
||||
this.props.history.push('/~publish');
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.host.slice(1) === window.ship) {
|
||||
return (
|
||||
<div className="flex-column">
|
||||
<p className="f9 mt3 lh-copy db">Delete Notebook</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Permanently delete this notebook. (All current members will no longer see this notebook)
|
||||
</p>
|
||||
<button className="b--red2 red2 pointer dib f9 ba pa2"
|
||||
onClick={this.deleteNotebook}>
|
||||
Delete this notebook
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
|
||||
//TODO fill sigil/avatar + name from props
|
||||
|
||||
export class SubscriberItem extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SubscriberItem
|
@ -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 (
|
||||
<div className="flex justify-between" key={i}>
|
||||
<div className="f9 mono mr2">{`~${who}`}</div>
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={"Options"}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
if (writers.length === 0) {
|
||||
writers =
|
||||
<div className="f9">
|
||||
There are no participants on this notebook.
|
||||
</div>
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="flex justify-between" key={i}>
|
||||
<div className="f9 mono mr2">{who}</div>
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={"Options"}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
}
|
||||
if (subscribers.length === 0) {
|
||||
subscribers =
|
||||
<div className="f9">
|
||||
There are no subscribers to this notebook.
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
let subsContainer = (readPath === writePath)
|
||||
? null
|
||||
: <div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">Subscribers (read access only)</div>
|
||||
{subscribers}
|
||||
</div>;
|
||||
|
||||
|
||||
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 (
|
||||
<div className="flex justify-between" key={i}>
|
||||
<div className="f9 mono mr2">{`~${who}`}</div>
|
||||
<Dropdown
|
||||
options={options}
|
||||
width={width}
|
||||
buttonText={"Options"}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
if (banned.length === 0) {
|
||||
banned =
|
||||
<div className="f9">
|
||||
There are no users banned from this notebook.
|
||||
</div>
|
||||
}
|
||||
bannedContainer =
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">Banned</div>
|
||||
{banned}
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2">Host</div>
|
||||
<div className="flex justify-between mt3">
|
||||
<div className="flex">
|
||||
<div className="f9 mono mr2">~fabled-faster</div>
|
||||
<div className="f9 gray2">Last active</div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2">Host</div>
|
||||
<div className="flex justify-between mt3">
|
||||
<div className="f9 mono mr2">{this.props.host}</div>
|
||||
</div>
|
||||
<div className="f9">Options ⌃</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6">Participants (read and write access)</div>
|
||||
<div className="f9 mt3">There are no paticipants in this notebook.</div>
|
||||
<div className="flex justify-between mt3">
|
||||
<div className="flex">
|
||||
<div className="f9 mono mr2">~fabled-faster</div>
|
||||
<div className="f9 gray2">Last active</div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">
|
||||
Participants (read and write access)
|
||||
</div>
|
||||
<div className="f9">Options ⌃</div>
|
||||
{writers}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">Subscribers (read access only)</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex">
|
||||
<div className="f9 mono mr2">~fabled-faster</div>
|
||||
<div className="f9 gray2">Last active</div>
|
||||
</div>
|
||||
<div className="f9">Options ⌃</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-column">
|
||||
<div className="f9 gray2 mt6 mb3">Banned</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex">
|
||||
<div className="f9 mono mr2">~fabled-faster</div>
|
||||
<div className="f9 gray2">Last active</div>
|
||||
</div>
|
||||
<div className="f9">Options ⌃</div>
|
||||
</div>
|
||||
</div>
|
||||
{subsContainer}
|
||||
{bannedContainer}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -143,6 +143,7 @@ export class Root extends Component {
|
||||
contacts={notebookContacts}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
permissions={state.permissions}
|
||||
{...props}
|
||||
/>
|
||||
</Skeleton>
|
||||
|
58
pkg/interface/publish/src/js/reducers/permission.js
Normal file
58
pkg/interface/publish/src/js/reducers/permission.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user