mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-23 14:07:20 +03:00
slates: deeplinking
This commit is contained in:
parent
3eca7e8946
commit
ab15d09572
@ -46,6 +46,13 @@ export const sendFilecoin = async (data) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getSlateBySlatename = async (data) => {
|
||||
return await returnJSON(`/api/search/slates/${data.query}`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
body: JSON.stringify({ data }),
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteTrustRelationship = async (data) => {
|
||||
return await returnJSON(`/api/users/trust-delete`, {
|
||||
...DEFAULT_OPTIONS,
|
||||
|
@ -35,9 +35,9 @@ export const getCIDGatewayURL = (cid) => {
|
||||
return `https://${cid}.${Constants.gateways.ipfs}`;
|
||||
};
|
||||
|
||||
export const createSlug = (text) => {
|
||||
export const createSlug = (text, base = "untitled") => {
|
||||
if (isEmpty(text)) {
|
||||
return "untitled";
|
||||
return base;
|
||||
}
|
||||
|
||||
const a = "æøåàáäâèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;";
|
||||
|
@ -1,3 +1,23 @@
|
||||
export const DeepLink = (props) => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
height={props.height}
|
||||
style={props.style}
|
||||
>
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const Directory = (props) => {
|
||||
return (
|
||||
<svg
|
||||
|
33
components/core/CircleButtonGray.js
Normal file
33
components/core/CircleButtonGray.js
Normal file
@ -0,0 +1,33 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/components/system/svg";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const STYLES_BUTTON = css`
|
||||
background-color: ${Constants.system.gray};
|
||||
color: ${Constants.system.pitchBlack};
|
||||
display: inline-flex;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 36px;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
transition: 100ms ease all;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
|
||||
:hover {
|
||||
background-color: ${Constants.system.brand};
|
||||
color: ${Constants.system.white};
|
||||
transform: scale(1.2);
|
||||
}
|
||||
`;
|
||||
|
||||
export default (props) => {
|
||||
return <span css={STYLES_BUTTON} {...props} />;
|
||||
};
|
@ -19,12 +19,6 @@ const STYLES_BUTTON = css`
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
/*
|
||||
:hover {
|
||||
color: ${Constants.system.white};
|
||||
background-color: ${Constants.system.brand};
|
||||
}
|
||||
*/
|
||||
`;
|
||||
|
||||
export default (props) => {
|
||||
|
@ -1,13 +1,15 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/components/system/svg";
|
||||
import * as Actions from "~/common/actions";
|
||||
import * as OldSVG from "~/common/svg";
|
||||
|
||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
||||
import { css } from "@emotion/react";
|
||||
import { LoaderSpinner } from "~/components/system/components/Loaders";
|
||||
|
||||
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
|
||||
import CircleButtonLight from "~/components/core/CircleButtonLight";
|
||||
import CircleButtonGray from "~/components/core/CircleButtonGray";
|
||||
|
||||
// NOTE(jim): I broke my own rules to do this. Sorry.
|
||||
const STYLES_ITEM = css`
|
||||
@ -22,6 +24,11 @@ const STYLES_ITEM = css`
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
img,
|
||||
article {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -42,10 +49,16 @@ const STYLES_BUTTON = css`
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: 200ms ease all;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const STYLES_ACTION_BUTTON = css`
|
||||
@ -128,6 +141,36 @@ export default class Slate extends React.Component {
|
||||
this.setState({ saving: "SAVED" });
|
||||
};
|
||||
|
||||
_handleSelect = (e, index) => {
|
||||
// TODO(jim): Test this again in React 17
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSelect(index);
|
||||
};
|
||||
|
||||
_handleDeepLink = async (e, object) => {
|
||||
// TODO(jim): Test this again in React 17
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const response = await Actions.getSlateBySlatename({
|
||||
query: object.deeplink,
|
||||
deeplink: true,
|
||||
});
|
||||
|
||||
if (!response.data) {
|
||||
alert("TODO: Can not find deeplink");
|
||||
}
|
||||
|
||||
if (!response.data.slate) {
|
||||
alert("TODO: Can not find deeplink");
|
||||
}
|
||||
|
||||
return window.open(
|
||||
`/${response.data.slate.user.username}/${response.data.slate.slatename}`
|
||||
);
|
||||
};
|
||||
|
||||
generateDOM = () => {
|
||||
return this.props.layouts.lg.map((each, index) => {
|
||||
const data = this.props.items[each.i];
|
||||
@ -138,13 +181,23 @@ export default class Slate extends React.Component {
|
||||
return (
|
||||
<div key={index} css={STYLES_ITEM}>
|
||||
<SlateMediaObjectPreview type={data.type} url={data.url} />
|
||||
<figure
|
||||
css={STYLES_BUTTON}
|
||||
onClick={() => this.props.onSelect(index)}
|
||||
>
|
||||
<CircleButtonLight>
|
||||
<figure css={STYLES_BUTTON}>
|
||||
<CircleButtonGray
|
||||
style={{ marginRight: 16 }}
|
||||
onMouseUp={(e) => this._handleSelect(e, index)}
|
||||
onTouchEnd={(e) => this._handleSelect(e, index)}
|
||||
>
|
||||
<SVG.Eye height="16px" />
|
||||
</CircleButtonLight>
|
||||
</CircleButtonGray>
|
||||
|
||||
{data.deeplink ? (
|
||||
<CircleButtonGray
|
||||
onMouseUp={(e) => this._handleDeepLink(e, data)}
|
||||
onTouchEnd={(e) => this._handleDeepLink(e, data)}
|
||||
>
|
||||
<OldSVG.DeepLink height="16px" />
|
||||
</CircleButtonGray>
|
||||
) : null}
|
||||
</figure>
|
||||
</div>
|
||||
);
|
||||
|
@ -8,6 +8,7 @@ const STYLES_IMAGE = css`
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
pointer-events: none;
|
||||
transition: 200ms ease all;
|
||||
`;
|
||||
|
||||
const STYLES_ENTITY = css`
|
||||
@ -29,22 +30,22 @@ export default class SlateMediaObjectPreview extends React.Component {
|
||||
// This is a hack to catch this undefined case I don't want to track down yet.
|
||||
const url = this.props.url.replace("https://undefined", "https://");
|
||||
|
||||
let element = <div css={STYLES_ENTITY}>No Preview</div>;
|
||||
let element = <article css={STYLES_ENTITY}>No Preview</article>;
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("video/")) {
|
||||
element = <div css={STYLES_ENTITY}>Video</div>;
|
||||
element = <article css={STYLES_ENTITY}>Video</article>;
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("audio/")) {
|
||||
element = <div css={STYLES_ENTITY}>Audio</div>;
|
||||
element = <article css={STYLES_ENTITY}>Audio</article>;
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("application/epub")) {
|
||||
element = <div css={STYLES_ENTITY}>EPub</div>;
|
||||
element = <article css={STYLES_ENTITY}>EPub</article>;
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("application/pdf")) {
|
||||
element = <div css={STYLES_ENTITY}>PDF</div>;
|
||||
element = <article css={STYLES_ENTITY}>PDF</article>;
|
||||
}
|
||||
|
||||
if (this.props.type && this.props.type.startsWith("image/")) {
|
||||
|
@ -143,12 +143,17 @@ export default class SlateMediaObjectSidebar extends React.Component {
|
||||
body: this.props.data.body ? this.props.data.body : "",
|
||||
source: this.props.data.source ? this.props.data.source : "",
|
||||
author: this.props.data.author ? this.props.data.author : "",
|
||||
deeplink: this.props.data.deeplink ? this.props.data.deeplink : "",
|
||||
};
|
||||
|
||||
_handleChange = (e) => {
|
||||
this.setState({ [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
_handleChangeDeepLink = (e) => {
|
||||
this.setState({ [e.target.name]: Strings.createSlug(e.target.value, "") });
|
||||
};
|
||||
|
||||
render() {
|
||||
const elements = [];
|
||||
|
||||
@ -193,7 +198,17 @@ export default class SlateMediaObjectSidebar extends React.Component {
|
||||
value={this.state.author}
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
</div>{" "}
|
||||
</div>
|
||||
<div
|
||||
css={STYLES_SIDEBAR_SECTION}
|
||||
style={{ borderTop: `1px solid #222222` }}
|
||||
>
|
||||
<SidebarInput
|
||||
name="deeplink"
|
||||
value={this.state.deeplink}
|
||||
onChange={this._handleChangeDeepLink}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
|
@ -53,8 +53,6 @@ const STYLES_STRONG = css`
|
||||
|
||||
export default class SidebarAddFileToBucket extends React.Component {
|
||||
_handleUpload = async (e) => {
|
||||
console.log("handle upload");
|
||||
|
||||
e.persist();
|
||||
let files = [];
|
||||
let fileLoading = {};
|
||||
@ -110,8 +108,6 @@ export default class SidebarAddFileToBucket extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("file upload");
|
||||
|
||||
await this.props.onRehydrate({ resetFiles: true });
|
||||
};
|
||||
|
||||
|
@ -20,6 +20,7 @@ export default async ({ query }) => {
|
||||
id: each.id,
|
||||
slatename: each.slatename,
|
||||
data: {
|
||||
ownerId: each.data.ownerId,
|
||||
name: each.data.name,
|
||||
body: each.data.body,
|
||||
objects: each.data.objects,
|
||||
|
58
pages/api/search/slates/[query].js
Normal file
58
pages/api/search/slates/[query].js
Normal file
@ -0,0 +1,58 @@
|
||||
import * as MW from "~/node_common/middleware";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Data from "~/node_common/data";
|
||||
|
||||
const initCORS = MW.init(MW.CORS);
|
||||
const initAuth = MW.init(MW.RequireCookieAuthentication);
|
||||
|
||||
export default async (req, res) => {
|
||||
initCORS(req, res);
|
||||
initAuth(req, res);
|
||||
|
||||
if (Strings.isEmpty(req.query.query)) {
|
||||
return {
|
||||
decorator: "SERVER_SEARCH_NO_QUERY",
|
||||
data: { results: [] },
|
||||
};
|
||||
}
|
||||
|
||||
const { query } = req.query;
|
||||
const slates = await Data.querySlates({ query });
|
||||
|
||||
if (req.body.data.deeplink) {
|
||||
if (slates.length) {
|
||||
const slate = { ...slates[0] };
|
||||
const user = await Data.getUserById({ id: slate.data.ownerId });
|
||||
|
||||
// NOTE(jim): I need to make sure that serializing user data is more
|
||||
// straightforward, because there is sensitive data that comes back
|
||||
// from this query.
|
||||
slate.user = {
|
||||
username: user.username,
|
||||
id: user.id,
|
||||
data: {
|
||||
name: user.data.name,
|
||||
body: user.data.body,
|
||||
photo: user.data.photo,
|
||||
},
|
||||
};
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_FIND_CLOSEST_LINK",
|
||||
data: { slate },
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(500).send({
|
||||
decorator: "SERVER_FIND_CLOSEST_LINK_ERROR",
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
|
||||
let results = [...slates];
|
||||
|
||||
return res.status(200).send({
|
||||
decorator: "SERVER_SEARCH_QUERY",
|
||||
data: { query, results },
|
||||
});
|
||||
};
|
@ -267,15 +267,19 @@ export default class SceneSlate extends React.Component {
|
||||
title={name}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<CircleButtonLight
|
||||
onClick={this._handleAdd}
|
||||
<CircleButtonGray
|
||||
onMouseUp={this._handleAdd}
|
||||
onTouchEnd={this._handleAdd}
|
||||
style={{ marginLeft: 12, marginRight: 12 }}
|
||||
>
|
||||
<SVG.Plus height="16px" />
|
||||
</CircleButtonLight>
|
||||
<CircleButtonLight onClick={this._handleShowSettings}>
|
||||
</CircleButtonGray>
|
||||
<CircleButtonGray
|
||||
onMouseUp={this._handleShowSettings}
|
||||
onTouchEnd={this._handleShowSettings}
|
||||
>
|
||||
<SVG.Settings height="16px" />
|
||||
</CircleButtonLight>
|
||||
</CircleButtonGray>
|
||||
</React.Fragment>
|
||||
}
|
||||
>
|
||||
|
Loading…
Reference in New Issue
Block a user