mirror of
https://github.com/urbit/shrub.git
synced 2024-11-27 18:34:48 +03:00
link: support loading individual submissions
On the frontend, updates the route path to include the (base64-encoded) url. Uses that and the load-single functionality to support loading directly into a submission page, which fetches just the requested submission. Also ensures we don't open duplicate comment subscriptions.
This commit is contained in:
parent
9727fab259
commit
ab21f67ba6
@ -7,6 +7,7 @@
|
||||
:: /json/[p]/submissions pages for all groups
|
||||
:: /json/[p]/submissions/[some-group] page for one group
|
||||
:: /json/[p]/discussions/[wood-url]/[some-group] page for url in group
|
||||
:: /json/[n]/submission/[wood-url]/[some-group] nth matching submission
|
||||
::
|
||||
/+ *link, *server, default-agent, verb
|
||||
::
|
||||
@ -83,6 +84,10 @@
|
||||
[%submissions ^]
|
||||
:_ this
|
||||
(give-initial-submissions:do p t.t.t.path)
|
||||
::
|
||||
[%submission @ ^]
|
||||
:_ this
|
||||
(give-specific-submission:do p (break-discussion-path t.t.t.path))
|
||||
::
|
||||
[%discussions @ ^]
|
||||
:_ this
|
||||
@ -258,6 +263,28 @@
|
||||
:- %discussions
|
||||
(build-discussion-path path url.submission)
|
||||
::
|
||||
++ give-specific-submission
|
||||
|= [n=@ud =path =url]
|
||||
:_ [%give %kick ~ ~]~
|
||||
=; =json
|
||||
[%give %fact ~ %json !>(json)]
|
||||
%+ frond:enjs:format 'submission'
|
||||
^- json
|
||||
=; sub=(unit submission)
|
||||
?~ sub ~
|
||||
(submission:en-json u.sub)
|
||||
=/ =submissions
|
||||
=- (~(got by -) path)
|
||||
%+ scry-for (map ^path submissions)
|
||||
[%submissions path]
|
||||
|-
|
||||
?~ submissions ~
|
||||
=* sub i.submissions
|
||||
?. =(url.sub url)
|
||||
$(submissions t.submissions)
|
||||
?: =(0 n) `sub
|
||||
$(n (dec n), submissions t.submissions)
|
||||
::
|
||||
++ give-initial-discussions
|
||||
|= [p=@ud =path =url]
|
||||
^- (list card)
|
||||
|
@ -96,8 +96,81 @@ class UrbitApi {
|
||||
}
|
||||
|
||||
getCommentsPage(path, url, page) {
|
||||
//TODO factor out
|
||||
// encode the url into @ta-safe format, using logic from +wood
|
||||
const strictUrl = this.encodeUrl(url);
|
||||
const endpoint = '/json/' + page + '/discussions/' + strictUrl + path;
|
||||
this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
|
||||
(res) => {
|
||||
if (res.data['initial-discussions']) {
|
||||
// these aren't returned with the response,
|
||||
// so this ensures the reducers know them.
|
||||
res.data['initial-discussions'].path = path;
|
||||
res.data['initial-discussions'].url = url;
|
||||
}
|
||||
store.handleEvent(res);
|
||||
},
|
||||
console.error,
|
||||
()=>{} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
getPage(path, page) {
|
||||
const endpoint = '/json/' + page + '/submissions' + path;
|
||||
this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
|
||||
(dat)=>{store.handleEvent(dat)},
|
||||
console.error,
|
||||
()=>{} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
getSubmission(path, url, callback) {
|
||||
const strictUrl = this.encodeUrl(url);
|
||||
const endpoint = '/json/0/submission/' + strictUrl + path;
|
||||
this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
|
||||
(res) => {
|
||||
if (res.data.submission) {
|
||||
callback(res.data.submission)
|
||||
} else {
|
||||
console.error('unexpected submission response', res);
|
||||
}
|
||||
},
|
||||
console.error,
|
||||
()=>{} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
linkAction(data) {
|
||||
return this.action("link-store", "link-action", data);
|
||||
}
|
||||
|
||||
postLink(path, url, title) {
|
||||
return this.linkAction({
|
||||
'save': { path, url, title }
|
||||
});
|
||||
}
|
||||
|
||||
postComment(path, url, comment) {
|
||||
return this.linkAction({
|
||||
'note': { path, url, udon: comment }
|
||||
});
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
'sidebarToggle': sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//TODO into lib?
|
||||
// encode the url into @ta-safe format, using logic from +wood
|
||||
encodeUrl(url) {
|
||||
let strictUrl = '';
|
||||
for (let i = 0; i < url.length; i++) {
|
||||
const char = url[i];
|
||||
@ -128,61 +201,7 @@ class UrbitApi {
|
||||
}
|
||||
strictUrl = strictUrl + add;
|
||||
}
|
||||
strictUrl = '~.' + strictUrl;
|
||||
|
||||
const endpoint = '/json/' + page + '/discussions/' + strictUrl + path;
|
||||
this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
|
||||
(res) => {
|
||||
if (res.data['initial-discussions']) {
|
||||
// these aren't returned with the response,
|
||||
// so this ensures the reducers know them.
|
||||
res.data['initial-discussions'].path = path;
|
||||
res.data['initial-discussions'].url = url;
|
||||
}
|
||||
store.handleEvent(res);
|
||||
},
|
||||
console.error,
|
||||
()=>{} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
getPage(path, page) {
|
||||
const endpoint = '/json/' + page + '/submissions' + path;
|
||||
this.bind.bind(this)(endpoint, 'PUT', this.authTokens.ship, 'link-view',
|
||||
(dat)=>{store.handleEvent(dat)},
|
||||
console.error,
|
||||
()=>{} // no-op on quit
|
||||
);
|
||||
}
|
||||
|
||||
linkAction(data) {
|
||||
return this.action("link-store", "link-action", data);
|
||||
}
|
||||
|
||||
postLink(path, url, title) {
|
||||
return this.linkAction({
|
||||
'save': { path, url, title }
|
||||
});
|
||||
}
|
||||
|
||||
postComment(path, url, comment, page, index) {
|
||||
return this.linkAction({
|
||||
'note': { path, url, udon: comment }
|
||||
});
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
'sidebarToggle': sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
return '~.' + strictUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export class CommentsPagination extends Component {
|
||||
? "dib"
|
||||
: "dn";
|
||||
|
||||
let encodedUrl = window.btoa(props.url);
|
||||
let popout = (props.popout) ? "/popout" : "";
|
||||
|
||||
return (
|
||||
@ -27,6 +28,7 @@ export class CommentsPagination extends Component {
|
||||
+ props.path
|
||||
+ "/" + props.linkPage
|
||||
+ "/" + props.linkIndex
|
||||
+ "/" + encodedUrl
|
||||
+ "/comments" + prevPage}>
|
||||
<- Previous Page
|
||||
</Link>
|
||||
@ -37,6 +39,7 @@ export class CommentsPagination extends Component {
|
||||
+ props.path
|
||||
+ "/" + props.linkPage
|
||||
+ "/" + props.linkIndex
|
||||
+ "/" + encodedUrl
|
||||
+ "/comments" + nextPage}>
|
||||
Next Page ->
|
||||
</Link>
|
||||
|
@ -9,11 +9,8 @@ export class Comments extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
let page = "page" + this.props.commentPage;
|
||||
let comments = !!this.props.comments;
|
||||
if ((page !== "page0") &&
|
||||
(!comments || !this.props.comments[page]) &&
|
||||
(this.props.path && this.props.url)
|
||||
) {
|
||||
if (!this.props.comments || !this.props.comments[page]) {
|
||||
this.setState({requested: this.props.commentPage});
|
||||
api.getCommentsPage(
|
||||
this.props.path,
|
||||
this.props.url,
|
||||
@ -22,15 +19,16 @@ export class Comments extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
let page = "page" + this.props.commentPage;
|
||||
if (prevProps !== this.props) {
|
||||
if (!!this.props.comments) {
|
||||
if ((page !== "page0") && !this.props.comments[page] && this.props.url) {
|
||||
api.getCommentsPage(
|
||||
this.props.path,
|
||||
this.props.url,
|
||||
this.props.commentPage);
|
||||
}
|
||||
let page = "page" + this.props.commentPage;
|
||||
if ( (!this.props.comments || !this.props.comments[page]) &&
|
||||
(this.state.requested !== this.props.commentPage)) {
|
||||
this.setState({requested: this.props.commentPage});
|
||||
api.getCommentsPage(
|
||||
this.props.path,
|
||||
this.props.url,
|
||||
this.props.commentPage
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,6 +91,7 @@ export class Comments extends Component {
|
||||
popout={props.popout}
|
||||
linkPage={props.linkPage}
|
||||
linkIndex={props.linkIndex}
|
||||
url={props.url}
|
||||
commentPage={props.commentPage}
|
||||
total={total}/>
|
||||
</div>
|
||||
|
@ -45,6 +45,8 @@ export class LinkItem extends Component {
|
||||
hostname = hostname[4];
|
||||
}
|
||||
|
||||
let encodedUrl = window.btoa(props.url);
|
||||
|
||||
let comments = props.comments + " comment" + ((props.comments === 1) ? "" : "s");
|
||||
|
||||
return (
|
||||
@ -68,7 +70,7 @@ export class LinkItem extends Component {
|
||||
: "~" + props.ship}</span>
|
||||
<span className="f9 inter gray2 pr3 v-mid">{this.state.timeSinceLinkPost}</span>
|
||||
<Link to=
|
||||
{"/~link" + props.popout + "/" + props.channel + "/" + props.page + "/" + props.index}
|
||||
{"/~link" + props.popout + "/" + props.channel + "/" + props.page + "/" + props.index + "/" + encodedUrl}
|
||||
className="v-top">
|
||||
<span className="f9 inter gray2">
|
||||
{comments}
|
||||
|
@ -13,21 +13,21 @@ export class LinkDetail extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
timeSinceLinkPost: this.getTimeSinceLinkPost(),
|
||||
comment: ""
|
||||
comment: "",
|
||||
data: props.data
|
||||
};
|
||||
|
||||
this.setComment = this.setComment.bind(this);
|
||||
}
|
||||
|
||||
updateData(submission) {
|
||||
this.setState({data: submission});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// if we have no preloaded data, and we aren't expecting it, get it
|
||||
if (this.props.page != 0 && (!this.props.data || !this.props.data.url)) {
|
||||
api.getPage(this.props.path, this.props.page);
|
||||
|
||||
// if we have preloaded our data,
|
||||
// but no comments, grab the comments
|
||||
} else if (!this.props.comments && this.props.data.url) {
|
||||
api.getCommentsPage(this.props.path, this.props.data.url, this.props.commentPage);
|
||||
if (!this.props.data.url || !this.props.url) {
|
||||
api.getSubmission(this.props.path, this.props.url, this.updateData.bind(this));
|
||||
}
|
||||
|
||||
this.updateTimeSinceNewestMessageInterval = setInterval( () => {
|
||||
@ -36,17 +36,13 @@ export class LinkDetail extends Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// if we came to this page *directly*,
|
||||
// load the comments -- DidMount will fail
|
||||
if ( (this.props.data.url !== prevProps.data.url) &&
|
||||
(!this.props.comments && this.props.data.url)
|
||||
) {
|
||||
api.getCommentsPage(this.props.path, this.props.data.url, this.props.commentPage);
|
||||
}
|
||||
|
||||
if (this.props.data.timestamp !== prevProps.data.timestamp) {
|
||||
this.setState({timeSinceLinkPost: this.getTimeSinceLinkPost()})
|
||||
}
|
||||
|
||||
if (this.props.url !== prevProps.url) {
|
||||
this.setState({data: this.props.data});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -63,15 +59,13 @@ export class LinkDetail extends Component {
|
||||
}
|
||||
|
||||
onClickPost() {
|
||||
let url = this.props.data.url || "";
|
||||
let url = this.props.url || "";
|
||||
|
||||
let request = api.postComment(
|
||||
this.props.path,
|
||||
url,
|
||||
this.state.comment,
|
||||
this.props.page,
|
||||
this.props.link
|
||||
);
|
||||
this.state.comment
|
||||
);
|
||||
|
||||
if (request) {
|
||||
this.setState({comment: ""})
|
||||
@ -87,9 +81,10 @@ export class LinkDetail extends Component {
|
||||
let popout = (props.popout) ? "/popout" : "";
|
||||
let path = props.path + "/" + props.page + "/" + props.link;
|
||||
|
||||
let ship = props.data.ship || "zod";
|
||||
let title = props.data.title || "";
|
||||
let url = props.data.url || "";
|
||||
const data = this.state.data || props.data;
|
||||
let ship = data.ship || "zod";
|
||||
let title = data.title || "";
|
||||
let url = data.url || "";
|
||||
|
||||
let URLparser = new RegExp(/((?:([\w\d\.-]+)\:\/\/?){1}(?:(www)\.?){0,1}(((?:[\w\d-]+\.)*)([\w\d-]+\.[\w\d]+))){1}(?:\:(\d+)){0,1}((\/(?:(?:[^\/\s\?]+\/)*))(?:([^\?\/\s#]+?(?:.[^\?\s]+){0,1}){0,1}(?:\?([^\s#]+)){0,1})){0,1}(?:#([^#\s]+)){0,1}/);
|
||||
|
||||
@ -99,18 +94,18 @@ export class LinkDetail extends Component {
|
||||
hostname = hostname[4];
|
||||
}
|
||||
|
||||
let commentCount = props.data.commentCount || 0;
|
||||
let commentCount = data.commentCount || 0;
|
||||
|
||||
let comments = commentCount + " comment" + ((commentCount === 1) ? "" : "s");
|
||||
|
||||
let nickname = !!props.members[props.data.ship]
|
||||
? props.members[props.data.ship].nickname
|
||||
let nickname = !!props.members[ship]
|
||||
? props.members[ship].nickname
|
||||
: "";
|
||||
|
||||
let nameClass = nickname ? "inter" : "mono";
|
||||
|
||||
let color = !!props.members[props.data.ship]
|
||||
? uxToHex(props.members[props.data.ship].color)
|
||||
let color = !!props.members[ship]
|
||||
? uxToHex(props.members[ship].color)
|
||||
: "000000";
|
||||
|
||||
let activeClasses = (this.state.comment)
|
||||
@ -198,7 +193,7 @@ export class LinkDetail extends Component {
|
||||
commentPage={props.commentPage}
|
||||
members={props.members}
|
||||
popout={props.popout}
|
||||
url={props.data.url}
|
||||
url={props.url}
|
||||
linkPage={props.page}
|
||||
linkIndex={props.link}
|
||||
/>
|
||||
|
@ -94,7 +94,7 @@ export class Root extends Component {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Route exact path="/~link/(popout)?/:ship/:channel/:page/:index/(comments)?/:commentpage?"
|
||||
<Route exact path="/~link/(popout)?/:ship/:channel/:page/:index/:encodedUrl/(comments)?/:commentpage?"
|
||||
render={ (props) => {
|
||||
let groupPath =
|
||||
`/${props.match.params.ship}/${props.match.params.channel}`;
|
||||
@ -105,6 +105,7 @@ export class Root extends Component {
|
||||
|
||||
let index = props.match.params.index || 0;
|
||||
let page = props.match.params.page || 0;
|
||||
let url = window.atob(props.match.params.encodedUrl);
|
||||
|
||||
let data = !!links[groupPath]
|
||||
? !!links[groupPath]["page" + page]
|
||||
@ -113,7 +114,7 @@ export class Root extends Component {
|
||||
: {};
|
||||
let coms = !comments[groupPath]
|
||||
? undefined
|
||||
: comments[groupPath][data.url];
|
||||
: comments[groupPath][url];
|
||||
|
||||
let commentPage = props.match.params.commentpage || 0;
|
||||
|
||||
@ -130,6 +131,7 @@ export class Root extends Component {
|
||||
<LinkDetail
|
||||
{...props}
|
||||
page={page}
|
||||
url={url}
|
||||
link={index}
|
||||
members={groupMembers}
|
||||
path={groupPath}
|
||||
|
Loading…
Reference in New Issue
Block a user