feat: Allow Google Embeds from regular (non publish-to-web) links (#1533)

Improve styling to allow getting back to source document
This commit is contained in:
Tom Moor 2020-09-15 18:38:42 -07:00 committed by GitHub
parent b3b71d2dc7
commit ac8f0ebaac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 118 additions and 31 deletions

View File

@ -2,9 +2,7 @@
import * as React from "react";
import Frame from "./components/Frame";
const URL_REGEX = new RegExp(
"^https?://docs.google.com/document/d/(.*)/pub(.*)$"
);
const URL_REGEX = new RegExp("^https?://docs.google.com/document/(.*)$");
type Props = {|
attrs: {|
@ -18,7 +16,20 @@ export default class GoogleDocs extends React.Component<Props> {
render() {
return (
<Frame src={this.props.attrs.href} title="Google Docs Embed" border />
<Frame
src={this.props.attrs.href.replace("/edit", "/preview")}
icon={
<img
src="/images/google-docs.png"
alt="Google Docs Icon"
width={16}
height={16}
/>
}
canonicalUrl={this.props.attrs.href}
title="Google Docs"
border
/>
);
}
}

View File

@ -14,14 +14,19 @@ describe("GoogleDocs", () => {
match
)
).toBeTruthy();
expect(
"https://docs.google.com/document/d/1SsDfWzFFTjZM2LanvpyUzjKhqVQpwpTMeiPeYxhVqOg/edit".match(
match
)
).toBeTruthy();
expect(
"https://docs.google.com/document/d/1SsDfWzFFTjZM2LanvpyUzjKhqVQpwpTMeiPeYxhVqOg/preview".match(
match
)
).toBeTruthy();
});
test("to not be enabled elsewhere", () => {
expect(
"https://docs.google.com/document/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigR/edit".match(
match
)
).toBe(null);
expect("https://docs.google.com/document".match(match)).toBe(null);
expect("https://docs.google.com".match(match)).toBe(null);
expect("https://www.google.com".match(match)).toBe(null);

View File

@ -2,9 +2,7 @@
import * as React from "react";
import Frame from "./components/Frame";
const URL_REGEX = new RegExp(
"^https?://docs.google.com/spreadsheets/d/(.*)/pub(.*)$"
);
const URL_REGEX = new RegExp("^https?://docs.google.com/spreadsheets/d/(.*)$");
type Props = {|
attrs: {|
@ -18,7 +16,20 @@ export default class GoogleSlides extends React.Component<Props> {
render() {
return (
<Frame src={this.props.attrs.href} title="Google Sheets Embed" border />
<Frame
src={this.props.attrs.href.replace("/edit", "/preview")}
icon={
<img
src="/images/google-sheets.png"
alt="Google Sheets Icon"
width={16}
height={16}
/>
}
canonicalUrl={this.props.attrs.href}
title="Google Sheets"
border
/>
);
}
}

View File

@ -9,14 +9,14 @@ describe("GoogleSheets", () => {
match
)
).toBeTruthy();
});
test("to not be enabled elsewhere", () => {
expect(
"https://docs.google.com/spreadsheets/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigR/edit".match(
match
)
).toBe(null);
).toBeTruthy();
});
test("to not be enabled elsewhere", () => {
expect("https://docs.google.com/spreadsheets".match(match)).toBe(null);
expect("https://docs.google.com".match(match)).toBe(null);
expect("https://www.google.com".match(match)).toBe(null);

View File

@ -2,9 +2,7 @@
import * as React from "react";
import Frame from "./components/Frame";
const URL_REGEX = new RegExp(
"^https?://docs.google.com/presentation/d/(.*)/pub(.*)$"
);
const URL_REGEX = new RegExp("^https?://docs.google.com/presentation/d/(.*)$");
type Props = {|
attrs: {|
@ -19,8 +17,19 @@ export default class GoogleSlides extends React.Component<Props> {
render() {
return (
<Frame
src={this.props.attrs.href.replace("/pub", "/embed")}
title="Google Slides Embed"
src={this.props.attrs.href
.replace("/edit", "/preview")
.replace("/pub", "/embed")}
icon={
<img
src="/images/google-slides.png"
alt="Google Slides Icon"
width={16}
height={16}
/>
}
canonicalUrl={this.props.attrs.href}
title="Google Slides"
border
/>
);

View File

@ -14,14 +14,14 @@ describe("GoogleSlides", () => {
match
)
).toBeTruthy();
});
test("to not be enabled elsewhere", () => {
expect(
"https://docs.google.com/presentation/d/e/2PACX-1vTdddHPoZ5M_47wmSHCoigR/edit".match(
match
)
).toBe(null);
).toBeTruthy();
});
test("to not be enabled elsewhere", () => {
expect("https://docs.google.com/presentation".match(match)).toBe(null);
expect("https://docs.google.com".match(match)).toBe(null);
expect("https://www.google.com".match(match)).toBe(null);

View File

@ -1,12 +1,17 @@
// @flow
import { observable } from "mobx";
import { observer } from "mobx-react";
import { OpenIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import Flex from "components/Flex";
type Props = {
src?: string,
border?: boolean,
title?: string,
icon?: React.Node,
canonicalUrl?: string,
width?: string,
height?: string,
};
@ -40,15 +45,20 @@ class Frame extends React.Component<PropsWithRef> {
width = "100%",
height = "400px",
forwardedRef,
icon,
title,
canonicalUrl,
...rest
} = this.props;
const Component = border ? StyledIframe : "iframe";
const withBar = !!(icon || canonicalUrl);
return (
<Rounded width={width} height={height}>
<Rounded width={width} height={height} withBar={withBar}>
{this.isLoaded && (
<Component
ref={forwardedRef}
withBar={withBar}
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
width={width}
height={height}
@ -60,16 +70,56 @@ class Frame extends React.Component<PropsWithRef> {
{...rest}
/>
)}
{withBar && (
<Bar align="center">
{icon} <Title>{title}</Title>
{canonicalUrl && (
<Open
href={canonicalUrl}
target="_blank"
rel="noopener noreferrer"
>
<OpenIcon color="currentColor" size={18} /> Open
</Open>
)}
</Bar>
)}
</Rounded>
);
}
}
const Rounded = styled.div`
border-radius: 3px;
border-radius: ${(props) => (props.withBar ? "3px 3px 0 0" : "3px")};
overflow: hidden;
width: ${(props) => props.width};
height: ${(props) => props.height};
height: ${(props) => (props.withBar ? props.height + 28 : props.height)};
`;
const Open = styled.a`
color: ${(props) => props.theme.textSecondary} !important;
font-size: 13px;
font-weight: 500;
align-items: center;
display: flex;
position: absolute;
right: 0;
padding: 0 8px;
`;
const Title = styled.span`
font-size: 13px;
font-weight: 500;
padding-left: 4px;
`;
const Bar = styled(Flex)`
background: ${(props) => props.theme.secondaryBackground};
color: ${(props) => props.theme.textSecondary};
padding: 0 8px;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
user-select: none;
`;
// This wrapper allows us to pass non-standard HTML attributes through to the DOM element
@ -79,7 +129,8 @@ const Iframe = (props) => <iframe {...props} />;
const StyledIframe = styled(Iframe)`
border: 1px solid;
border-color: ${(props) => props.theme.embedBorder};
border-radius: 3px;
border-radius: ${(props) => (props.withBar ? "3px 3px 0 0" : "3px")};
display: block;
`;
export default React.forwardRef<Props, typeof Frame>((props, ref) => (

View File

@ -159,7 +159,7 @@ export const light = {
quote: colors.slateLight,
codeBackground: colors.smoke,
codeBorder: colors.smokeDark,
embedBorder: "#DDD #DDD #CCC",
embedBorder: colors.slateLight,
horizontalRule: colors.smokeDark,
noticeInfoBackground: colors.warmGrey,