Share document link that opens full editor (#4134)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -46,7 +46,7 @@ const Contents = styled.div<{ $shrink?: boolean; $width?: number }>`
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: ${(props) => (props.$shrink ? "6px 0" : "12px 24px")};
|
padding: ${(props) => (props.$shrink ? "6px 0" : "12px 24px")};
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
box-shadow: ${(props) => props.theme.menuShadow};
|
box-shadow: ${(props) => props.theme.menuShadow};
|
||||||
width: ${(props) => props.$width}px;
|
width: ${(props) => props.$width}px;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { RouteComponentProps, useLocation } from "react-router-dom";
|
import { RouteComponentProps, useLocation, Redirect } from "react-router-dom";
|
||||||
import styled, { useTheme } from "styled-components";
|
import styled, { useTheme } from "styled-components";
|
||||||
import { setCookie } from "tiny-cookie";
|
import { setCookie } from "tiny-cookie";
|
||||||
import DocumentModel from "~/models/Document";
|
import DocumentModel from "~/models/Document";
|
||||||
@@ -12,6 +12,7 @@ import ErrorOffline from "~/scenes/ErrorOffline";
|
|||||||
import Layout from "~/components/Layout";
|
import Layout from "~/components/Layout";
|
||||||
import Sidebar from "~/components/Sidebar/Shared";
|
import Sidebar from "~/components/Sidebar/Shared";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
|
import usePolicy from "~/hooks/usePolicy";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import { NavigationNode } from "~/types";
|
import { NavigationNode } from "~/types";
|
||||||
import { AuthorizationError, OfflineError } from "~/utils/errors";
|
import { AuthorizationError, OfflineError } from "~/utils/errors";
|
||||||
@@ -78,12 +79,17 @@ function SharedDocumentScene(props: Props) {
|
|||||||
const { ui } = useStores();
|
const { ui } = useStores();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const searchParams = React.useMemo(
|
||||||
|
() => new URLSearchParams(location.search),
|
||||||
|
[location.search]
|
||||||
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [response, setResponse] = React.useState<Response>();
|
const [response, setResponse] = React.useState<Response>();
|
||||||
const [error, setError] = React.useState<Error | null | undefined>();
|
const [error, setError] = React.useState<Error | null | undefined>();
|
||||||
const { documents } = useStores();
|
const { documents } = useStores();
|
||||||
const { shareId, documentSlug } = props.match.params;
|
const { shareId, documentSlug } = props.match.params;
|
||||||
const documentId = useDocumentId(documentSlug, response);
|
const documentId = useDocumentId(documentSlug, response);
|
||||||
|
const can = usePolicy(response?.document.id ?? "");
|
||||||
|
|
||||||
// ensure the wider page color always matches the theme
|
// ensure the wider page color always matches the theme
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -140,6 +146,10 @@ function SharedDocumentScene(props: Props) {
|
|||||||
return <Loading location={props.location} />;
|
return <Loading location={props.location} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response && searchParams.get("edit") === "true" && can.update) {
|
||||||
|
return <Redirect to={response.document.url} />;
|
||||||
|
}
|
||||||
|
|
||||||
const sidebar = response.sharedTree ? (
|
const sidebar = response.sharedTree ? (
|
||||||
<Sidebar rootNode={response.sharedTree} shareId={shareId} />
|
<Sidebar rootNode={response.sharedTree} shareId={shareId} />
|
||||||
) : undefined;
|
) : undefined;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import invariant from "invariant";
|
import invariant from "invariant";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { GlobeIcon, PadlockIcon } from "outline-icons";
|
import { ExpandedIcon, GlobeIcon, PadlockIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation, Trans } from "react-i18next";
|
import { useTranslation, Trans } from "react-i18next";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
@@ -10,7 +10,6 @@ import Share from "~/models/Share";
|
|||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import CopyToClipboard from "~/components/CopyToClipboard";
|
import CopyToClipboard from "~/components/CopyToClipboard";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
import Input from "~/components/Input";
|
|
||||||
import Notice from "~/components/Notice";
|
import Notice from "~/components/Notice";
|
||||||
import Switch from "~/components/Switch";
|
import Switch from "~/components/Switch";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
@@ -42,6 +41,8 @@ function SharePopover({
|
|||||||
const { shares } = useStores();
|
const { shares } = useStores();
|
||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
const [isCopied, setIsCopied] = React.useState(false);
|
const [isCopied, setIsCopied] = React.useState(false);
|
||||||
|
const [expandedOptions, setExpandedOptions] = React.useState(false);
|
||||||
|
const [isEditMode, setIsEditMode] = React.useState(false);
|
||||||
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
|
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
|
||||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||||
const can = usePolicy(share ? share.id : "");
|
const can = usePolicy(share ? share.id : "");
|
||||||
@@ -56,6 +57,11 @@ function SharePopover({
|
|||||||
((share && share.published) ||
|
((share && share.published) ||
|
||||||
(sharedParent && sharedParent.published && !document.isDraft));
|
(sharedParent && sharedParent.published && !document.isDraft));
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!visible && expandedOptions) {
|
||||||
|
setExpandedOptions(false);
|
||||||
|
}
|
||||||
|
}, [visible]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
useKeyDown("Escape", onRequestClose);
|
useKeyDown("Escape", onRequestClose);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -116,9 +122,10 @@ function SharePopover({
|
|||||||
|
|
||||||
const userLocale = useUserLocale();
|
const userLocale = useUserLocale();
|
||||||
const locale = userLocale ? dateLocale(userLocale) : undefined;
|
const locale = userLocale ? dateLocale(userLocale) : undefined;
|
||||||
const shareUrl = team.sharing
|
let shareUrl = team.sharing ? share?.url ?? "" : `${team.url}${document.url}`;
|
||||||
? share?.url ?? ""
|
if (isEditMode) {
|
||||||
: `${team.url}${document.url}`;
|
shareUrl += "?edit=true";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -127,8 +134,8 @@ function SharePopover({
|
|||||||
<GlobeIcon size={28} color="currentColor" />
|
<GlobeIcon size={28} color="currentColor" />
|
||||||
) : (
|
) : (
|
||||||
<PadlockIcon size={28} color="currentColor" />
|
<PadlockIcon size={28} color="currentColor" />
|
||||||
)}{" "}
|
)}
|
||||||
{t("Share this document")}
|
<span>{t("Share this document")}</span>
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
{sharedParent && !document.isDraft && (
|
{sharedParent && !document.isDraft && (
|
||||||
@@ -196,19 +203,52 @@ function SharePopover({
|
|||||||
{share.includeChildDocuments
|
{share.includeChildDocuments
|
||||||
? t("Nested documents are publicly available")
|
? t("Nested documents are publicly available")
|
||||||
: t("Nested documents are not shared")}
|
: t("Nested documents are not shared")}
|
||||||
|
.
|
||||||
</SwitchText>
|
</SwitchText>
|
||||||
</SwitchLabel>
|
</SwitchLabel>
|
||||||
</SwitchWrapper>
|
</SwitchWrapper>
|
||||||
)}
|
)}
|
||||||
<Flex>
|
|
||||||
<InputLink
|
{expandedOptions && (
|
||||||
type="text"
|
<>
|
||||||
label={t("Link")}
|
<Separator />
|
||||||
placeholder={`${t("Loading")}…`}
|
<SwitchWrapper>
|
||||||
value={shareUrl}
|
<Switch
|
||||||
labelHidden
|
id="enableEditMode"
|
||||||
readOnly
|
label={t("Automatically redirect to the editor")}
|
||||||
/>
|
onChange={({ currentTarget: { checked } }) =>
|
||||||
|
setIsEditMode(checked)
|
||||||
|
}
|
||||||
|
checked={isEditMode}
|
||||||
|
disabled={!share}
|
||||||
|
/>
|
||||||
|
<SwitchLabel>
|
||||||
|
<SwitchText>
|
||||||
|
{isEditMode
|
||||||
|
? t(
|
||||||
|
"Users with edit permission will be redirected to the main app"
|
||||||
|
)
|
||||||
|
: t("All users see the same publicly shared view")}
|
||||||
|
.
|
||||||
|
</SwitchText>
|
||||||
|
</SwitchLabel>
|
||||||
|
</SwitchWrapper>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Flex justify="space-between" style={{ marginBottom: 8 }}>
|
||||||
|
{expandedOptions ? (
|
||||||
|
<span />
|
||||||
|
) : (
|
||||||
|
<MoreOptionsButton
|
||||||
|
icon={<ExpandedIcon />}
|
||||||
|
onClick={() => setExpandedOptions(true)}
|
||||||
|
neutral
|
||||||
|
borderOnHover
|
||||||
|
>
|
||||||
|
{t("More options")}
|
||||||
|
</MoreOptionsButton>
|
||||||
|
)}
|
||||||
<CopyToClipboard text={shareUrl} onCopy={handleCopied}>
|
<CopyToClipboard text={shareUrl} onCopy={handleCopied}>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -228,6 +268,9 @@ const Heading = styled.h2`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
/* accounts for icon padding */
|
||||||
margin-left: -4px;
|
margin-left: -4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -235,9 +278,17 @@ const SwitchWrapper = styled.div`
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const InputLink = styled(Input)`
|
const MoreOptionsButton = styled(Button)`
|
||||||
flex-grow: 1;
|
background: none;
|
||||||
margin-right: 8px;
|
font-size: 14px;
|
||||||
|
color: ${(props) => props.theme.textSecondary};
|
||||||
|
margin-left: -8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Separator = styled.div`
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: ${(props) => props.theme.divider};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SwitchLabel = styled(Flex)`
|
const SwitchLabel = styled(Flex)`
|
||||||
|
|||||||
@@ -449,6 +449,10 @@
|
|||||||
"Share nested documents": "Share nested documents",
|
"Share nested documents": "Share nested documents",
|
||||||
"Nested documents are publicly available": "Nested documents are publicly available",
|
"Nested documents are publicly available": "Nested documents are publicly available",
|
||||||
"Nested documents are not shared": "Nested documents are not shared",
|
"Nested documents are not shared": "Nested documents are not shared",
|
||||||
|
"Automatically redirect to the editor": "Automatically redirect to the editor",
|
||||||
|
"Users with edit permission will be redirected to the main app": "Users with edit permission will be redirected to the main app",
|
||||||
|
"All users see the same publicly shared view": "All users see the same publicly shared view",
|
||||||
|
"More options": "More options",
|
||||||
"{{ teamName }} is using Outline to share documents, please login to continue.": "{{ teamName }} is using Outline to share documents, please login to continue.",
|
"{{ teamName }} is using Outline to share documents, please login to continue.": "{{ teamName }} is using Outline to share documents, please login to continue.",
|
||||||
"Are you sure you want to delete the <em>{{ documentTitle }}</em> template?": "Are you sure you want to delete the <em>{{ documentTitle }}</em> template?",
|
"Are you sure you want to delete the <em>{{ documentTitle }}</em> template?": "Are you sure you want to delete the <em>{{ documentTitle }}</em> template?",
|
||||||
"Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history</em>.": "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history</em>.",
|
"Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history</em>.": "Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history</em>.",
|
||||||
|
|||||||
Reference in New Issue
Block a user