Merge pull request #32 from urbit/publish-fixes

Publish fixes
This commit is contained in:
ixv 2019-07-18 15:36:42 -07:00 committed by GitHub
commit 1f7bcdb7ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 754 additions and 60365 deletions

4
.gitignore vendored
View File

@ -74,8 +74,8 @@ apps/*/dist/
*.swn
# built js and css files
apps/publish/urbit/app/write/js/*
apps/publish/urbit/app/write/css/*
apps/publish/urbit/app/publish/js/*
apps/publish/urbit/app/publish/css/*
apps/chat/urbit/app/chat/js/*
apps/chat/urbit/app/chat/css/*
apps/clock/urbit/app/clock/js/*

View File

@ -4,6 +4,25 @@ p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
font-family: Inter, sans-serif;
}
button {
background: none;
color: inherit;
border: none;
cursor: pointer;
outline: inherit;
padding: 0;
}
p {
font-size: 16px;
line-height: 24px;
}
pre {
padding: 8px;
background-color: #f9f9f9;
}
a {
color: inherit;
text-decoration: inherit;
@ -130,6 +149,10 @@ h4 {
width: 688px;
}
.mw-336 {
max-width: 336px;
}
.mw-688 {
max-width: 688px;
}
@ -228,7 +251,7 @@ h4 {
background-color: #B1B2B3;
}
.title-preview {
.two-lines {
display: -webkit-box;
-webkit-box-orient: vertical;
word-wrap: break-word;
@ -236,10 +259,17 @@ h4 {
overflow: hidden;
}
.body-preview {
.five-lines {
display: -webkit-box;
-webkit-box-orient: vertical;
word-wrap: break-word;
-webkit-line-clamp: 5;
overflow: hidden;
}
.one-line {
word-wrap: break-word;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

View File

@ -3,44 +3,26 @@ import classnames from 'classnames';
import { PostPreview } from '/components/lib/post-preview';
import _ from 'lodash';
import { PathControl } from '/components/lib/path-control';
import { BlogData } from '/components/lib/blog-data';
import { BlogNotes } from '/components/lib/blog-notes';
import { BlogSubs } from '/components/lib/blog-subs';
import { BlogSettings } from '/components/lib/blog-settings';
import { withRouter } from 'react-router';
import { NotFound } from '/components/not-found';
import { Link } from 'react-router-dom';
const PC = withRouter(PathControl);
const NF = withRouter(NotFound);
const BN = withRouter(BlogNotes);
const BS = withRouter(BlogSettings)
class Subscribe extends Component {
constructor(props) {
super(props);
}
render() {
if (this.props.actionType === 'subscribe') {
return (
<p className="label-small-2 b pointer"
onClick={this.props.subscribe}>
Subscribe
</p>
);
} else if (this.props.actionType === 'unsubscribe') {
return (
<p className="label-small-2 b pointer"
onClick={this.props.unsubscribe}>
Unsubscribe
</p>
);
} else {
return null;
}
}
}
export class Blog extends Component {
constructor(props){
super(props);
this.state = {
view: 'notes',
awaiting: false,
postProps: [],
blogTitle: '',
@ -54,11 +36,17 @@ export class Blog extends Component {
this.subscribe = this.subscribe.bind(this);
this.unsubscribe = this.unsubscribe.bind(this);
this.viewSubs = this.viewSubs.bind(this);
this.viewSettings = this.viewSettings.bind(this);
this.viewNotes = this.viewNotes.bind(this);
this.blog = null;
}
handleEvent(diff) {
if (diff.data.total) {
let blog = diff.data.total.data;
this.blog = blog;
this.setState({
postProps: this.buildPosts(blog),
blog: blog,
@ -74,8 +62,11 @@ export class Blog extends Component {
this.props.setSpinner(false);
} else if (diff.data.remove) {
if (diff.data.remove.post) {
// XX TODO
} else {
this.props.history.push("/~publish/recent");
}
}
}
@ -94,10 +85,16 @@ export class Blog extends Component {
? _.get(this.props, `pubs[${blogId}]`, false)
: _.get(this.props, `subs[${ship}][${blogId}]`, false);
if (!(blog) && (ship === window.ship)) {
this.setState({notFound: true});
return;
};
} else if (this.blog && !blog) {
this.props.history.push("/~publish/recent");
return;
}
this.blog = blog;
if (this.state.awaitingSubscribe && blog) {
this.setState({
@ -138,6 +135,8 @@ export class Blog extends Component {
this.props.api.bind(`/collection/${blogId}`, "PUT", ship, "publish",
this.handleEvent.bind(this),
this.handleError.bind(this));
} else {
this.blog = blog;
}
}
@ -228,6 +227,21 @@ export class Blog extends Component {
this.props.history.push("/~publish/recent");
}
viewSubs() {
console.log("view subs");
this.setState({view: 'subs'});
}
viewSettings() {
console.log("view settings");
this.setState({view: 'settings'});
}
viewNotes() {
console.log("view notes");
this.setState({view: 'notes'});
}
render() {
if (this.state.notFound) {
@ -239,50 +253,11 @@ export class Blog extends Component {
} else {
let data = this.buildData();
let posts = data.postProps.map((post, key) => {
return (
<PostPreview
post={post}
key={key}
/>
);
});
if ((posts.length === 0) && (this.props.ship === window.ship)) {
let link = {
pathname: "/~publish/new-post",
state: {
lastPath: this.props.location.pathname,
lastMatch: this.props.match.path,
lastParams: this.props.match.params,
}
}
posts.push(
<div key={0} className="w-336 relative">
<hr className="gray-10" style={{marginBottom:18}}/>
<Link to={link}>
<p className="body-large b">
-> Create First Post
</p>
</Link>
</div>
);
}
let contributors = `~${this.props.ship}`;
let create = (this.props.ship === window.ship);
let subscribers = 'None';
let subNum = _.get(data.blog, 'subscribers.length', 0);
if (subNum === 1) {
subscribers = `~${data.blog.subscribers[0]}`;
} else if (subNum === 2) {
subscribers = `~${data.blog.subscribers[0]} and 1 other`;
} else if (subNum > 2) {
subscribers = `~${data.blog.subscribers[0]} and ${subNum-1} others`;
}
let foreign = _.get(this.props,
`subs[${this.props.ship}][${this.props.blogId}]`, false);
@ -293,39 +268,102 @@ export class Blog extends Component {
actionType = 'unsubscribe';
}
let viewSubs = (this.props.ship === window.ship)
? this.viewSubs
: null;
let viewSettings = (this.props.ship === window.ship)
? this.viewSettings
: null;
if (this.state.view === 'notes') {
return (
<div>
<PC pathData={data.pathData} create={create}/>
<div className="absolute w-100"
style={{top:124, marginLeft: 16, marginRight: 16, marginTop: 32}}>
style={{top:124, paddingLeft: 16, paddingRight: 16, paddingTop: 32}}>
<div className="flex-col">
<h2>{data.blogTitle}</h2>
<h2 style={{wordBreak: "break-word"}}>
{data.blogTitle}
</h2>
<div className="flex" style={{marginTop: 22}}>
<div className="flex-col" style={{flexBasis: 160, marginRight:16}}>
<p className="gray-50 label-small-2 b">Host</p>
<p className="label-small-2">{data.blogHost}</p>
</div>
<div style={{flexBasis: 160, marginRight:16}}>
<p className="gray-50 label-small-2 b">Contributors</p>
<p className="label-small-2">{contributors}</p>
</div>
<div style={{flexBasis: 160, marginRight: 16}}>
<p className="gray-50 label-small-2 b">Subscribers</p>
<p className="label-small-2">{subscribers}</p>
<Subscribe actionType={actionType}
<BlogData
host={this.props.ship}
viewSubs={viewSubs}
subNum={subNum}
viewSettings={viewSettings}
subscribeAction={actionType}
subscribe={this.subscribe}
unsubscribe={this.unsubscribe}
/>
</div>
<BN ship={this.props.ship} posts={data.postProps} />
</div>
<div className="flex flex-wrap" style={{marginTop: 48}}>
{posts}
</div>
</div>
);
} else if (this.state.view === 'subs') {
let subscribers = _.get(data, 'blog.subscribers', []);
return (
<div>
<PC pathData={data.pathData} create={create}/>
<div className="absolute w-100"
style={{top:124, paddingLeft: 16, paddingRight: 16, paddingTop: 32}}>
<div className="flex-col">
<h2 style={{wordBreak: "break-word"}}>
{data.blogTitle}
</h2>
<div className="flex" style={{marginTop: 22}}>
<BlogData
host={this.props.ship}
viewSubs={viewSubs}
subNum={subNum}
viewSettings={viewSettings}
subscribeAction={actionType}
subscribe={this.subscribe}
unsubscribe={this.unsubscribe}
/>
</div>
<BlogSubs back={this.viewNotes}
subs={subscribers}
blogId={this.props.blogId}
title={data.blogTitle}
api={this.props.api}/>
</div>
</div>
</div>
);
} else if (this.state.view === 'settings') {
return (
<div>
<PC pathData={data.pathData} create={create}/>
<div className="absolute w-100"
style={{top:124, paddingLeft: 16, paddingRight: 16, paddingTop: 32}}>
<div className="flex-col">
<h2 style={{wordBreak: "break-word"}}>
{data.blogTitle}
</h2>
<div className="flex" style={{marginTop: 22}}>
<BlogData
host={this.props.ship}
viewSubs={viewSubs}
subNum={subNum}
viewSettings={viewSettings}
subscribeAction={actionType}
subscribe={this.subscribe}
unsubscribe={this.unsubscribe}
/>
</div>
<BS back={this.viewNotes}
blogId={this.props.blogId}
title={data.blogTitle}
api={this.props.api}/>
</div>
</div>
</div>
);
}
}
}
}

View File

@ -0,0 +1,90 @@
import React, { Component } from 'react';
import classnames from 'classnames';
class Subscribe extends Component {
constructor(props) {
super(props);
}
render() {
if (this.props.actionType === 'subscribe') {
return (
<p className="label-small b pointer"
onClick={this.props.subscribe}>
Subscribe
</p>
);
} else if (this.props.actionType === 'unsubscribe') {
return (
<p className="label-small b pointer"
onClick={this.props.unsubscribe}>
Unsubscribe
</p>
);
} else {
return null;
}
}
}
class Subscribers extends Component {
constructor(props) {
super(props);
}
render() {
let subscribers = (this.props.subNum === 1)
? `${this.props.subNum} Subscriber`
: `${this.props.subNum} Subscribers`;
if (this.props.action !== null) {
return (
<p className="label-small b pointer" onClick={this.props.action}>
{subscribers}
</p>
);
} else {
return (
<p className="label-small b">{subscribers}</p>
);
}
}
}
class Settings extends Component {
constructor(props) {
super(props);
}
render() {
if (this.props.action !== null) {
return (
<p className="label-small b pointer" onClick={this.props.action}>
Settings
</p>
);
} else {
return null;
}
}
}
export class BlogData extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="flex-col">
<p className="label-small">By ~{this.props.host}</p>
<Subscribers action={this.props.viewSubs} subNum={this.props.subNum}/>
<Settings action={this.props.viewSettings}/>
<Subscribe actionType={this.props.subscribeAction}
subscribe={this.props.subscribe}
unsubscribe={this.props.unsubscribe}
/>
</div>
);
}
}

View File

@ -0,0 +1,50 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { PostPreview } from '/components/lib/post-preview';
import { Link } from 'react-router-dom';
export class BlogNotes extends Component {
constructor(props) {
super(props);
}
render() {
if (!this.props.posts ||
((this.props.posts.length === 0) &&
(this.props.ship === window.ship))) {
let link = {
pathname: "/~publish/new-post",
state: {
lastPath: this.props.location.pathname,
lastMatch: this.props.match.path,
lastParams: this.props.match.params,
}
}
return (
<div className="flex flex-wrap">
<div className="w-336 relative">
<hr className="gray-10" style={{marginTop: 48, marginBottom:25}}/>
<Link to={link}>
<p className="body-large b">
-> Create First Post
</p>
</Link>
</div>
</div>
);
}
let posts = this.props.posts.map((post, key) => {
return (
<PostPreview post={post} key={key}/>
);
});
return (
<div className="flex flex-wrap" style={{marginTop: 48}}>
{posts}
</div>
);
}
}

View File

@ -0,0 +1,122 @@
import React, { Component } from 'react';
import classnames from 'classnames';
class SaveLink extends Component {
constructor(props) {
super(props);
}
render() {
if (this.props.enabled) {
return (
<button className="label-regular b"
onClick={this.props.action}>
-> Save
</button>
);
} else {
return (
<p className="label-regular b gray-50">
-> Save
</p>
);
}
}
}
export class BlogSettings extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
awaitingTitleChange: false,
}
this.rename = this.rename.bind(this);
this.titleChange = this.titleChange.bind(this);
this.deleteBlog = this.deleteBlog.bind(this);
}
rename() {
let edit = {
"edit-collection": {
name: this.props.blogId,
title: this.state.title,
}
}
this.setState({
awaitingTitleChange: true,
}, () => {
this.props.api.action("publish", "publish-action", edit);
});
}
titleChange(evt) {
this.setState({title: evt.target.value});
}
deleteBlog() {
let del = {
"delete-collection": {
coll: this.props.blogId,
}
}
this.props.api.action("publish", "publish-action", del);
this.props.history.push("/~publish/recent");
}
componentDidUpdate(prevProps) {
if (this.state.awaitingTitleChange) {
if (prevProps.title !== this.props.title){
this.titleInput.value = '';
this.setState({
awaitingTitleChange: false,
});
}
}
}
render() {
let back = '<- Back to notes'
let enableSave = ((this.state.title !== '') &&
(this.state.title !== this.props.title) &&
!this.state.awaitingTitleChange);
return (
<div className="flex-col mw-688" style={{marginTop:48}}>
<hr className="gray-30" style={{marginBottom:25}}/>
<p className="label-regular pointer b" onClick={this.props.back}>
{back}
</p>
<p className="body-large b" style={{marginTop:16, marginBottom: 20}}>
Settings
</p>
<div className="flex">
<div className="flex-col w-100">
<p className="body-regular-400">Delete Notebook</p>
<p className="gray-50 label-small-2" style={{marginTop:12, marginBottom:8}}>
Permanently delete this notebook
</p>
<button className="red label-regular b" onClick={this.deleteBlog}>
-> Delete
</button>
</div>
<div className="flex-col w-100">
<p className="body-regular-400">Rename</p>
<p className="gray-50 label-small-2" style={{marginTop:12, marginBottom:23}}>
Change the name of this notebook
</p>
<p className="label-small-2">Notebook Name</p>
<input className="body-regular-400 w-100"
ref={(el) => {this.titleInput = el}}
style={{marginBottom:8}}
placeholder={this.props.title}
onChange={this.titleChange}
disabled={this.state.awaitingTitleChange}/>
<SaveLink action={this.rename} enabled={enableSave}/>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,140 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import urbitOb from 'urbit-ob';
class InviteLink extends Component {
constructor(props) {
super(props);
}
render() {
if (this.props.enabled) {
return (
<button className="label-regular b underline"
onClick={this.props.action}>
Invite
</button>
);
} else {
return (
<p className="label-regular b underline gray-50">
Invite
</p>
);
}
}
}
export class BlogSubs extends Component {
constructor(props) {
super(props);
this.state = {
validInvites: false,
invites: [],
}
this.inviteHeight = 133;
this.invite = this.invite.bind(this);
this.inviteChange = this.inviteChange.bind(this);
}
inviteChange(evt) {
this.inviteInput.style.height = 'auto';
let newHeight = (this.inviteInput.scrollHeight < 133)
? 133 : this.inviteInput.scrollHeight + 2;
this.inviteInput.style.height = newHeight+'px';
this.inviteHeight = this.inviteInput.style.height;;
let tokens = evt.target.value
.trim()
.split(/[\s,]+/)
.map(t => t.trim());
let valid = tokens.reduce((valid, s) =>
valid && ((s !== '~') && urbitOb.isValidPatp(s) && s.includes('~')), true);
if (valid) {
this.setState({
validInvites: true,
invites: tokens.map(t => t.slice(1)),
});
} else {
this.setState({validInvites: false});
}
}
invite() {
if (this.inviteInput) this.inviteInput.value = '';
let invite = {
invite: {
coll: this.props.blogId,
title: this.props.title,
who: this.state.invites,
}
}
this.inviteHeight = 133;
this.setState({
validInvites: false,
invites: [],
}, () => {
this.props.api.action("publish", "publish-action", invite);
});
}
render() {
let back = '<- Back to notes'
let subscribers = this.props.subs.map((sub, i) => {
return (
<div className="flex w-100" key={i+1}>
<p className="label-regular-mono w-100">~{sub}</p>
</div>
);
});
subscribers.unshift(
<div className="flex w-100" key={0}>
<p className="label-regular-mono w-100">~{window.ship}</p>
<p className="label-regular-mono w-100">Host (You)</p>
</div>
);
return (
<div className="flex-col mw-688" style={{marginTop:48}}>
<hr className="gray-30" style={{marginBottom:25}}/>
<p className="label-regular pointer b" onClick={this.props.back}>
{back}
</p>
<p className="body-large b" style={{marginTop:16, marginBottom: 20}}>
Manage Notebook
</p>
<div className="flex">
<div className="flex-col w-100">
<p className="body-regular-400">Members</p>
<p className="gray-50 label-small-2"
style={{marginTop:12, marginBottom: 23}}>
Everyone subscribed to this notebook
</p>
{subscribers}
</div>
<div className="flex-col w-100">
<p className="body-regular-400">Invite</p>
<p className="gray-50 label-small-2"
style={{marginTop:12, marginBottom: 23}}>
Invite people to subscribe to this notebook
</p>
<textarea className="w-100 label-regular-mono overflow-y-hidden"
ref={(el) => {this.inviteInput = el}}
style={{resize:"none", marginBottom:8, height: this.inviteHeight}}
onChange={this.inviteChange}>
</textarea>
<InviteLink enabled={this.state.validInvites} action={this.invite}/>
</div>
</div>
</div>
);
}
}

View File

@ -23,16 +23,30 @@ class PostButton extends Component {
export class CommentBox extends Component {
constructor(props){
super(props);
this.commentChange = this.commentChange.bind(this);
this.commentHeight = 54;
}
componentDidUpdate(prevProps, prevState) {
if (!prevProps.enabled && this.props.enabled) {
if (this.textarea) {
this.textarea.value = '';
if (this.commentInput) {
this.commentInput.value = '';
this.commentInput.style.height = 54;
}
}
}
commentChange(evt) {
this.commentInput.style.height = 'auto';
let newHeight = (this.commentInput.scrollHeight < 54)
? 54 : this.commentInput.scrollHeight+2;
this.commentInput.style.height = newHeight+'px';
this.commentHeight = this.commentInput.style.height;
this.props.action(evt);
}
render() {
let textClass = (this.props.enabled)
? "body-regular-400 w-100"
@ -45,12 +59,12 @@ export class CommentBox extends Component {
</div>
<div className="flex-col w-100">
<textarea className={textClass}
ref={(el) => {this.textarea = el}}
style={{resize: "none"}}
ref={(el) => {this.commentInput = el}}
style={{resize: "none", height: this.commentHeight}}
type="text"
name="commentBody"
defaultValue=''
onChange={this.props.action}
onChange={this.commentChange}
disabled={(!this.props.enabled)}>
</textarea>
<PostButton

View File

@ -45,8 +45,9 @@ export class Comment extends Component {
</div>
<div className="flex-col fl">
<div className="label-small-mono gray-50">
<p className="fl" style={{width: 107}}>{this.props.ship}</p>
<p className="fl">{date}</p>
<p className="fl label-small-mono"
style={{width: 107}}>{this.props.ship}</p>
<p className="fl label-small-mono">{date}</p>
</div>
<div className="cb body-regular-400">
{body}

View File

@ -2,7 +2,6 @@ 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){

View File

@ -8,6 +8,20 @@ const PC = withRouter(PublishCreate);
export class HeaderMenu extends Component {
render () {
let recentText = (this.props.unread)
? <p className="label-regular">
<span className="green-medium body-large"></span>
<span>Recent</span>
</p>
: <p className="label-regular">Recent</p>;
let subsText = (this.props.invites)
? <p className="label-regular">
<span className="green-medium body-large"></span>
<span>Subscriptions</span>
</p>
: <p className="label-regular">Subscriptions</p>;
return (
<div className="fixed w-100 bg-white cf h-publish-header z-4"
style={{top:48}}>
@ -38,7 +52,7 @@ export class HeaderMenu extends Component {
borderColor: "black",
}}
style={{flexBasis:148}}>
Subscriptions
{subsText}
</NavLink>
<div className="fl bb b-gray-30 w-16" >
@ -52,7 +66,7 @@ export class HeaderMenu extends Component {
borderColor: "black",
}}
style={{flexBasis:148}}>
My Blogs
Notebooks
</NavLink>
<div className="fl bb b-gray-30 w-16" style={{flexGrow:1}}>

View File

@ -1,57 +0,0 @@
import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { HeaderMenu } from '/components/lib/header-menu';
import { PathControl } from '/components/lib/path-control';
import { Switch } from 'react-router';
export class Header extends Component {
constructor(props){
super(props);
}
render() {
return (
<div className="cf w-100 bg-white h-publish-header">
<Switch>
<Route exact path="/~publish/recent" component={HeaderMenu}/>
<Route exact path="/~publish/subs" component={HeaderMenu}/>
<Route exact path="/~publish/pubs" component={HeaderMenu}/>
<Route exact path="/~publish/new"
render={ (props) => {
return (
<PathControl {...this.props} {...props}/>
)
}}/>
<Route exact path="/~publish/new/blog"
render={ (props) => {
return (
<PathControl {...this.props} {...props}/>
)
}}/>
<Route exact path="/~publish/new/post"
render={ (props) => {
return (
<PathControl {...this.props} {...props}/>
)
}}/>
<Route exact path="/~publish/:ship/:blog"
render={ (props) => {
return (
<PathControl {...this.props} {...props}/>
)
}}/>
<Route exact path="/~publish/:ship/:blog/:post"
render={ (props) => {
return (
<PathControl {...this.props} {...props}/>
)
}}/>
</Switch>
</div>
);
}
}

View File

@ -3,6 +3,10 @@ import { sealDict } from '/components/lib/seal-dict';
export class Sigil extends Component {
constructor(props) {
super(props);
}
render() {
let prefix = this.props.prefix ? JSON.parse(this.props.prefix) : false;
@ -11,7 +15,7 @@ export class Sigil extends Component {
className="bg-black"
style={{ flexBasis: 35, padding: 4}}>
{
sealDict.getSeal(this.props.ship, this.props.size, prefix)
sealDict.getSeal(this.props.ship.slice(1), this.props.size, prefix)
}
</div>
);

View File

@ -39,7 +39,7 @@ export class PathControl extends Component {
if (this.props.location.pathname === '/~publish/new-blog') {
path.push(
{ text: 'New Blog', url: finalUrl }
{ text: 'New Notebook', url: finalUrl }
);
} else if (this.props.location.pathname === '/~publish/new-post') {
if (blog) {
@ -49,7 +49,7 @@ export class PathControl extends Component {
});
}
path.push(
{ text: 'New Post', url: finalUrl }
{ text: 'New Note', url: finalUrl }
);
}
return path;
@ -71,7 +71,7 @@ export class PathControl extends Component {
path.push(
<Link to={seg.url} key={key++}
className="fl gray-30 label-regular" style={style}>
className="fl gray-30 label-regular one-line mw-336" style={style}>
{seg.text}
</Link>
);

View File

@ -9,7 +9,7 @@ export class PostBody extends Component {
super(props)
}
renderA(what, node, attr) {
renderA(what, node, attr, parentNode) {
let aStyle = {
textDecorationLine: "underline",
wordWrap: "break-word"
@ -19,7 +19,7 @@ export class PostBody extends Component {
return item;
} else {
let newAttr = Object.assign({style: aStyle, key: key}, item.ga);
return this.parseContent(item.c, item.gn, newAttr);
return this.parseContent(item.c, item.gn, newAttr, node);
}
});
const element =
@ -28,26 +28,30 @@ export class PostBody extends Component {
}
renderIMG(what, node, attr) {
renderIMG(what, node, attr, parentNode) {
let imgStyle = {
width: "100%",
height: "auto"
height: "auto",
marginBottom: 12,
};
let newAttr = Object.assign({style: imgStyle}, attr);
const element = React.createElement(node, newAttr);
return element;
}
renderDefault(what, node, attr) {
renderP(what, node, attr, parentNode) {
let dStyle = {
wordWrap: "break-word"
wordWrap: "break-word",
};
if (parentNode !== 'li') {
dStyle.marginBottom = 12;
}
let children = what.map((item, key) => {
if (typeof(item) === 'string') {
return item;
} else {
let newAttr = Object.assign({key: key}, item.ga);
return this.parseContent(item.c, item.gn, newAttr);
return this.parseContent(item.c, item.gn, newAttr, node);
}
});
const element =
@ -55,20 +59,42 @@ export class PostBody extends Component {
return element;
}
parseContent(what, node, attr) {
renderDefault(what, node, attr, parentNode) {
let dStyle = {
wordWrap: "break-word",
};
let children = what.map((item, key) => {
if (typeof(item) === 'string') {
return item;
} else {
let newAttr = Object.assign({key: key}, item.ga);
return this.parseContent(item.c, item.gn, newAttr, node);
}
});
const element =
React.createElement(node, Object.assign({style: dStyle}, attr), children);
return element;
}
parseContent(what, node, attr, parentNode) {
switch (node) {
case "a":
return this.renderA(what, node, attr);
return this.renderA(what, node, attr, parentNode);
case "img":
return this.renderIMG(what, node, attr);
return this.renderIMG(what, node, attr, parentNode);
case "p":
return this.renderP(what, node, attr, parentNode);
default:
return this.renderDefault(what, node, attr);
return this.renderDefault(what, node, attr, parentNode);
}
}
render() {
let page = this.parseContent(this.props.body.c, this.props.body.gn, this.props.body.ga);
let page = this.parseContent(this.props.body.c,
this.props.body.gn,
this.props.body.ga,
null);
return page;
}
}

View File

@ -8,14 +8,15 @@ export class PostSnippet extends Component {
render() {
let elem = this.props.body.c.find((elem) => {
return elem.gn === "p" &&
typeof(elem.c[0]) === "string";
return (elem.gn === "p" && typeof(elem.c[0]) === "string");
});
let string = elem.c[0];
let string = (elem === undefined)
? null
: elem.c[0];
return (
<p className="body-regular-400 body-preview"
<p className="body-regular-400 five-lines"
style={{WebkitBoxOrient: "vertical"}}>
{string}
</p>

View File

@ -28,7 +28,7 @@ export class PublishCreate extends Component {
<div className="w-100">
<p className="publish">Publish</p>
<Link to={link}>
<p className="create">+New Blog</p>
<p className="create">+New Notebook</p>
</Link>
</div>
);
@ -45,7 +45,7 @@ export class PublishCreate extends Component {
<div className="w-100">
<p className="publish">Publish</p>
<Link to={link}>
<p className="create">+New Post</p>
<p className="create">+New Note</p>
</Link>
</div>
);

View File

@ -57,7 +57,8 @@ export class RecentPreview extends Component {
{comments}
</p>
<Link to={collLink}>
<p className="body-regular gray-50">
<p className="body-regular gray-50 one-line mw-336"
style={{WebkitBoxOrient: "vertical"}}>
{this.props.post.collectionTitle}
</p>
</Link>

View File

@ -8,7 +8,7 @@ export class TitleSnippet extends Component {
render() {
return (
<p className="body-large b title-preview"
<p className="body-large b two-lines"
style={{WebkitBoxOrient: "vertical"}}>
{this.props.title}
</p>

View File

@ -11,9 +11,9 @@ class FormLink extends Component {
render(props){
if (this.props.enabled) {
return (
<p className="body-large b z-2 pointer" onClick={this.props.action}>
<button className="body-large b z-2 pointer" onClick={this.props.action}>
{this.props.body}
</p>
</button>
);
}
return (
@ -184,16 +184,16 @@ export class NewBlog extends Component {
<FormLink
enabled={(this.state.title !== '')}
action={this.firstPost}
body={"-> Create a first post"}
action={this.addInvites}
body={"-> Send Invites"}
/>
<hr className="gray-30" style={{marginTop:32, marginBottom: 32}}/>
<FormLink
enabled={(this.state.title !== '')}
action={this.addInvites}
body={"-> Send Invites"}
action={this.firstPost}
body={"-> Create a first note"}
/>
<hr className="gray-30" style={{marginTop:32, marginBottom: 32}}/>
@ -228,7 +228,7 @@ export class NewBlog extends Component {
/>
<p className="body-regular-400" style={{marginTop:25, marginBottom:27}}>
Who is invited to read this blog?
Who is invited to read this notebook?
</p>
<input className={invitesStyle}
@ -244,7 +244,7 @@ export class NewBlog extends Component {
<FormLink
enabled={enableButtons}
action={this.firstPost}
body={"-> Save and create a first post"}
body={"-> Save and create a first note"}
/>
<hr className="gray-30" style={{marginTop:32, marginBottom: 32}}/>

View File

@ -23,7 +23,7 @@ class SideTab extends Component {
-> Post
</p>
<p className="pointer" onClick={this.props.discardPost}>
Discard post
Discard note
</p>
</div>
);

View File

@ -410,7 +410,7 @@ export class Post extends Component {
<div className="absolute w-100" style={{top:124}}>
<div className="mw-688 center mt4 flex-col" style={{flexBasis: 688}}>
<Link to={blogLink}>
<p className="body-regular">
<p className="body-regular one-line mw-688">
{blogLinkText}
</p>
</Link>

View File

@ -58,9 +58,11 @@ export class Pubs extends Component {
let cls = "w-100 flex " + bg;
return (
<div className={cls} key={i}>
<div className="fl body-regular-400" style={{flexBasis: 336}}>
<div className="fl body-regular-400 mw-336 w-336 pr3">
<Link to={data.url}>
<p className="one-line mw-336">
{data.title}
</p>
</Link>
</div>
<p className="fl body-regular-400" style={{flexBasis:336}}>
@ -73,16 +75,18 @@ export class Pubs extends Component {
);
});
let invites = (this.props.invites.length > 0);
let unread = (this.props.unread.length > 0);
return (
<div>
<HM/>
<HM invites={invites} unread={unread}/>
<div className="absolute w-100" style={{top:124}}>
<div className="flex-col">
<div className="w-100">
<h2 className="gray-50"
style={{marginLeft: 16, marginTop:32, marginBottom: 16}}>
My Blogs
Notebooks
</h2>
</div>
<div className="w-100 flex">

View File

@ -154,9 +154,12 @@ export class Recent extends Component {
);
});
let invites = (this.props.invites.length > 0);
let unread = (this.props.unread.length > 0);
return (
<div>
<HM/>
<HM invites={invites} unread={unread}/>
<div className="absolute w-100"
style={{top:124, marginLeft: 16, marginRight: 16, marginTop: 32}}>
<div className="flex-col">

View File

@ -1,7 +1,5 @@
import React, { Component } from 'react';
import classnames from 'classnames';
import { Header } from '/components/lib/header';
import { HeaderBar } from '/components/lib/header-bar';

View File

@ -108,9 +108,11 @@ export class Subs extends Component {
if (data.type === 'regular') {
return (
<div className={cls} key={i}>
<div className="fl body-regular-400" style={{flexBasis: 336}}>
<div className="fl mw-336" style={{flexBasis: 336}}>
<Link to={data.url}>
<p className="body-regular-400 one-line pr3">
{data.title}
</p>
</Link>
</div>
<p className="fl body-regular-400" style={{flexBasis:336}}>
@ -131,9 +133,13 @@ export class Subs extends Component {
<div className={cls} key={i}>
<div className="fl body-regular-400" style={{flexBasis: 336}}>
<Link to={data.url}>
<div className="mw-336 one-line pr3">
<span className="body-large green-medium"></span>
<span className="body-regular-400">Invite to</span>
<span className="body-regular">{data.title}</span>
<span className="body-regular">
{data.title}
</span>
</div>
</Link>
</div>
<p className="fl body-regular-400" style={{flexBasis:336}}>
@ -157,10 +163,12 @@ export class Subs extends Component {
}
});
let invites = (this.props.invites.length > 0);
let unread = (this.props.unread.length > 0);
return (
<div>
<HM/>
<HM invites={invites} unread={unread}/>
<div className="absolute w-100" style={{top:124}}>
<div className="flex-col">
<div className="w-100">

View File

@ -23,6 +23,7 @@ export class RumorReducer {
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({
@ -32,6 +33,7 @@ export class RumorReducer {
}, state);
});
delete state.pubs[json.coll];
}
} else {
if (json.post) {
@ -99,11 +101,19 @@ export class RumorReducer {
reduceCollection(json, state) {
if (json.who === window.ship) {
if (state.pubs[json.coll]) {
state.pubs[json.coll].info = json.data;
} else {
state.pubs[json.coll] = {
info: json.data,
order: { pin: [], unpin: [] },
posts: {},
}
}
} else {
if (state.subs[json.who]) {
if (state.subs[json.who][json.coll]) {
state.subs[json.who][json.coll].info = json.data;
} else {
state.subs[json.who][json.coll] = {
info: json.data,
@ -111,6 +121,16 @@ export class RumorReducer {
posts: {},
}
}
} else {
state.subs[json.who] = {
[json.coll]: {
info: json.data,
order: { pin: [], unpin: [] },
posts: {},
}
}
}
}
}
reducePost(json, state) {

View File

@ -348,7 +348,9 @@
[~ da-this]
=. subs.sat (~(del by subs.sat) who.del col.del)
:- ~(tap in ~(key by pos.u.old))
(da-emit [ost.bol %pull /collection/[col.del] [who.del %publish] ~])
%- da-emil
:- [ost.bol %pull /collection/[col.del] [who.del %publish] ~]
(affection-primary del)
:: iterate through post ids collected before, removing each from
:: secondary indices in state
::
@ -382,7 +384,8 @@
=. da-this (da-remove who.del col.del u.pos.del)
(da-emil (affection del))
=. subs.sat (~(put by subs.sat) [who.del col.del] new)
(da-remove who.del col.del u.pos.del)
=. da-this (da-remove who.del col.del u.pos.del)
(da-emil (affection-primary del))
::
==
::
@ -527,6 +530,15 @@
|= del=delta
^- (quip move _this)
da-done:(da-change:da del)
:: +affection: rumors to primary
::
++ affection-primary
|= del=delta
^- (list move)
%+ turn (prey:pubsub:userlib /primary bol)
|= [b=bone *]
^- move
[b %diff %publish-rumor del]
:: +affection: rumors to interested
::
++ affection
@ -1023,7 +1035,15 @@
%edit-collection
?. =(src.bol our.bol)
[~ this]
=/ pax=path /web/publish/[name.act]/publish-info
=/ col=(unit collection) (~(get by pubs.sat) name.act)
?~ col
[~ this]
?: ?=(%.n -.dat.col.u.col)
[~ this]
=/ out=collection-info p.dat.col.u.col(title title.act)
:_ this
[(write-file pax %publish-info !>(out))]~
::
%edit-post
?. =(who.act our.bol)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -73,7 +73,10 @@
content+so:dejs
==
::
++ delete-collection (of:dejs coll+(su:dejs sym) ~)
++ delete-collection
%- ot:dejs
:~ coll+(su:dejs sym)
==
::
++ delete-post
%- ot:dejs
@ -92,9 +95,6 @@
%- ot:dejs
:~ name+(su:dejs sym)
title+so:dejs
comments+comment-config
allow-edit+edit-config
perm+perm-config
==
::
++ edit-post

View File

@ -8,7 +8,7 @@
%+ sort ~(val by comments)
|= [a=comment:publish b=comment:publish]
^- ?
(lte date-created.info.a date-created.info.b)
(gte date-created.info.a date-created.info.b)
::
/_ /publish-comment/
result

View File

@ -25,13 +25,7 @@
[%delete-post coll=@tas post=@tas]
[%delete-comment coll=@tas post=@tas comment=@tas]
::
$: %edit-collection
name=@tas
title=@t
com=comment-config
edit=edit-config
perm=perm-config
==
[%edit-collection name=@tas title=@t]
::
$: %edit-post
who=@p