Compare commits

..

12 Commits

Author SHA1 Message Date
Tom Moor 7a988ed284 fix: Animation direction when screen placement causes context menu to be flipped 2021-07-12 12:46:20 -04:00
Tom Moor bab6ded8b6 Merge branch 'main' into feat/motion-design 2021-07-12 09:46:00 -04:00
Tom Moor 5cd4dbd9d7 fix: Mispositioned TOC control on mobile due to merge conflict
fix: Show message in mobile TOC when no headings in document
fix: MenuItem with level should still have background edge-to-edge
fix: Show developer warning when creating incorrect menu item type
2021-07-11 13:09:10 -04:00
Tom Moor 17f55cc140 feat: Improved UI motion design 2021-07-11 12:35:07 -04:00
Tom Moor 587a0e0517 chore: Update html import related deps 2021-07-11 10:02:35 -04:00
Tom Moor 686ecdfa92 fix: CSS syntax error 2021-07-09 14:09:52 -04:00
Translate-O-Tron bb019b081f New Crowdin updates (#2281) 2021-07-09 05:55:06 -07:00
Saumya Pandey 7d5fbeb7b0 fix: Add access to document TOC on mobile (#2279)
* Add TOC button for mobile

* Undo NewDocumentMenu changes

* Place the toc button in the correct position.

* Pass menu props to menuitem

* Update app/menus/TableOfContentsMenu.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Update app/menus/TableOfContentsMenu.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Use the existing prop type

* Write menu inside actions prop

* Prevent blank webpage behaviour for toc

* Use href instead of level to determine target

* Update app/scenes/Document/components/Header.js

Co-authored-by: Tom Moor <tom.moor@gmail.com>

* Add heading to menu items

* Use existing Heading component

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2021-07-09 04:50:27 -07:00
Tom Moor 056f89fcfd fix: Allow TOC to scroll when larger than browser height (#2296) 2021-07-09 04:07:28 -07:00
Tom Moor 0e7d352781 chore: Add fetch-retry, remove isomorphic-fetch (#2297)
* chore: Add fetch-retry, remove isomorphic-fetch

closes #2270

* test: Mock fetch
2021-07-09 04:07:18 -07:00
Tom Moor b5e4e4fe82 fix: Various mobile fixes (#2295)
* fix: Input placeholder ellipsis

* fix: Hide scrollbar on nav tabs on mobile

* fix: Header actions should be fixed on mobile

* fix: Add fade when content in tabs does not fit in available horizontal width
2021-07-08 18:32:14 -07:00
Tom Moor e41f17c701 feat: Enable Japanese translations (#2282) 2021-07-08 18:32:05 -07:00
60 changed files with 614 additions and 904 deletions
+2
View File
@@ -15,6 +15,7 @@ type Props = {|
target?: "_blank",
as?: string | React.ComponentType<*>,
hide?: () => void,
level?: number,
|};
const MenuItem = ({
@@ -88,6 +89,7 @@ export const MenuAnchor = styled.a`
margin: 0;
border: 0;
padding: 12px;
padding-left: ${(props) => 12 + props.level * 10}px;
width: 100%;
min-height: 32px;
background: none;
+9 -41
View File
@@ -9,49 +9,11 @@ import {
MenuItem as BaseMenuItem,
} from "reakit/Menu";
import styled from "styled-components";
import Header from "./Header";
import MenuItem, { MenuAnchor } from "./MenuItem";
import Separator from "./Separator";
import ContextMenu from ".";
type TMenuItem =
| {|
title: React.Node,
to: string,
visible?: boolean,
selected?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
onClick: (event: SyntheticEvent<>) => void | Promise<void>,
visible?: boolean,
selected?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
href: string,
visible?: boolean,
selected?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
visible?: boolean,
disabled?: boolean,
style?: Object,
hover?: boolean,
items: TMenuItem[],
|}
| {|
type: "separator",
visible?: boolean,
|}
| {|
type: "heading",
visible?: boolean,
title: React.Node,
|};
import { type MenuItem as TMenuItem } from "types";
type Props = {|
items: TMenuItem[],
@@ -128,7 +90,8 @@ function Template({ items, ...menu }: Props): React.Node {
key={index}
disabled={item.disabled}
selected={item.selected}
target="_blank"
level={item.level}
target={item.href.startsWith("#") ? undefined : "_blank"}
{...menu}
>
{item.title}
@@ -167,6 +130,11 @@ function Template({ items, ...menu }: Props): React.Node {
return <Separator key={index} />;
}
if (item.type === "heading") {
return <Header>{item.title}</Header>;
}
console.warn("Unrecognized menu item", item);
return null;
});
}
+27 -12
View File
@@ -6,14 +6,16 @@ import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import {
fadeIn,
fadeAndScaleIn,
fadeAndSlideIn,
fadeAndSlideUp,
fadeAndSlideDown,
mobileContextMenu,
} from "shared/styles/animations";
import usePrevious from "hooks/usePrevious";
type Props = {|
"aria-label": string,
visible?: boolean,
placement?: string,
animating?: boolean,
children: React.Node,
onOpen?: () => void,
@@ -44,13 +46,25 @@ export default function ContextMenu({
return (
<>
<Menu hideOnClickOutside preventBodyScroll {...rest}>
{(props) => (
<Position {...props}>
<Background dir="auto">
{rest.visible || rest.animating ? children : null}
</Background>
</Position>
)}
{(props) => {
// kind of hacky, but this is an effective way of telling which way
// the menu will _actually_ be placed when taking into account screen
// positioning.
const topAnchor = props.style.top === "0";
const rightAnchor = props.placement === "bottom-end";
return (
<Position {...props}>
<Background
dir="auto"
topAnchor={topAnchor}
rightAnchor={rightAnchor}
>
{rest.visible || rest.animating ? children : null}
</Background>
</Position>
);
}}
</Menu>
{(rest.visible || rest.animating) && (
<Portal>
@@ -91,7 +105,7 @@ const Position = styled.div`
`;
const Background = styled.div`
animation: ${fadeAndSlideIn} 200ms ease;
animation: ${mobileContextMenu} 200ms ease;
transform-origin: 50% 100%;
max-width: 100%;
background: ${(props) => props.theme.menuBackground};
@@ -109,9 +123,10 @@ const Background = styled.div`
}
${breakpoint("tablet")`
animation: ${fadeAndScaleIn} 200ms ease;
animation: ${(props) =>
props.topAnchor ? fadeAndSlideDown : fadeAndSlideUp} 200ms ease;
transform-origin: ${(props) =>
props.left !== undefined ? "25%" : "75%"} 0;
props.rightAnchor === "bottom-end" ? "75%" : "25%"} 0;
max-width: 276px;
background: ${(props) => props.theme.menuBackground};
box-shadow: ${(props) => props.theme.menuShadow};
+5 -2
View File
@@ -66,6 +66,9 @@ function DocumentListItem(props: Props, ref) {
!document.isDraft && !document.isArchived && !document.isTemplate;
const can = policies.abilities(currentTeam.id);
const handleMenuOpen = React.useCallback(() => setMenuOpen(true), []);
const handleMenuClosed = React.useCallback(() => setMenuOpen(false), []);
return (
<DocumentLink
ref={ref}
@@ -143,8 +146,8 @@ function DocumentListItem(props: Props, ref) {
<DocumentMenu
document={document}
showPin={showPin}
onOpen={() => setMenuOpen(true)}
onClose={() => setMenuOpen(false)}
onOpen={handleMenuOpen}
onClose={handleMenuClosed}
modal={false}
/>
</Actions>
+5 -1
View File
@@ -72,6 +72,10 @@ const Actions = styled(Flex)`
flex-basis: 0;
min-width: auto;
padding-left: 8px;
${breakpoint("tablet")`
position: unset;
`};
`;
const Wrapper = styled(Flex)`
@@ -84,12 +88,12 @@ const Wrapper = styled(Flex)`
transform: translate3d(0, 0, 0);
backdrop-filter: blur(20px);
min-height: 56px;
justify-content: flex-start;
@media print {
display: none;
}
justify-content: flex-start;
${breakpoint("tablet")`
padding: ${(props) => (props.isCompact ? "12px" : `24px 24px 0`)};
justify-content: "center";
+2 -2
View File
@@ -4,7 +4,7 @@ import { transparentize } from "polished";
import * as React from "react";
import { Portal } from "react-portal";
import styled from "styled-components";
import { fadeAndSlideIn } from "shared/styles/animations";
import { fadeAndSlideDown } from "shared/styles/animations";
import parseDocumentSlug from "shared/utils/parseDocumentSlug";
import DocumentsStore from "stores/DocumentsStore";
import HoverPreviewDocument from "components/HoverPreviewDocument";
@@ -136,7 +136,7 @@ function HoverPreview({ node, ...rest }: Props) {
}
const Animate = styled.div`
animation: ${fadeAndSlideIn} 150ms ease;
animation: ${fadeAndSlideDown} 150ms ease;
@media print {
display: none;
+4
View File
@@ -29,6 +29,10 @@ const RealInput = styled.input`
background: none;
color: ${(props) => props.theme.text};
height: 30px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:disabled,
&::placeholder {
+2
View File
@@ -6,6 +6,7 @@ import {
fr,
es,
it,
ja,
ko,
ptBR,
pt,
@@ -23,6 +24,7 @@ const locales = {
es_ES: es,
fr_FR: fr,
it_IT: it,
ja_JP: ja,
ko_KR: ko,
pt_BR: ptBR,
pt_PT: pt,
+21 -20
View File
@@ -1,5 +1,4 @@
// @flow
import { observer } from "mobx-react";
import * as React from "react";
import Document from "models/Document";
import DocumentListItem from "components/DocumentListItem";
@@ -19,24 +18,26 @@ type Props = {|
showTemplate?: boolean,
|};
@observer
class PaginatedDocumentList extends React.Component<Props> {
render() {
const { empty, heading, documents, fetch, options, ...rest } = this.props;
return (
<PaginatedList
items={documents}
empty={empty}
heading={heading}
fetch={fetch}
options={options}
renderItem={(item) => (
<DocumentListItem key={item.id} document={item} {...rest} />
)}
/>
);
}
}
const PaginatedDocumentList = React.memo<Props>(function PaginatedDocumentList({
empty,
heading,
documents,
fetch,
options,
...rest
}: Props) {
return (
<PaginatedList
items={documents}
empty={empty}
heading={heading}
fetch={fetch}
options={options}
renderItem={(item) => (
<DocumentListItem key={item.id} document={item} {...rest} />
)}
/>
);
});
export default PaginatedDocumentList;
+49 -8
View File
@@ -1,14 +1,26 @@
// @flow
import { m } from "framer-motion";
import * as React from "react";
import { NavLink } from "react-router-dom";
import { NavLink, Route } from "react-router-dom";
import styled, { withTheme } from "styled-components";
import { type Theme } from "types";
type Props = {
theme: Theme,
children: React.Node,
};
const TabLink = styled(NavLink)`
const NavLinkWithChildrenFunc = ({ to, exact = false, children, ...rest }) => (
<Route path={to} exact={exact}>
{({ match }) => (
<NavLink to={to} {...rest}>
{children(match)}
</NavLink>
)}
</Route>
);
const TabLink = styled(NavLinkWithChildrenFunc)`
position: relative;
display: inline-flex;
align-items: center;
@@ -20,19 +32,48 @@ const TabLink = styled(NavLink)`
&:hover {
color: ${(props) => props.theme.textSecondary};
border-bottom: 3px solid ${(props) => props.theme.divider};
padding-bottom: 5px;
}
`;
function Tab({ theme, ...rest }: Props) {
const Active = styled(m.div)`
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
width: 100%;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
background: ${(props) => props.theme.textSecondary};
`;
const transition = {
type: "spring",
stiffness: 500,
damping: 30,
};
function Tab({ theme, children, ...rest }: Props) {
const activeStyle = {
paddingBottom: "5px",
borderBottom: `3px solid ${theme.textSecondary}`,
color: theme.textSecondary,
};
return <TabLink {...rest} activeStyle={activeStyle} />;
return (
<TabLink {...rest} activeStyle={activeStyle}>
{(match) => (
<>
{children}
{match && (
<Active
layoutId="underline"
initial={false}
transition={transition}
/>
)}
</>
)}
</TabLink>
);
}
export default withTheme(Tab);
+57 -5
View File
@@ -1,13 +1,40 @@
// @flow
import { AnimateSharedLayout } from "framer-motion";
import { transparentize } from "polished";
import * as React from "react";
import styled from "styled-components";
import useWindowSize from "hooks/useWindowSize";
const Nav = styled.nav`
border-bottom: 1px solid ${(props) => props.theme.divider};
margin: 12px 0;
overflow-y: auto;
white-space: nowrap;
transition: opacity 250ms ease-out;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
&:after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 100%;
pointer-events: none;
background: ${(props) =>
props.$shadowVisible
? `linear-gradient(
90deg,
${transparentize(1, props.theme.background)} 0%,
${props.theme.background} 100%
)`
: `transparent`};
}
`;
// When sticky we need extra background coverage around the sides otherwise
@@ -30,11 +57,36 @@ export const Separator = styled.span`
margin-top: 6px;
`;
const Tabs = (props: {}) => {
const Tabs = ({ children }: {| children: React.Node |}) => {
const ref = React.useRef<?HTMLDivElement>();
const [shadowVisible, setShadow] = React.useState(false);
const { width } = useWindowSize();
const updateShadows = React.useCallback(() => {
const c = ref.current;
if (!c) return;
const scrollLeft = c.scrollLeft;
const wrapperWidth = c.scrollWidth - c.clientWidth;
const fade = !!(wrapperWidth - scrollLeft !== 0);
if (fade !== shadowVisible) {
setShadow(fade);
}
}, [shadowVisible]);
React.useEffect(() => {
updateShadows();
}, [width, updateShadows]);
return (
<Sticky>
<Nav {...props}></Nav>
</Sticky>
<AnimateSharedLayout>
<Sticky>
<Nav ref={ref} onScroll={updateShadows} $shadowVisible={shadowVisible}>
{children}
</Nav>
</Sticky>
</AnimateSharedLayout>
);
};
+16 -9
View File
@@ -1,5 +1,6 @@
// @flow
import "focus-visible";
import { LazyMotion } from "framer-motion";
import { createBrowserHistory } from "history";
import { Provider } from "mobx-react";
import * as React from "react";
@@ -49,6 +50,10 @@ if ("serviceWorker" in window.navigator) {
});
}
// Make sure to return the specific export containing the feature bundle.
const loadFeatures = () =>
import("./utils/motion.js").then((res) => res.default);
if (element) {
const App = () => (
<React.StrictMode>
@@ -56,15 +61,17 @@ if (element) {
<Analytics>
<Theme>
<ErrorBoundary>
<Router history={history}>
<>
<PageTheme />
<ScrollToTop>
<Routes />
</ScrollToTop>
<Toasts />
</>
</Router>
<LazyMotion features={loadFeatures}>
<Router history={history}>
<>
<PageTheme />
<ScrollToTop>
<Routes />
</ScrollToTop>
<Toasts />
</>
</Router>
</LazyMotion>
</ErrorBoundary>
</Theme>
</Analytics>
+76
View File
@@ -0,0 +1,76 @@
// @flow
import { observer } from "mobx-react";
import { TableOfContentsIcon } from "outline-icons";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { MenuButton, useMenuState } from "reakit/Menu";
import Button from "components/Button";
import ContextMenu from "components/ContextMenu";
import Template from "components/ContextMenu/Template";
import { type MenuItem } from "types";
type Props = {|
headings: { title: string, level: number, id: string }[],
|};
function TableOfContentsMenu({ headings }: Props) {
const menu = useMenuState({
modal: true,
unstable_preventOverflow: true,
unstable_fixed: true,
unstable_flip: true,
});
const { t } = useTranslation();
const minHeading = headings.reduce(
(memo, heading) => (heading.level < memo ? heading.level : memo),
Infinity
);
const items: MenuItem[] = React.useMemo(() => {
let i = [
{
type: "heading",
visible: true,
title: t("Contents"),
},
...headings.map((heading) => ({
href: `#${heading.id}`,
title: t(heading.title),
level: heading.level - minHeading,
})),
];
if (i.length === 1) {
i.push({
href: "#",
title: t("Headings you add to the document will appear here"),
disabled: true,
});
}
return i;
}, [t, headings, minHeading]);
return (
<>
<MenuButton {...menu}>
{(props) => (
<Button
{...props}
icon={<TableOfContentsIcon />}
iconColor="currentColor"
borderOnHover
neutral
/>
)}
</MenuButton>
<ContextMenu {...menu} aria-label={t("Table of contents")}>
<Template {...menu} items={items} />
</ContextMenu>
</>
);
}
export default observer(TableOfContentsMenu);
@@ -70,11 +70,13 @@ const Wrapper = styled("div")`
display: none;
position: sticky;
top: 80px;
max-height: calc(100vh - 80px);
box-shadow: 1px 0 0 ${(props) => props.theme.divider};
margin-top: 40px;
margin-right: 2em;
min-height: 40px;
overflow-y: auto;
${breakpoint("desktopLarge")`
margin-left: -16em;
@@ -374,6 +374,7 @@ class DocumentScene extends React.Component<Props> {
sharedTree={this.props.sharedTree}
goBack={this.goBack}
onSave={this.onSave}
headings={headings}
/>
<MaxWidth
archived={document.isArchived}
+13
View File
@@ -24,6 +24,7 @@ import useMobile from "hooks/useMobile";
import useStores from "hooks/useStores";
import DocumentMenu from "menus/DocumentMenu";
import NewChildDocumentMenu from "menus/NewChildDocumentMenu";
import TableOfContentsMenu from "menus/TableOfContentsMenu";
import TemplatesMenu from "menus/TemplatesMenu";
import { type NavigationNode } from "types";
import { metaDisplay } from "utils/keyboard";
@@ -46,6 +47,7 @@ type Props = {|
publish?: boolean,
autosave?: boolean,
}) => void,
headings: { title: string, level: number, id: string }[],
|};
function DocumentHeader({
@@ -60,6 +62,7 @@ function DocumentHeader({
publishingIsDisabled,
sharedTree,
onSave,
headings,
}: Props) {
const { t } = useTranslation();
const { auth, ui, policies } = useStores();
@@ -153,6 +156,11 @@ function DocumentHeader({
}
actions={
<>
{isMobile && (
<TocWrapper>
<TableOfContentsMenu headings={headings} />
</TocWrapper>
)}
{!isPublishing && isSaving && <Status>{t("Saving")}</Status>}
<Collaborators
document={document}
@@ -274,4 +282,9 @@ const Status = styled(Action)`
color: ${(props) => props.theme.slate};
`;
const TocWrapper = styled(Action)`
position: absolute;
left: 42px;
`;
export default observer(DocumentHeader);
+2
View File
@@ -7,3 +7,5 @@ import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });
global.localStorage = localStorage;
require("jest-fetch-mock").enableMocks();
+41
View File
@@ -58,3 +58,44 @@ export type SearchResult = {
context: string,
document: Document,
};
export type MenuItem =
| {|
title: React.Node,
to: string,
visible?: boolean,
selected?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
onClick: (event: SyntheticEvent<>) => void | Promise<void>,
visible?: boolean,
selected?: boolean,
disabled?: boolean,
|}
| {|
title: React.Node,
href: string,
visible?: boolean,
selected?: boolean,
disabled?: boolean,
level?: number,
|}
| {|
title: React.Node,
visible?: boolean,
disabled?: boolean,
style?: Object,
hover?: boolean,
items: MenuItem[],
|}
| {|
type: "separator",
visible?: boolean,
|}
| {|
type: "heading",
visible?: boolean,
title: React.Node,
|};
+4 -1
View File
@@ -1,4 +1,5 @@
// @flow
import retry from "fetch-retry";
import invariant from "invariant";
import { map, trim } from "lodash";
import { getCookie } from "tiny-cookie";
@@ -24,6 +25,8 @@ const CF_AUTHORIZATION = getCookie("CF_Authorization");
// if the cookie is set, we must pass it with all ApiClient requests
const CREDENTIALS = CF_AUTHORIZATION ? "same-origin" : "omit";
const fetchWithRetry = retry(fetch);
class ApiClient {
baseUrl: string;
userAgent: string;
@@ -92,7 +95,7 @@ class ApiClient {
let response;
try {
response = await fetch(urlToFetch, {
response = await fetchWithRetry(urlToFetch, {
method,
body,
headers,
+3
View File
@@ -0,0 +1,3 @@
// @flow
import { domMax } from "framer-motion";
export default domMax;
+5 -4
View File
@@ -73,11 +73,13 @@
"emoji-regex": "^6.5.1",
"es6-error": "^4.1.1",
"exports-loader": "^0.6.4",
"fetch-retry": "^4.1.1",
"fetch-with-proxy": "^3.0.1",
"file-loader": "^1.1.6",
"flow-typed": "^3.3.1",
"focus-visible": "^5.1.0",
"fractional-index": "^1.0.0",
"framer-motion": "^4.1.17",
"fs-extra": "^4.0.2",
"http-errors": "1.4.0",
"i18next": "^19.8.3",
@@ -86,7 +88,6 @@
"imports-loader": "0.6.5",
"invariant": "^2.2.2",
"ioredis": "^4.24.3",
"isomorphic-fetch": "2.2.1",
"joplin-turndown-plugin-gfm": "^1.0.12",
"js-search": "^1.4.2",
"json-loader": "0.5.4",
@@ -110,7 +111,6 @@
"mobx": "^4.15.4",
"mobx-react": "^6.3.1",
"natural-sort": "^1.0.0",
"node-htmldiff": "^0.9.3",
"nodemailer": "^6.4.16",
"outline-icons": "^1.27.0",
"oy-vey": "^0.10.0",
@@ -162,8 +162,8 @@
"styled-normalize": "^8.0.4",
"tiny-cookie": "^2.3.1",
"tmp": "^0.2.1",
"turndown": "^6.0.0",
"utf8": "^2.1.0",
"turndown": "^7.1.1",
"utf8": "^3.0.0",
"uuid": "^8.3.2",
"validator": "5.2.0"
},
@@ -190,6 +190,7 @@
"html-webpack-plugin": "3.2.0",
"i18next-parser": "^3.3.0",
"jest-cli": "^26.0.0",
"jest-fetch-mock": "^3.0.3",
"koa-webpack-dev-middleware": "^1.4.5",
"koa-webpack-hot-middleware": "^1.0.3",
"nodemon": "^1.19.4",
+2 -4
View File
@@ -41,13 +41,11 @@ export const CollectionNotificationEmail = ({
<Body>
<Heading>{collection.name}</Heading>
<p>
{actor.name} {eventName} the collection {collection.name}.
{actor.name} {eventName} the collection "{collection.name}".
</p>
<EmptySpace height={10} />
<p>
<Button
href={`${process.env.URL}${collection.url}?ref=notification-email`}
>
<Button href={`${process.env.URL}${collection.url}`}>
Open Collection
</Button>
</p>
+6 -225
View File
@@ -1,10 +1,8 @@
// @flow
import * as React from "react";
import theme from "../../shared/styles/theme";
import { User, Document, Team, Collection } from "../models";
import Body from "./components/Body";
import Button from "./components/Button";
import Diff from "./components/Diff";
import EmailTemplate from "./components/EmailLayout";
import EmptySpace from "./components/EmptySpace";
import Footer from "./components/Footer";
@@ -17,7 +15,6 @@ export type Props = {
document: Document,
collection: Collection,
eventName: string,
summary: string,
unsubscribeUrl: string,
};
@@ -41,34 +38,26 @@ export const DocumentNotificationEmail = ({
document,
collection,
eventName = "published",
summary,
unsubscribeUrl,
}: Props) => {
const link = `${team.url}${document.url}?ref=notification-email`;
return (
<EmailTemplate>
<Header />
<Body>
<Heading>
{document.title} {eventName}
"{document.title}" {eventName}
</Heading>
<p>
{actor.name} {eventName} the document "{document.title}", in the{" "}
{collection.name} collection.
</p>
{summary && (
<>
<EmptySpace height={20} />
<Diff href={link}>
<div dangerouslySetInnerHTML={{ __html: summary }} />
</Diff>
<EmptySpace height={20} />
</>
)}
<hr />
<EmptySpace height={10} />
<p>{document.getSummary()}</p>
<EmptySpace height={10} />
<p>
<Button href={link}>Open Document</Button>
<Button href={`${team.url}${document.url}`}>Open Document</Button>
</p>
</Body>
@@ -76,211 +65,3 @@ export const DocumentNotificationEmail = ({
</EmailTemplate>
);
};
export const css = `
font-family: ${theme.fontFamily};
font-weight: ${theme.fontWeight};
font-size: 1em;
line-height: 1.7em;
pre {
white-space: pre-wrap;
}
img {
text-align: center;
max-width: 100%;
max-height: 75vh;
clear: both;
}
img.image-right-50 {
float: right;
width: 50%;
margin-left: 2em;
margin-bottom: 1em;
clear: initial;
}
img.image-left-50 {
float: left;
width: 50%;
margin-right: 2em;
margin-bottom: 1em;
clear: initial;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 1em 0 0.5em;
font-weight: 500;
}
.notice {
display: flex;
align-items: center;
background: ${theme.noticeInfoBackground};
color: ${theme.noticeInfoText};
border-radius: 4px;
padding: 8px 16px;
margin: 8px 0;
}
.notice-tip {
background: ${theme.noticeTipBackground};
color: ${theme.noticeTipText};
}
.notice-warning {
background: ${theme.noticeWarningBackground};
color: ${theme.noticeWarningText};
}
b,
strong {
font-weight: 600;
}
p {
margin: 0;
}
a {
color: ${theme.link};
}
ins {
background-color: #128a2929;
text-decoration: none;
}
del {
background-color: ${theme.slateLight};
color: ${theme.slate};
text-decoration: strikethrough;
}
hr {
position: relative;
height: 1em;
border: 0;
}
hr:before {
content: "";
display: block;
position: absolute;
border-top: 1px solid ${theme.horizontalRule};
top: 0.5em;
left: 0;
right: 0;
}
hr.page-break {
page-break-after: always;
}
hr.page-break:before {
border-top: 1px dashed ${theme.horizontalRule};
}
code {
border-radius: 4px;
border: 1px solid ${theme.codeBorder};
padding: 3px 4px;
font-family: ${theme.fontFamilyMono};
font-size: 85%;
}
mark {
border-radius: 1px;
color: ${theme.textHighlightForeground};
background: ${theme.textHighlight};
a {
color: ${theme.textHighlightForeground};
}
}
ul {
padding-left: 0;
}
.checkbox-list-item {
list-style: none;
padding: 4px 0;
margin: 0;
}
.checkbox {
font-size: 0;
display: block;
float: left;
white-space: nowrap;
width: 12px;
height: 12px;
margin-top: 2px;
margin-right: 8px;
border: 1px solid ${theme.textSecondary};
border-radius: 3px;
}
pre {
display: block;
overflow-x: auto;
padding: 0.75em 1em;
line-height: 1.4em;
position: relative;
background: ${theme.codeBackground};
border-radius: 4px;
border: 1px solid ${theme.codeBorder};
-webkit-font-smoothing: initial;
font-family: ${theme.fontFamilyMono};
font-size: 13px;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
margin: 0;
code {
font-size: 13px;
background: none;
padding: 0;
border: 0;
}
}
table {
width: 100%;
border-collapse: collapse;
border-radius: 4px;
margin-top: 1em;
box-sizing: border-box;
* {
box-sizing: border-box;
}
tr {
position: relative;
border-bottom: 1px solid ${theme.tableDivider};
}
td,
th {
position: relative;
vertical-align: top;
border: 1px solid ${theme.tableDivider};
position: relative;
padding: 4px 8px;
min-width: 100px;
}
}
`;
+1 -1
View File
@@ -47,7 +47,7 @@ export const InviteEmail = ({
</p>
<EmptySpace height={10} />
<p>
<Button href={`${teamUrl}?ref=invite-email`}>Join now</Button>
<Button href={teamUrl}>Join now</Button>
</p>
</Body>
+1 -3
View File
@@ -43,9 +43,7 @@ export const WelcomeEmail = ({ teamUrl }: Props) => {
</p>
<EmptySpace height={10} />
<p>
<Button href={`${teamUrl}/home?ref=welcome-email`}>
View my dashboard
</Button>
<Button href={`${teamUrl}/home`}>View my dashboard</Button>
</p>
</Body>
-25
View File
@@ -1,25 +0,0 @@
// @flow
import * as React from "react";
import theme from "../../../shared/styles/theme";
type Props = {|
children: React.Node,
href?: string,
|};
export default ({ children, ...rest }: Props) => {
const style = {
borderRadius: "4px",
background: theme.secondaryBackground,
padding: ".5em 1em",
color: theme.text,
display: "block",
textDecoration: "none",
};
return (
<a width="100%" style={style} {...rest}>
{children}
</a>
);
};
+2 -2
View File
@@ -3,9 +3,9 @@ import { Table, TBody, TR, TD } from "oy-vey";
import * as React from "react";
import theme from "../../../shared/styles/theme";
type Props = {|
type Props = {
children: React.Node,
|};
};
export default (props: Props) => (
<Table width="550" padding="40">
-2
View File
@@ -100,8 +100,6 @@ export type RevisionEvent = {
documentId: string,
collectionId: string,
teamId: string,
actorId: string,
modelId: string,
};
export type CollectionImportEvent = {
+1 -3
View File
@@ -13,7 +13,6 @@ import {
type Props as DocumentNotificationEmailT,
DocumentNotificationEmail,
documentNotificationEmailText,
css as documentNotificationEmailCSS,
} from "./emails/DocumentNotificationEmail";
import { ExportEmail, exportEmailText } from "./emails/ExportEmail";
import {
@@ -147,9 +146,8 @@ export class Mailer {
this.sendMail({
to: opts.to,
title: `${opts.document.title}${opts.eventName}`,
previewText: `${opts.actor.name} ${opts.eventName} a document`,
previewText: `${opts.actor.name} ${opts.eventName} a new document`,
html: <DocumentNotificationEmail {...opts} />,
headCSS: documentNotificationEmailCSS,
text: documentNotificationEmailText(opts),
});
};
+18 -139
View File
@@ -1,60 +1,32 @@
// @flow
import debug from "debug";
import type {
DocumentEvent,
RevisionEvent,
CollectionEvent,
Event,
} from "../events";
import type { DocumentEvent, CollectionEvent, Event } from "../events";
import mailer from "../mailer";
import {
View,
Document,
Team,
Collection,
Revision,
User,
NotificationSetting,
Attachment,
} from "../models";
import { Op } from "../sequelize";
import markdownDiff from "../utils/markdownDiff";
import parseAttachmentIds from "../utils/parseAttachmentIds";
import { getSignedImageUrl } from "../utils/s3";
const log = debug("services");
async function replaceImageAttachments(text: string) {
const attachmentIds = parseAttachmentIds(text);
await Promise.all(
attachmentIds.map(async (id) => {
const attachment = await Attachment.findByPk(id);
if (attachment) {
const accessUrl = await getSignedImageUrl(attachment.key, 86400 * 4);
text = text.replace(attachment.redirectUrl, accessUrl);
}
})
);
return text;
}
export default class Notifications {
async on(event: Event) {
switch (event.name) {
case "documents.publish":
return this.documentPublished(event);
case "revisions.create":
return this.revisionCreated(event);
case "documents.update.debounced":
return this.documentUpdated(event);
case "collections.create":
return this.collectionCreated(event);
default:
}
}
async documentPublished(event: DocumentEvent) {
async documentUpdated(event: DocumentEvent) {
// never send notifications when batch importing documents
if (event.data && event.data.source === "import") return;
@@ -73,7 +45,10 @@ export default class Notifications {
[Op.ne]: document.lastModifiedById,
},
teamId: document.teamId,
event: "documents.publish",
event:
event.name === "documents.publish"
? "documents.publish"
: "documents.update",
},
include: [
{
@@ -84,104 +59,25 @@ export default class Notifications {
],
});
const eventName = "published";
for (const setting of notificationSettings) {
// Check the user has access to the collection this document is in. Just
// because they were a collaborator once doesn't mean they still are.
const collectionIds = await setting.user.collectionIds();
if (!collectionIds.includes(document.collectionId)) {
continue;
}
// If this user has viewed the document since the last update was made
// then we can avoid sending them a useless notification, yay.
const view = await View.findOne({
where: {
userId: setting.userId,
documentId: event.documentId,
updatedAt: {
[Op.gt]: document.updatedAt,
},
},
});
if (view) {
log(
`suppressing notification to ${setting.userId} because update viewed`
);
continue;
}
mailer.documentNotification({
to: setting.user.email,
eventName,
document,
team,
collection,
summary: document.getSummary(),
actor: document.updatedBy,
unsubscribeUrl: setting.unsubscribeUrl,
});
}
}
async revisionCreated(event: RevisionEvent) {
const revision = await Revision.findByPk(event.modelId, {
include: [
{
model: Document,
as: "document",
include: [
{
model: Collection,
as: "collection",
},
],
},
],
});
if (!revision) return;
const { document } = revision;
const { collection } = document;
if (!collection || !document) return;
const team = await Team.findByPk(document.teamId);
if (!team) return;
const notificationSettings = await NotificationSetting.findAll({
where: {
userId: {
[Op.ne]: revision.userId,
},
teamId: document.teamId,
event: "documents.update",
},
include: [
{
model: User,
required: true,
as: "user",
},
],
});
const eventName = "updated";
const eventName =
event.name === "documents.publish" ? "published" : "updated";
for (const setting of notificationSettings) {
// For document updates we only want to send notifications if
// the document has been edited by the user with this notification setting
// This could be replaced with ability to "follow" in the future
if (!document.collaboratorIds.includes(setting.userId)) {
continue;
if (
eventName === "updated" &&
!document.collaboratorIds.includes(setting.userId)
) {
return;
}
// Check the user has access to the collection this document is in. Just
// because they were a collaborator once doesn't mean they still are.
const collectionIds = await setting.user.collectionIds();
if (!collectionIds.includes(document.collectionId)) {
continue;
return;
}
// If this user has viewed the document since the last update was made
@@ -200,33 +96,16 @@ export default class Notifications {
log(
`suppressing notification to ${setting.userId} because update viewed`
);
continue;
return;
}
const previous = await Revision.findOne({
where: {
documentId: document.id,
createdAt: {
[Op.lt]: revision.createdAt,
},
},
order: [["createdAt", "DESC"]],
});
let summary = markdownDiff(previous ? previous.text : "", revision.text);
console.log(summary);
summary = await replaceImageAttachments(summary);
console.log(summary);
mailer.documentNotification({
to: setting.user.email,
eventName,
document,
team,
collection,
summary,
actor: revision.user,
actor: document.updatedBy,
unsubscribeUrl: setting.unsubscribeUrl,
});
}
+6 -12
View File
@@ -1,6 +1,6 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import mailer from "../mailer";
import { View, NotificationSetting, Revision } from "../models";
import { View, NotificationSetting } from "../models";
import { buildDocument, buildCollection, buildUser } from "../test/factories";
import { flushdb } from "../test/support";
import NotificationsService from "./notifications";
@@ -89,10 +89,9 @@ describe("documents.publish", () => {
});
});
describe("revisions.create", () => {
describe("documents.update.debounced", () => {
test("should send a notification to other collaborator", async () => {
const document = await buildDocument();
const revision = await Revision.createFromDocument(document);
const collaborator = await buildUser({ teamId: document.teamId });
document.collaboratorIds = [collaborator.id];
await document.save();
@@ -104,9 +103,8 @@ describe("revisions.create", () => {
});
await Notifications.on({
name: "revisions.create",
name: "documents.update.debounced",
documentId: document.id,
modelId: revision.id,
collectionId: document.collectionId,
teamId: document.teamId,
actorId: document.createdById,
@@ -117,7 +115,6 @@ describe("revisions.create", () => {
test("should not send a notification if viewed since update", async () => {
const document = await buildDocument();
const revision = await Revision.createFromDocument(document);
const collaborator = await buildUser({ teamId: document.teamId });
document.collaboratorIds = [collaborator.id];
await document.save();
@@ -131,10 +128,9 @@ describe("revisions.create", () => {
await View.touch(document.id, collaborator.id, true);
await Notifications.on({
name: "revisions.create",
name: "documents.update.debounced",
documentId: document.id,
collectionId: document.collectionId,
modelId: revision.id,
teamId: document.teamId,
actorId: document.createdById,
});
@@ -142,13 +138,12 @@ describe("revisions.create", () => {
expect(mailer.documentNotification).not.toHaveBeenCalled();
});
test("should not send a notification to the last user that modified", async () => {
test("should not send a notification to last editor", async () => {
const user = await buildUser();
const document = await buildDocument({
teamId: user.teamId,
lastModifiedById: user.id,
});
const revision = await Revision.createFromDocument(document);
await NotificationSetting.create({
userId: user.id,
@@ -157,9 +152,8 @@ describe("revisions.create", () => {
});
await Notifications.on({
name: "revisions.create",
name: "documents.update.debounced",
documentId: document.id,
modelId: revision.id,
collectionId: document.collectionId,
teamId: document.teamId,
actorId: document.createdById,
+2 -10
View File
@@ -1,6 +1,6 @@
// @flow
import type { DocumentEvent, RevisionEvent } from "../events";
import { Revision, Document, Event } from "../models";
import { Revision, Document } from "../models";
export default class Revisions {
async on(event: DocumentEvent | RevisionEvent) {
@@ -22,15 +22,7 @@ export default class Revisions {
return;
}
const revision = await Revision.createFromDocument(document);
Event.add({
name: "revisions.create",
documentId: document.id,
collectionId: document.collectionId,
modelId: revision.id,
teamId: document.teamId,
actorId: revision.userId,
});
await Revision.createFromDocument(document);
break;
}
-34
View File
@@ -1,34 +0,0 @@
# Heading 1
## Heading 2
This is a test paragraph
This is a second test paragraph. This is a second sentence.
This is a another test paragraph. This is a another sentence.
- list item 1
- list item 2
```
this is a codeblock
```
:::info
This is an info block
:::
!!This is a placeholder!!
==this is a highlight==
- [ ] checklist item 1
- [ ] checklist item 2
- [x] checklist item 3
same on both sides
same on both sides
same on both sides
-37
View File
@@ -1,37 +0,0 @@
# Heading 1
## Heading 2
This is a test paragraph
This is a second test paragraph. This is a second sentence.
This is a another test paragraph. This is a another sentence.
- list item 1
```
this is a codeblock
```
This is a new paragraph.
:::info
This is an info block
:::
!!This is a placeholder!!
==this is a highlight==
- [x] checklist item 1
- [x] checklist item 2
- [ ] checklist item 3
- [ ] checklist item 4
- [x] checklist item 5
same on both sides
same on both sides
same on both sides
-2
View File
@@ -2,11 +2,9 @@
require("dotenv").config({ silent: true });
// test environment variables
process.env.URL = "http://localhost:3000";
process.env.DATABASE_URL = process.env.DATABASE_URL_TEST;
process.env.NODE_ENV = "test";
process.env.GOOGLE_CLIENT_ID = "123";
process.env.AZURE_CLIENT_ID = "";
process.env.SLACK_KEY = "123";
process.env.DEPLOYMENT = "";
process.env.ALLOWED_DOMAINS = "allowed-domain.com";
@@ -1,26 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should diff a complex document 1`] = `
"<p>This is a second test paragraph. This is a second sentence.</p>
<p>This is a another test paragraph. This is a another sentence.</p>
<ul>
<li>list item 1</li>
<li data-diff-node=\\"del\\" data-operation-index=\\"1\\"><del data-operation-index=\\"1\\">list item 2</del></li></ul>
<pre><code>this is a codeblock
</code></pre><p data-diff-node=\\"ins\\" data-operation-index=\\"3\\"><ins data-operation-index=\\"3\\">This is a new paragraph.</ins></p>
<div class=\\"notice notice-info\\">
<p>This is an info block</p>
</div>
<p><span class=\\"placeholder\\">This is a placeholder</span></p>
<p><span class=\\"highlight\\">this is a highlight</span></p>
<ul>
<li class=\\"checkbox-list-item\\"><span class=\\"checkbox checked\\">[<del data-operation-index=\\"5\\"> ]</del><ins data-operation-index=\\"5\\">x]</ins></span>checklist item 1</li><li class=\\"checkbox-list-item\\"><span class=\\"checkbox checked\\" data-diff-node=\\"ins\\" data-operation-index=\\"7\\"><ins data-operation-index=\\"7\\">[x]</ins></span><ins data-operation-index=\\"7\\">checklist item 2</ins></li>
<li class=\\"checkbox-list-item\\"><span class=\\"checkbox \\">[ ]</span>checklist item <del data-operation-index=\\"9\\">2</del><ins data-operation-index=\\"9\\">3</ins></li><li class=\\"checkbox-list-item\\"><span class=\\"checkbox \\" data-diff-node=\\"ins\\" data-operation-index=\\"9\\"><ins data-operation-index=\\"9\\">[ ]</ins></span><ins data-operation-index=\\"9\\">checklist item 4</ins></li>
<li class=\\"checkbox-list-item\\"><span class=\\"checkbox checked\\">[x]</span>checklist item <del data-operation-index=\\"11\\">3</del><ins data-operation-index=\\"11\\">5</ins></li>
</ul>"
`;
exports[`should return everything inserted when previously empty 1`] = `
"<h1 data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">Heading 1</ins></h1><h2 data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">Heading 2</ins></h2><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">This is a test paragraph</ins></p><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">This is a second test paragraph. This is a second sentence.</ins></p><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">This is a another test paragraph. This is a another sentence.</ins></p><ul data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><li data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">list item 1</ins></li><li data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">list item 2</ins></li></ul><pre data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><code data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">this is a codeblock
</ins></code></pre><div class=\\"notice notice-info\\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">This is an info block</ins></p></div><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><span class=\\"placeholder\\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">This is a placeholder</ins></span></p><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><span class=\\"highlight\\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">this is a highlight</ins></span></p><ul data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><li class=\\"checkbox-list-item\\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><span class=\\"checkbox \\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">[ ]</ins></span><ins data-operation-index=\\"0\\">checklist item 1</ins></li><li class=\\"checkbox-list-item\\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><span class=\\"checkbox \\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">[ ]</ins></span><ins data-operation-index=\\"0\\">checklist item 2</ins></li><li class=\\"checkbox-list-item\\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><span class=\\"checkbox checked\\" data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">[x]</ins></span><ins data-operation-index=\\"0\\">checklist item 3</ins></li></ul><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">same on both sides</ins></p><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">same on both sides</ins></p><p data-diff-node=\\"ins\\" data-operation-index=\\"0\\"><ins data-operation-index=\\"0\\">same on both sides</ins></p>"
`;
-57
View File
@@ -1,57 +0,0 @@
// @flow
import { findIndex, findLastIndex } from "lodash";
import diff from "node-htmldiff";
import { renderToHtml } from "rich-markdown-editor";
export default function markdownDiff(
before: string,
after: string,
fullDiff: boolean = false,
buffer: number = 1
) {
// The basic idea here is to first render the Markdown to HTML, then diff the
// HTML - both sides will have valid HTML so we should have a valid diff as well
const beforeHtml = renderToHtml(before);
const afterHtml = renderToHtml(after);
const diffHtml = diff(beforeHtml, afterHtml);
if (fullDiff) {
return diffHtml;
}
if (before === after) {
return "";
}
// Split diff at paragraphs and find the first and last changed tags
// so we can chop around paragraphs rather than return the entire document.
//
// In an ideal world we'd use an AST here and parse that rather than be doing
// operations on strings. I hope this can be revisted in the future with an
// improved diffing library.
const newParagraph = /(?:^|\n)<p>/;
let lines = diffHtml.split(newParagraph);
const firstChangedLineIndex = findIndex(
lines,
(value) => value.includes("<ins ") || value.includes("<del ")
);
const lastChangedLineIndex = findLastIndex(
lines,
(value) => value.includes("</ins>") || value.includes("</del>")
);
const start = Math.max(0, firstChangedLineIndex - buffer);
const end = Math.min(lines.length, lastChangedLineIndex + buffer);
lines = lines.slice(start, end);
if (!lines.length) {
return "";
}
return [start > 0 ? "" : undefined, ...lines]
.filter((x) => x !== undefined)
.join("\n<p>")
.trim();
}
-55
View File
@@ -1,55 +0,0 @@
// @flow
import fs from "fs";
import path from "path";
import markdownDiff from "./markdownDiff";
it("should diff a complex document", async () => {
const before = await fs.promises.readFile(
path.resolve(process.cwd(), "server", "test", "fixtures", "complex.md"),
"utf8"
);
const after = await fs.promises.readFile(
path.resolve(
process.cwd(),
"server",
"test",
"fixtures",
"complexModified.md"
),
"utf8"
);
const diff = markdownDiff(before, after);
expect(diff).toMatchSnapshot();
});
it("should return empty string when both sides are empty", () => {
const diff = markdownDiff("", "");
expect(diff).toEqual("");
});
it("should return everything inserted when previously empty", async () => {
const content = await fs.promises.readFile(
path.resolve(process.cwd(), "server", "test", "fixtures", "complex.md"),
"utf8"
);
const diff = markdownDiff("", content);
expect(diff).toMatchSnapshot();
});
it("should return empty for changed nodes", async () => {
// Note: This isn't ideal behavior, but it is current behavior. If the diffing
// library is improved then we could potentially render the old + new heading
// with ins/del tags as appropriate.
const diff = markdownDiff("# Heading", "## Heading");
expect(diff).toEqual("");
});
it("should return deleted nodes", async () => {
const diff = markdownDiff("![caption](/image.png)", "");
expect(diff).toEqual(
'<p><del data-operation-index="0"><img src="/image.png" alt="caption"></del></p>'
);
});
+2 -2
View File
@@ -163,13 +163,13 @@ export const deleteFromS3 = (key: string) => {
.promise();
};
export const getSignedImageUrl = async (key: string, expires: number = 60) => {
export const getSignedImageUrl = async (key: string) => {
const isDocker = process.env.AWS_S3_UPLOAD_BUCKET_URL.match(/http:\/\/s3:/);
const params = {
Bucket: AWS_S3_UPLOAD_BUCKET_NAME,
Key: key,
Expires: expires,
Expires: 60,
};
return isDocker
+1
View File
@@ -13,6 +13,7 @@ export const languageOptions = [
{ label: "Español (España)", value: "es_ES" },
{ label: "Français (France)", value: "fr_FR" },
{ label: "Italiano (Italia)", value: "it_IT" },
{ label: "日本語 (Japanese)", value: "ja_JP" },
{ label: "한국어 (Korean)", value: "ko_KR" },
{ label: "Português (Brazil)", value: "pt_BR" },
{ label: "Português (Portugal)", value: "pt_PT" },
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Pfad zum Dokument",
"Group member options": "Optionen für Gruppenmitglieder",
"Remove": "Entfernen",
"Collection": "Sammlung",
"New document": "Neues Dokument",
"Import document": "Dokument importieren",
"Edit": "Bearbeiten",
"Permissions": "Berechtigungen",
"Delete": "Löschen",
"Collection": "Sammlung",
"Collection permissions": "Berechtigungen für Sammlungen",
"Edit collection": "Sammlung bearbeiten",
"Delete collection": "Sammlung löschen",
@@ -206,6 +206,7 @@
"Share options": "Teilen-Einstellungen",
"Go to document": "Zum Dokument gehen",
"Revoke link": "Link widerrufen",
"Table of contents": "Inhaltsverzeichnis",
"By {{ author }}": "Von {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Sind Sie sicher, dass Sie {{ userName }} zu einem Administrator machen möchten? Administratoren können Team- und Rechnungsinformationen ändern.",
"Are you sure you want to make {{ userName }} a member?": "Sind Sie sicher, dass Sie {{ userName }} zu einem Mitglied machen möchten?",
@@ -337,7 +338,6 @@
"Move current document": "Aktuelles Dokument verschieben",
"Jump to search": "Zur Suche springen",
"Jump to home": "Zurück zum Dashboard",
"Table of contents": "Inhaltsverzeichnis",
"Toggle navigation": "Navigation ausblenden",
"Focus search input": "Fokussiere Suchergebnis",
"Open this guide": "Diese Anleitung öffnen",
+3 -1
View File
@@ -206,6 +206,9 @@
"Share options": "Share options",
"Go to document": "Go to document",
"Revoke link": "Revoke link",
"Contents": "Contents",
"Headings you add to the document will appear here": "Headings you add to the document will appear here",
"Table of contents": "Table of contents",
"By {{ author }}": "By {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
@@ -337,7 +340,6 @@
"Move current document": "Move current document",
"Jump to search": "Jump to search",
"Jump to home": "Jump to home",
"Table of contents": "Table of contents",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Open this guide",
+24 -24
View File
@@ -1,7 +1,7 @@
{
"currently editing": "actualmente editando",
"currently viewing": "viendo actualmente",
"previously edited": "previously edited",
"previously edited": "editado previamente",
"You": "Usted",
"Viewers": "Viewers",
"Sorry, an error occurred saving the collection": "Lo sentimos, se produjo un error al guardar la colección",
@@ -30,9 +30,9 @@
"in": "en",
"nested document": "documento anidado",
"nested document_plural": "documentos anidados",
"Viewed by": "Viewed by",
"only you": "only you",
"person": "person",
"Viewed by": "Visto por",
"only you": "sólo tú",
"person": "persona",
"people": "people",
"Currently editing": "Currently editing",
"Currently viewing": "Currently viewing",
@@ -55,8 +55,8 @@
"Delete column": "Eliminar columna",
"Delete row": "Borrar fila",
"Delete table": "Eliminar tabla",
"Delete image": "Delete image",
"Download image": "Download image",
"Delete image": "Eliminar imagen",
"Download image": "Descargar imagen",
"Float left": "Float left",
"Float right": "Float right",
"Center large": "Center large",
@@ -109,7 +109,7 @@
"Dismiss": "Descartar",
"Keyboard shortcuts": "Atajos del teclado",
"Back": "Atras",
"Collections could not be loaded, please reload the app": "Collections could not be loaded, please reload the app",
"Collections could not be loaded, please reload the app": "No se pudieron cargar las colecciones, por favor recarga la aplicación",
"New collection": "Nueva colección",
"Collections": "Colecciones",
"Untitled": "Sin título",
@@ -136,8 +136,8 @@
"Installation": "Instalación",
"Unstar": "Eliminar de favoritos",
"Star": "Favorito",
"Previous page": "Previous page",
"Next page": "Next page",
"Previous page": "Página anterior",
"Next page": "Página siguiente",
"Could not import file": "No se pudo importar el archivo",
"Appearance": "Apariencia",
"System": "Sistema",
@@ -152,12 +152,12 @@
"Path to document": "Ruta al documento",
"Group member options": "Opciones de miembros del grupo",
"Remove": "Eliminar",
"Collection": "Colección",
"New document": "Nuevo documento",
"Import document": "Importar documento",
"Edit": "Editar",
"Permissions": "Permisos",
"Delete": "Borrar",
"Collection": "Colección",
"Collection permissions": "Permisos de colección",
"Edit collection": "Editar colección",
"Delete collection": "Eliminar colección",
@@ -181,7 +181,7 @@
"Create template": "Crear plantilla",
"Duplicate": "Duplicar",
"Unpublish": "Cancelar publicación",
"Permanently delete": "Permanently delete",
"Permanently delete": "Eliminar permanentemente",
"Move": "Mover",
"History": "Historial",
"Download": "Descargar",
@@ -206,19 +206,20 @@
"Share options": "Share options",
"Go to document": "Ir al documento",
"Revoke link": "Revocar enlace",
"Table of contents": "Tabla de contenido",
"By {{ author }}": "Por {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "¿Estás seguro de que quieres convertir a {{ userName }} en administrador? Los administradores pueden modificar el equipo e información de facturación.",
"Are you sure you want to make {{ userName }} a member?": "¿Estás seguro de que quieres convertir a {{ userName }} en miembro?",
"Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content": "Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content",
"Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "¿Está seguro que desea suspender esta cuenta? Los usuarios suspendidos no podrán iniciar sesión.",
"User options": "User options",
"User options": "Opciones de usuario",
"Make {{ userName }} a member": "Make {{ userName }} a member",
"Make {{ userName }} a viewer": "Make {{ userName }} a viewer",
"Make {{ userName }} an admin…": "Hacer a {{ userName }} un administrador…",
"Revoke invite": "Revocar Invitación",
"Activate account": "Activar cuenta",
"Suspend account": "Suspender cuenta",
"API token created": "API token created",
"API token created": "Token de API creado",
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".",
"Documents": "Documentos",
"The document archive is empty at the moment.": "El archivo de documento está vacío en este momento.",
@@ -226,9 +227,9 @@
"<em>{{ collectionName }}</em> doesnt contain any\n documents yet.": "<em>{{ collectionName }}</em> aún no contiene\n documentos.",
"Get started by creating a new one!": "¡Empiece creando uno nuevo!",
"Create a document": "Crear documento",
"Manage permissions": "Manage permissions",
"This collection is only visible to those given access": "This collection is only visible to those given access",
"Private": "Private",
"Manage permissions": "Administrar permisos",
"This collection is only visible to those given access": "Esta colección sólo es visible a aquellos con acceso",
"Private": "Privado",
"Pinned": "Fijado",
"Recently updated": "Recientemente actualizado",
"Recently published": "Recientemente publicado",
@@ -241,7 +242,7 @@
"Alphabetical": "Alfabético",
"Public document sharing": "Public document sharing",
"When enabled, documents can be shared publicly on the internet.": "When enabled, documents can be shared publicly on the internet.",
"Public sharing is currently disabled in the team security settings.": "Public sharing is currently disabled in the team security settings.",
"Public sharing is currently disabled in the team security settings.": "Compartir públicamente está desactivado en las configuraciones de seguridad del equipo.",
"Saving": "Guardando",
"Save": "Guardar",
"Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.",
@@ -268,15 +269,15 @@
"Never signed in": "No ha iniciado sesión nunca",
"Invited": "Invitado",
"Admin": "Admin",
"{{ userName }} was removed from the collection": "{{ userName }} was removed from the collection",
"{{ userName }} was removed from the collection": "{{ userName }} fue quitado de la colección",
"Could not remove user": "No se pudo remover al usuario",
"{{ userName }} permissions were updated": "{{ userName }} permissions were updated",
"Could not update user": "Could not update user",
"The {{ groupName }} group was removed from the collection": "The {{ groupName }} group was removed from the collection",
"Could not remove group": "Could not remove group",
"{{ userName }} permissions were updated": "Se actualizaron los permisos de {{ userName }}",
"Could not update user": "No se pudo actualizar el usuario",
"The {{ groupName }} group was removed from the collection": "El grupo {{ groupName }} fue quitado de la colección",
"Could not remove group": "No se pudo eliminar el grupo",
"{{ groupName }} permissions were updated": "{{ groupName }} permissions were updated",
"Default access permissions were updated": "Default access permissions were updated",
"Could not update permissions": "Could not update permissions",
"Could not update permissions": "No se pudo actualizar los permisos",
"The <em>{{ collectionName }}</em> collection is private. Team members have no access to it by default.": "The <em>{{ collectionName }}</em> collection is private. Team members have no access to it by default.",
"Team members can view documents in the <em>{{ collectionName }}</em> collection by default.": "Team members can view documents in the <em>{{ collectionName }}</em> collection by default.",
"Team members can view and edit documents in the <em>{{ collectionName }}</em> collection by\n default.": "Team members can view and edit documents in the <em>{{ collectionName }}</em> collection by\n default.",
@@ -337,7 +338,6 @@
"Move current document": "Mover el documento actual",
"Jump to search": "Ir a la búsqueda",
"Jump to home": "Jump to home",
"Table of contents": "Tabla de contenido",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Abra esta guía",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Path to document",
"Group member options": "Group member options",
"Remove": "حذف",
"Collection": "مجموعه",
"New document": "سند جدید",
"Import document": "وارد کردن سند",
"Edit": "ویرایش",
"Permissions": "دسترسی‌ها",
"Delete": "حذف",
"Collection": "مجموعه",
"Collection permissions": "دسترسی‌های مجموعه",
"Edit collection": "ویرایش مجموعه",
"Delete collection": "حذف مجموعه",
@@ -206,6 +206,7 @@
"Share options": "Share options",
"Go to document": "Go to document",
"Revoke link": "Revoke link",
"Table of contents": "Table of contents",
"By {{ author }}": "By {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
@@ -337,7 +338,6 @@
"Move current document": "Move current document",
"Jump to search": "Jump to search",
"Jump to home": "Jump to home",
"Table of contents": "Table of contents",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Open this guide",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Chemin d'accès au document",
"Group member options": "Options des membres du groupe",
"Remove": "Supprimer",
"Collection": "Collection",
"New document": "Nouveau document",
"Import document": "Importer un document",
"Edit": "Modifier",
"Permissions": "Permissions",
"Delete": "Supprimer",
"Collection": "Collection",
"Collection permissions": "Permissions sur les collections",
"Edit collection": "Modifier la collection",
"Delete collection": "Supprimer la collection",
@@ -206,6 +206,7 @@
"Share options": "Options de partage",
"Go to document": "Accéder au document",
"Revoke link": "Révoquer le lien",
"Table of contents": "Table des matières",
"By {{ author }}": "Par {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Voulez-vous vraiment faire de {{ userName }} un administrateur ? Les administrateurs peuvent modifier les informations relatives à l'équipe et à la facturation.",
"Are you sure you want to make {{ userName }} a member?": "Êtes-vous sûr de vouloir faire de {{ userName }} un membre ?",
@@ -337,7 +338,6 @@
"Move current document": "Déplacer le document courant",
"Jump to search": "Aller à la recherche",
"Jump to home": "Jump to home",
"Table of contents": "Table des matières",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Ouvrir ce guide",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Percorso del documento",
"Group member options": "Opzioni membri del gruppo",
"Remove": "Elimina",
"Collection": "Raccolta",
"New document": "Nuovo documento",
"Import document": "Importa documento",
"Edit": "Modifica",
"Permissions": "Permessi",
"Delete": "Cancella",
"Collection": "Raccolta",
"Collection permissions": "Collection permissions",
"Edit collection": "Modifica raccolta",
"Delete collection": "Elimina raccolta",
@@ -206,6 +206,7 @@
"Share options": "Opzioni di condivisione",
"Go to document": "Vai al documento",
"Revoke link": "Revoca il link",
"Table of contents": "Indice contenuti",
"By {{ author }}": "Di {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Sei sicuro di voler rendere {{ userName }} un amministratore? Gli amministratori possono modificare le informazioni sul team e sulla fatturazione.",
"Are you sure you want to make {{ userName }} a member?": "Sei sicuro di voler rendere {{ userName }} un membro?",
@@ -337,7 +338,6 @@
"Move current document": "Sposta il documento corrente",
"Jump to search": "Vai alla ricerca",
"Jump to home": "Jump to home",
"Table of contents": "Indice contenuti",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Apri questa guida",
+16 -16
View File
@@ -30,7 +30,7 @@
"in": "に",
"nested document": "ネストされたドキュメント",
"nested document_plural": "ネストされたドキュメント",
"Viewed by": "閲覧者:",
"Viewed by": "閲覧者",
"only you": "自分のみ",
"person": "個人",
"people": "メンバー",
@@ -98,7 +98,7 @@
"Icon": "アイコン",
"Show menu": "メニューを表示",
"Choose icon": "アイコンの選択",
"Loading": "ローディング...",
"Loading": "ローディング",
"Search": "検索",
"Default access": "デフォルトアクセス",
"View and edit": "表示・編集",
@@ -109,7 +109,7 @@
"Dismiss": "終了",
"Keyboard shortcuts": "キーボードショートカット",
"Back": "戻る",
"Collections could not be loaded, please reload the app": "Collections could not be loaded, please reload the app",
"Collections could not be loaded, please reload the app": "コレクションを読み込めませんでした。アプリを再読み込みしてください。",
"New collection": "コレクションを追加",
"Collections": "コレクション",
"Untitled": "文書",
@@ -152,12 +152,12 @@
"Path to document": "ドキュメントのパス",
"Group member options": "グループメンバーのオプション",
"Remove": "削除",
"Collection": "コレクション",
"New document": "新しいドキュメント",
"Import document": "ドキュメントのインポート",
"Edit": "編集",
"Permissions": "権限",
"Delete": "削除",
"Collection": "コレクション",
"Collection permissions": "コレクションの権限",
"Edit collection": "編集コレクション",
"Delete collection": "コレクションの削除",
@@ -181,14 +181,14 @@
"Create template": "テンプレートの作成",
"Duplicate": "文書の複製",
"Unpublish": "未発表",
"Permanently delete": "Permanently delete",
"Permanently delete": "完全に削除します。",
"Move": "移動",
"History": "変更履歴",
"Download": "ダウンロード",
"Print": "プリント",
"Move {{ documentName }}": "{{ documentName }} を移動",
"Delete {{ documentName }}": "{{ documentName }}を削除します。",
"Permanently delete {{ documentName }}": "Permanently delete {{ documentName }}",
"Permanently delete {{ documentName }}": "{{ documentName }} を完全に削除します。",
"Edit group": "グループの編集",
"Delete group": "グループを削除",
"Group options": "グループのオプション",
@@ -206,6 +206,7 @@
"Share options": "共有オプション",
"Go to document": "ドキュメントに移動",
"Revoke link": "リンクを取り消す",
"Table of contents": "目次",
"By {{ author }}": "作ったのは{{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "本当に{{ userName }}を管理者にしていいのか?管理者は課金情報を修正することができます。また、チームを変更することもできます。",
"Are you sure you want to make {{ userName }} a member?": "本当に{{ userName }}をメンバーにしていいのか?",
@@ -219,7 +220,7 @@
"Activate account": "アカウントの有効化",
"Suspend account": "アカウントの一時停止",
"API token created": "APIトークンが作成されました",
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".",
"Name your token something that will help you to remember it's use in the future, for example \"local development\", \"production\", or \"continuous integration\".": "トークンには、「ローカル開発」、「本番」、「継続的インテグレーション」など、使用目的を思い出しやすい名前を付けてください。",
"Documents": "ドキュメント",
"The document archive is empty at the moment.": "ドキュメントアーカイブは現在空です。",
"Search in collection": "コレクション内を検索",
@@ -242,7 +243,7 @@
"Public document sharing": "公開ドキュメントを共有",
"When enabled, documents can be shared publicly on the internet.": "有効にすると、ドキュメントをインターネット上で公開して共有できます。",
"Public sharing is currently disabled in the team security settings.": "チームのセキュリティ設定で公開共有が無効になっています。",
"Saving": "保存文書...",
"Saving": "保存",
"Save": "セーブ",
"Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "コレクションは、ドキュメントをグループ化するためのものです。製品やエンジニアリングなど、トピックや社内のチームを基準に作成することで、最も効果的に機能します。",
"This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.": "これはチームメンバーに付与されるデフォルトのアクセスレベルです。コレクションが作成されると、さらに多くのアクセスを特定のユーザーまたはグループに付与できます。",
@@ -272,9 +273,9 @@
"Could not remove user": "このユーザーを削除することはできません。",
"{{ userName }} permissions were updated": "{{ userName }} の権限が更新されました",
"Could not update user": "ユーザーを更新できませんでした",
"The {{ groupName }} group was removed from the collection": "グループ:{{ groupName }} がコレクションから削除されました",
"The {{ groupName }} group was removed from the collection": "グループ {{ groupName }} がコレクションから削除されました",
"Could not remove group": "グループを削除できませんでした",
"{{ groupName }} permissions were updated": "グループ:{{ groupName }} の権限が更新されました",
"{{ groupName }} permissions were updated": "グループ {{ groupName }} の権限が更新されました",
"Default access permissions were updated": "デフォルトのアクセス権限が更新されました",
"Could not update permissions": "権限を更新できませんでした",
"The <em>{{ collectionName }}</em> collection is private. Team members have no access to it by default.": "<em>{{ collectionName }}</em> コレクションは非公開です。チームメンバーはデフォルトではアクセスできません。",
@@ -307,15 +308,15 @@
"Share nested documents": "ネストされたドキュメントを共有する",
"Nested documents are publicly available": "ネストされたドキュメントも公開されます",
"Nested documents are not shared": "ネストされたドキュメントは共有されません",
"Are you sure you want to delete the <em>{{ documentTitle }}</em> template?": "テンプレート:<em>{{ documentTitle }}</em> を削除してもよろしいですか?",
"Are you sure you want to delete the <em>{{ documentTitle }}</em> template?": "テンプレート <em>{{ documentTitle }}</em> を削除してもよろしいですか?",
"Are you sure about that? Deleting the <em>{{ documentTitle }}</em> document will delete all of its history and any nested documents.": "<em>{{ documentTitle }}</em> ドキュメントを削除すると、その履歴とネストされたドキュメントがすべて削除されます。よろしいですか?",
"If youd like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.": "将来的に {{noun}} の参照または復元が必要になる可能性がある場合は、代わりにアーカイブすることを検討してください。",
"Deleting": "削除中",
"Im sure  Delete": "間違いありません – 削除",
"Archiving": "アーカイブ中",
"Couldnt create the document, try again?": "Couldnt create the document, try again?",
"Document permanently deleted": "Document permanently deleted",
"Are you sure you want to permanently delete the <em>{{ documentTitle }}</em> document? This action is immediate and cannot be undone.": "Are you sure you want to permanently delete the <em>{{ documentTitle }}</em> document? This action is immediate and cannot be undone.",
"Couldnt create the document, try again?": "ドキュメントを作成できませんでした。もう一度やり直してください。",
"Document permanently deleted": "ドキュメントが完全に削除されました。",
"Are you sure you want to permanently delete the <em>{{ documentTitle }}</em> document? This action is immediate and cannot be undone.": "<em>{{ documentTitle }}</em> ドキュメントを完全に削除してもよろしいですか?この操作は即時に反映され、元に戻すことはできません。",
"Search documents": "ドキュメントの検索",
"No documents found for your filters.": "検索結果はありませんでした。",
"Youve not got any drafts at the moment.": "この時は下書きがない。",
@@ -337,7 +338,6 @@
"Move current document": "現在のドキュメントを移動する",
"Jump to search": "検索へ",
"Jump to home": "ホームへ移動",
"Table of contents": "目次",
"Toggle navigation": "ナビゲーションの切り替え",
"Focus search input": "検索窓にフォーカスする",
"Open this guide": "ガイドを開く",
@@ -397,7 +397,7 @@
"All groups": "すべてのグループ",
"No groups have been created yet": "グループはまだ作成されていません",
"Import started": "インポートを開始しました",
"Export in progress…": "エクスポートが進行中...",
"Export in progress…": "エクスポートが進行中",
"It is possible to import a zip file of folders and Markdown files previously exported from an Outline instance. Support will soon be added for importing from other services.": "OutlineからエクスポートしたフォルダーとMarkdownファイルを含むzipファイルをインポートすることができます。他のサービスからのインポートについては、間もなくサポートが追加される予定です。",
"Your file has been uploaded and the import is currently being processed, you can safely leave this page while it completes.": "ファイルのアップロードが成功し、現在インポート処理を行っています。完了後、このページを離れることが出来ます。",
"Sorry, the file <em>{{ fileName }}</em> is missing valid collections or documents.": "ファイル <em>{{ fileName }}</em> は有効なコレクションまたはドキュメントがありません。",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "문서 경로",
"Group member options": "그룹 멤버 옵션",
"Remove": "제거",
"Collection": "컬렉션",
"New document": "새 문서",
"Import document": "문서 가져 오기",
"Edit": "편집",
"Permissions": "권한",
"Delete": "삭제",
"Collection": "컬렉션",
"Collection permissions": "Collection permissions",
"Edit collection": "컬렉션 편집",
"Delete collection": "컬렉션 삭제",
@@ -206,6 +206,7 @@
"Share options": "공유 옵션",
"Go to document": "문서로 이동",
"Revoke link": "링크 삭제",
"Table of contents": "목차",
"By {{ author }}": "{{ author }} 작성",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "정말로 {{ userName }} 에게 관리자 권한을 부여 하시겠습니까? 관리자는 팀과 결제 정보를 수정할 수 있습니다.",
"Are you sure you want to make {{ userName }} a member?": "정말로 {{ userName }} 에게 멤버 권한을 부여 하시겠습니까?",
@@ -337,7 +338,6 @@
"Move current document": "현재 문서로 이동",
"Jump to search": "검색으로 이동",
"Jump to home": "홈으로 이동",
"Table of contents": "목차",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "이 가이드 열기",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Path to document",
"Group member options": "Group member options",
"Remove": "Verwijder",
"Collection": "Collectie",
"New document": "Nieuw document",
"Import document": "Importeer document",
"Edit": "Wijzig",
"Permissions": "Rechten",
"Delete": "Verwijder",
"Collection": "Collectie",
"Collection permissions": "Collection permissions",
"Edit collection": "Wijzig collectie",
"Delete collection": "Verwijder collectie",
@@ -206,6 +206,7 @@
"Share options": "Deel opties",
"Go to document": "Ga naar document",
"Revoke link": "Trek link in",
"Table of contents": "Table of contents",
"By {{ author }}": "Door {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
@@ -337,7 +338,6 @@
"Move current document": "Move current document",
"Jump to search": "Jump to search",
"Jump to home": "Jump to home",
"Table of contents": "Table of contents",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Open this guide",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Ścieżka do dokumentu",
"Group member options": "Group member options",
"Remove": "Usuń",
"Collection": "Kolekcja",
"New document": "Nowy dokument",
"Import document": "Zaimportuj dokument",
"Edit": "Edytuj",
"Permissions": "Uprawnienia",
"Delete": "Usuń",
"Collection": "Kolekcja",
"Collection permissions": "Uprawnienia kolekcji",
"Edit collection": "Edit collection",
"Delete collection": "Delete collection",
@@ -206,6 +206,7 @@
"Share options": "Opcje udostępniania",
"Go to document": "Przejdź do dokumentu",
"Revoke link": "Unieważnij link",
"Table of contents": "Table of contents",
"By {{ author }}": "By {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Czy na pewno chcesz uczynić {{ userName }} administratorem? Administratorzy mogą modyfikować informacje o zespole i rozliczeniach.",
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
@@ -337,7 +338,6 @@
"Move current document": "Przenieś bieżący dokument",
"Jump to search": "Jump to search",
"Jump to home": "Jump to home",
"Table of contents": "Table of contents",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Open this guide",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Caminho para o documento",
"Group member options": "Opções de membros do grupo",
"Remove": "Remover",
"Collection": "Coleção",
"New document": "Novo documento",
"Import document": "Importar documento",
"Edit": "Editar",
"Permissions": "Permissões",
"Delete": "Excluir",
"Collection": "Coleção",
"Collection permissions": "Permissões da coleção",
"Edit collection": "Editar coleção",
"Delete collection": "Excluir coleção",
@@ -206,6 +206,7 @@
"Share options": "Opções de compartilhamento",
"Go to document": "Ir para o documento",
"Revoke link": "Revogar link",
"Table of contents": "Índice",
"By {{ author }}": "Por {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Tem certeza que deseja tornar {{ userName }} um administrador? Os administradores podem modificar as informações da equipe e do faturamento.",
"Are you sure you want to make {{ userName }} a member?": "Tem certeza de que deseja tornar {{ userName }} um membro?",
@@ -337,7 +338,6 @@
"Move current document": "Mover documento atual",
"Jump to search": "Ir para pesquisa",
"Jump to home": "Ir para a tela inicial",
"Table of contents": "Índice",
"Toggle navigation": "Alternar de navegação",
"Focus search input": "Focar pesquisa",
"Open this guide": "Abrir este guia",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Path to document",
"Group member options": "Group member options",
"Remove": "Remover",
"Collection": "Coleção",
"New document": "Novo documento",
"Import document": "Importar documento",
"Edit": "Editar",
"Permissions": "Permissões",
"Delete": "Apagar",
"Collection": "Coleção",
"Collection permissions": "Collection permissions",
"Edit collection": "Editar coleção",
"Delete collection": "Apagar coleção",
@@ -206,6 +206,7 @@
"Share options": "Share options",
"Go to document": "Ir para documento",
"Revoke link": "Revogar link",
"Table of contents": "Índice de conteúdos",
"By {{ author }}": "De {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Tem a certeza que deseja tornar {{ userName }} um administrador? Os administradores podem modificar as informações de faturação e da equipa.",
"Are you sure you want to make {{ userName }} a member?": "Tem a certeza de que deseja tornar {{ userName }} um membro?",
@@ -337,7 +338,6 @@
"Move current document": "Mover documento atual",
"Jump to search": "Ir para a pesquisa",
"Jump to home": "Jump to home",
"Table of contents": "Índice de conteúdos",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Abrir este guia",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Путь к документу",
"Group member options": "Параметры участников группы",
"Remove": "Удалить",
"Collection": "Коллекция",
"New document": "Новый документ",
"Import document": "Импорт документа",
"Edit": "Редактировать",
"Permissions": "Права доступа",
"Delete": "Удалить",
"Collection": "Коллекция",
"Collection permissions": "Права доступа к коллекции",
"Edit collection": "Изменить подборку",
"Delete collection": "Удалить коллекцию",
@@ -206,6 +206,7 @@
"Share options": "Настройка доступа",
"Go to document": "Перейти к документу",
"Revoke link": "Отозвать ссылку",
"Table of contents": "Содержание",
"By {{ author }}": "По {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Удаляем админа {{ userName }}? Админы могут настраивать команду и платежную информацию.",
"Are you sure you want to make {{ userName }} a member?": "Делаем {{ userName }} участником?",
@@ -337,7 +338,6 @@
"Move current document": "Переместить текущий документ",
"Jump to search": "Перейти к поиску",
"Jump to home": "Перейти на главную",
"Table of contents": "Содержание",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Открыть эту инструкцию",
+34 -34
View File
@@ -3,37 +3,37 @@
"currently viewing": "currently viewing",
"previously edited": "previously edited",
"You": "Du",
"Viewers": "Viewers",
"Viewers": "Tittare",
"Sorry, an error occurred saving the collection": "Sorry, an error occurred saving the collection",
"Add a description": "Lägg till en beskrivning",
"Collapse": "Collapse",
"Expand": "Expand",
"Expand": "Expandera",
"Submenu": "Undermeny",
"Trash": "Papperskorg",
"Archive": "Arkiv",
"Drafts": "Utkast",
"Templates": "Mallar",
"Deleted Collection": "Deleted Collection",
"New": "New",
"New": "Ny",
"Only visible to you": "Only visible to you",
"Draft": "Utkast",
"Template": "Mall",
"New doc": "New doc",
"deleted": "deleted",
"archived": "archived",
"New doc": "Nytt dokument",
"deleted": "raderad",
"archived": "arkiverad",
"created": "skapad",
"published": "publicerad",
"saved": "sparad",
"updated": "uppdaterad",
"Never viewed": "Never viewed",
"Viewed": "Viewed",
"Viewed": "Sett",
"in": "i",
"nested document": "nested document",
"nested document_plural": "nested documents",
"Viewed by": "Viewed by",
"only you": "only you",
"only you": "endast dig",
"person": "person",
"people": "people",
"people": "människor",
"Currently editing": "Currently editing",
"Currently viewing": "Currently viewing",
"Viewed {{ timeAgo }} ago": "Viewed {{ timeAgo }} ago",
@@ -48,7 +48,7 @@
"Todo list": "Att göra-lista",
"Code block": "Kodblock",
"Copied to clipboard": "Copied to clipboard",
"Code": "Code",
"Code": "Kod",
"Create link": "Skapa länk",
"Sorry, an error occurred creating the link": "Sorry, an error occurred creating the link",
"Create a new doc": "Create a new doc",
@@ -124,7 +124,7 @@
"Profile": "Profil",
"Notifications": "Notifications",
"API Tokens": "API Tokens",
"Team": "Team",
"Team": "Lag",
"Details": "Detaljer",
"Security": "Säkerhet",
"Members": "Members",
@@ -134,15 +134,15 @@
"Export": "Exportera",
"Integrations": "Integrations",
"Installation": "Installation",
"Unstar": "Unstar",
"Star": "Star",
"Unstar": "Ta bort stjärnmärkning",
"Star": "Stjärna",
"Previous page": "Previous page",
"Next page": "Next page",
"Could not import file": "Could not import file",
"Appearance": "Utseende",
"System": "System",
"Light": "Light",
"Dark": "Dark",
"Light": "Ljus",
"Dark": "rk",
"API documentation": "API documentation",
"Changelog": "Changelog",
"Send us feedback": "Send us feedback",
@@ -151,13 +151,13 @@
"Show path to document": "Show path to document",
"Path to document": "Path to document",
"Group member options": "Group member options",
"Remove": "Remove",
"Collection": "Collection",
"Remove": "Ta bort",
"New document": "New document",
"Import document": "Import document",
"Edit": "Edit",
"Edit": "Redigera",
"Permissions": "Permissions",
"Delete": "Delete",
"Delete": "Radera",
"Collection": "Collection",
"Collection permissions": "Collection permissions",
"Edit collection": "Edit collection",
"Delete collection": "Delete collection",
@@ -173,7 +173,7 @@
"Document options": "Document options",
"Restore": "Återställ",
"Choose a collection": "Choose a collection",
"Unpin": "Unpin",
"Unpin": "Lossa",
"Pin to collection": "Pin to collection",
"Enable embeds": "Enable embeds",
"Disable embeds": "Disable embeds",
@@ -206,6 +206,7 @@
"Share options": "Share options",
"Go to document": "Go to document",
"Revoke link": "Revoke link",
"Table of contents": "Table of contents",
"By {{ author }}": "By {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
@@ -229,7 +230,7 @@
"Manage permissions": "Manage permissions",
"This collection is only visible to those given access": "This collection is only visible to those given access",
"Private": "Private",
"Pinned": "Pinned",
"Pinned": "Fastsatt",
"Recently updated": "Recently updated",
"Recently published": "Recently published",
"Least recently updated": "Least recently updated",
@@ -242,12 +243,12 @@
"Public document sharing": "Public document sharing",
"When enabled, documents can be shared publicly on the internet.": "When enabled, documents can be shared publicly on the internet.",
"Public sharing is currently disabled in the team security settings.": "Public sharing is currently disabled in the team security settings.",
"Saving": "Saving",
"Saving": "Sparar",
"Save": "Spara",
"Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your documents. They work best when organized around a topic or internal team — Product or Engineering for example.",
"This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.": "This is the default level of access given to team members, you can give specific users or groups more access once the collection is created.",
"Creating": "Creating",
"Create": "Create",
"Create": "Skapa",
"{{ groupName }} was added to the collection": "{{ groupName }} was added to the collection",
"Could not add user": "Could not add user",
"Cant find the group youre looking for?": "Cant find the group youre looking for?",
@@ -297,7 +298,7 @@
"Publishing": "Publishing",
"Nested documents": "Nested documents",
"Anyone with the link <1></1>can view this document": "Anyone with the link <1></1>can view this document",
"Share": "Share",
"Share": "Dela",
"Share this document": "Share this document",
"This document is shared because the parent <em>{{ documentTitle }}</em> is publicly shared": "This document is shared because the parent <em>{{ documentTitle }}</em> is publicly shared",
"Publish to internet": "Publish to internet",
@@ -337,7 +338,6 @@
"Move current document": "Move current document",
"Jump to search": "Jump to search",
"Jump to home": "Jump to home",
"Table of contents": "Table of contents",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Open this guide",
@@ -352,8 +352,8 @@
"Small header": "Small header",
"Underline": "Underline",
"Undo": "Ångra",
"Redo": "Redo",
"Lists": "Lists",
"Redo": "Upprepa",
"Lists": "Listor",
"Indent list item": "Indent list item",
"Outdent list item": "Outdent list item",
"Move list item up": "Move list item up",
@@ -373,25 +373,25 @@
"All documents": "All documents",
"Include documents that are in the archive": "Include documents that are in the archive",
"Any author": "Any author",
"Author": "Author",
"Author": "Författare",
"Not Found": "Not Found",
"We were unable to find the page youre looking for.": "We were unable to find the page youre looking for.",
"Use the <em>{{ meta }}+K</em> shortcut to search from anywhere in your knowledge base": "Use the <em>{{ meta }}+K</em> shortcut to search from anywhere in your knowledge base",
"No documents found for your search filters. <1></1>": "No documents found for your search filters. <1></1>",
"Create a new document?": "Create a new document?",
"Clear filters": "Clear filters",
"Email": "Email",
"Email": "E-post",
"Last active": "Last active",
"Role": "Role",
"Viewer": "Viewer",
"Role": "Roll",
"Viewer": "Läs",
"Suspended": "Suspended",
"Shared": "Shared",
"Shared": "Delad",
"by {{ name }}": "by {{ name }}",
"Last accessed": "Last accessed",
"Add to Slack": "Add to Slack",
"Active": "Active",
"Active": "Aktiv",
"Everyone": "Everyone",
"Admins": "Admins",
"Admins": "Administratörer",
"New group": "New group",
"Groups can be used to organize and manage the people on your team.": "Groups can be used to organize and manage the people on your team.",
"All groups": "All groups",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "Path to document",
"Group member options": "Group member options",
"Remove": "Remove",
"Collection": "Collection",
"New document": "New document",
"Import document": "Import document",
"Edit": "Edit",
"Permissions": "Permissions",
"Delete": "Delete",
"Collection": "Collection",
"Collection permissions": "Collection permissions",
"Edit collection": "Edit collection",
"Delete collection": "Delete collection",
@@ -206,6 +206,7 @@
"Share options": "Share options",
"Go to document": "Go to document",
"Revoke link": "Revoke link",
"Table of contents": "Table of contents",
"By {{ author }}": "By {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.",
"Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?",
@@ -337,7 +338,6 @@
"Move current document": "Move current document",
"Jump to search": "Jump to search",
"Jump to home": "Jump to home",
"Table of contents": "Table of contents",
"Toggle navigation": "Toggle navigation",
"Focus search input": "Focus search input",
"Open this guide": "Open this guide",
+2 -2
View File
@@ -152,12 +152,12 @@
"Path to document": "文件路径",
"Group member options": "小组成员选项",
"Remove": "移除",
"Collection": "文档集",
"New document": "新建文档",
"Import document": "导入文档",
"Edit": "编辑",
"Permissions": "权限",
"Delete": "删除",
"Collection": "文档集",
"Collection permissions": "文档集权限",
"Edit collection": "编辑文档集",
"Delete collection": "删除文档集",
@@ -206,6 +206,7 @@
"Share options": "分享选项",
"Go to document": "转到文档",
"Revoke link": "撤消链接",
"Table of contents": "目录",
"By {{ author }}": "创建人 {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "您确定要设置 {{ userName }} 为管理员吗?管理员可以修改团队和帐单信息。",
"Are you sure you want to make {{ userName }} a member?": "您确定要设置 {{ userName }} 为成员吗?",
@@ -337,7 +338,6 @@
"Move current document": "移动当前文档",
"Jump to search": "跳转到搜索",
"Jump to home": "跳到页",
"Table of contents": "目录",
"Toggle navigation": "切换导航",
"Focus search input": "聚焦搜索结果",
"Open this guide": "打开指南",
+11 -11
View File
@@ -32,8 +32,8 @@
"nested document_plural": "子文件",
"Viewed by": "已瀏覽過",
"only you": "僅限自己",
"person": "person",
"people": "people",
"person": "",
"people": "",
"Currently editing": "正在編輯",
"Currently viewing": "正在瀏覽",
"Viewed {{ timeAgo }} ago": "{{ timeAgo }} 前瀏覽過",
@@ -57,8 +57,8 @@
"Delete table": "刪除表格",
"Delete image": "刪除圖片",
"Download image": "下載圖片",
"Float left": "Float left",
"Float right": "Float right",
"Float left": "靠左",
"Float right": "靠右",
"Center large": "Center large",
"Italic": "斜體",
"Sorry, that link wont work for this embed type": "抱歉,不支援將這個連結嵌入到文件",
@@ -152,12 +152,12 @@
"Path to document": "文件路徑",
"Group member options": "群組成員選項",
"Remove": "移除",
"Collection": "文件集",
"New document": "建立新文件",
"Import document": "匯入文件",
"Edit": "編輯",
"Permissions": "權限設定",
"Delete": "刪除",
"Collection": "文件集",
"Collection permissions": "文件集權限",
"Edit collection": "編輯文件集",
"Delete collection": "刪除文件集",
@@ -206,9 +206,10 @@
"Share options": "分享設定",
"Go to document": "跳轉到文件",
"Revoke link": "註銷連結",
"Table of contents": "目錄",
"By {{ author }}": "由 {{ author }}",
"Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "你確定要將 {{ userName }} 設為管理員嗎?管理員可以修改團隊及帳單資訊。",
"Are you sure you want to make {{ userName }} a member?": "您卻將要將 {{ userName }} 設定設為成員之一嗎?",
"Are you sure you want to make {{ userName }} a member?": "您確定要將 {{ userName }} 設定設為成員之一嗎?",
"Are you sure you want to make {{ userName }} a read-only viewer? They will not be able to edit any content": "你確定要將 {{ userName }} 設定為唯讀檢視者嗎?他們將無法編輯任何內容。",
"Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "你確定要停用這個使用者帳號嗎?遭到停用的使用者將無法登入。",
"User options": "使用者選項",
@@ -255,15 +256,15 @@
"Search by group name": "依照群組名稱搜尋",
"Search groups": "搜尋群組",
"No groups matching your search": "找不到符合搜尋條件的群組",
"No groups left to add": "No groups left to add",
"No groups left to add": "所有群組都已經被新增",
"Add": "新增",
"{{ userName }} was added to the collection": "{{ userName }} 已經被新增到文件集",
"Need to add someone whos not yet on the team yet?": "Need to add someone whos not yet on the team yet?",
"Need to add someone whos not yet on the team yet?": "需要新增尚未加入團隊的人嗎?",
"Invite people to {{ teamName }}": "邀請他人加入 {{ teamName }}",
"Search by name": "依名稱搜尋",
"Search people": "搜尋使用者",
"No people matching your search": "找不到符合搜尋條件的使用者",
"No people left to add": "No people left to add",
"No people left to add": "所有人員都已經被新增",
"Active <1></1> ago": "最後活動於 <1></1> 前",
"Never signed in": "從未登入",
"Invited": "已邀請",
@@ -285,7 +286,7 @@
"Add people": "新增人員",
"Add specific access for individual groups and team members": "為個別群組和團隊成員增加特定的存取權限",
"Add groups to {{ collectionName }}": "將群組加入到 {{ collectionName }}",
"Add people to {{ collectionName }}": "將使用者加入到 {{ collectionName }}",
"Add people to {{ collectionName }}": "將人員加入到 {{ collectionName }}",
"Hide contents": "隱藏內容",
"Show contents": "顯示內容",
"Edit {{noun}}": "編輯 {{noun}}",
@@ -337,7 +338,6 @@
"Move current document": "移動目前文件",
"Jump to search": "跳轉到搜尋",
"Jump to home": "跳轉到首頁",
"Table of contents": "目錄",
"Toggle navigation": "切換導覽",
"Focus search input": "Focus search input",
"Open this guide": "Open this guide",
+25 -1
View File
@@ -18,7 +18,19 @@ export const fadeAndScaleIn = keyframes`
}
`;
export const fadeAndSlideIn = keyframes`
export const fadeAndSlideDown = keyframes`
from {
opacity: 0;
transform: scale(.98) translateY(-10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0px);
}
`;
export const fadeAndSlideUp = keyframes`
from {
opacity: 0;
transform: scale(.98) translateY(10px);
@@ -30,6 +42,18 @@ export const fadeAndSlideIn = keyframes`
}
`;
export const mobileContextMenu = keyframes`
from {
opacity: 0;
transform: scale(.98) translateY(10vh);
}
to {
opacity: 1;
transform: scale(1) translateY(0px);
}
`;
export const bounceIn = keyframes`
from,
20%,
-3
View File
@@ -57,9 +57,6 @@ module.exports = {
new webpack.DefinePlugin({
EDITOR_VERSION: JSON.stringify(pkg.version)
}),
new webpack.ProvidePlugin({
fetch: 'imports-loader?this=>global!exports-loader?global.fetch!isomorphic-fetch',
}),
new webpack.IgnorePlugin(/unicode\/category\/So/),
new HtmlWebpackPlugin({
template: 'server/static/index.html',
+89 -46
View File
@@ -1040,7 +1040,7 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
"@emotion/is-prop-valid@^0.8.8":
"@emotion/is-prop-valid@^0.8.2", "@emotion/is-prop-valid@^0.8.8":
version "0.8.8"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
@@ -4323,6 +4323,13 @@ cron-parser@^2.13.0:
is-nan "^1.3.0"
moment-timezone "^0.5.31"
cross-fetch@^3.0.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
dependencies:
node-fetch "2.6.1"
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -4861,6 +4868,11 @@ domhandler@^4.0.0, domhandler@^4.2.0:
dependencies:
domelementtype "^2.2.0"
domino@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe"
integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==
domutils@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
@@ -5040,13 +5052,6 @@ encodeurl@^1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
encoding@^0.1.11:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
dependencies:
iconv-lite "^0.6.2"
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
@@ -5764,6 +5769,11 @@ feature-policy@0.3.0:
resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069"
integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==
fetch-retry@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-4.1.1.tgz#fafe0bb22b54f4d0a9c788dff6dd7f8673ca63f3"
integrity sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==
fetch-test-server@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/fetch-test-server/-/fetch-test-server-1.2.0.tgz#65f23af1d030c293249a49bbd1b51e45fc68eb69"
@@ -6004,6 +6014,26 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
framer-motion@^4.1.17:
version "4.1.17"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.17.tgz#4029469252a62ea599902e5a92b537120cc89721"
integrity sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==
dependencies:
framesync "5.3.0"
hey-listen "^1.0.8"
popmotion "9.3.6"
style-value-types "4.1.4"
tslib "^2.1.0"
optionalDependencies:
"@emotion/is-prop-valid" "^0.8.2"
framesync@5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/framesync/-/framesync-5.3.0.tgz#0ecfc955e8f5a6ddc8fdb0cc024070947e1a0d9b"
integrity sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==
dependencies:
tslib "^2.1.0"
fresh@~0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -6601,6 +6631,11 @@ helmet@^3.21.1:
referrer-policy "1.2.0"
x-xss-protection "1.3.0"
hey-listen@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
hide-powered-by@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.1.0.tgz#be3ea9cab4bdb16f8744be873755ca663383fa7a"
@@ -6866,13 +6901,6 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
ieee754@1.1.13:
version "1.1.13"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
@@ -7391,7 +7419,7 @@ is-retry-allowed@^1.0.0:
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -7489,14 +7517,6 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
isomorphic-fetch@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isomorphic-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4"
@@ -7661,6 +7681,14 @@ jest-environment-node@^26.6.2:
jest-mock "^26.6.2"
jest-util "^26.6.2"
jest-fetch-mock@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b"
integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==
dependencies:
cross-fetch "^3.0.4"
promise-polyfill "^8.1.3"
jest-get-type@^26.3.0:
version "26.3.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
@@ -7985,7 +8013,7 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
jsdom@^16.2.0, jsdom@^16.4.0:
jsdom@^16.4.0:
version "16.4.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb"
integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==
@@ -9424,24 +9452,11 @@ node-fetch@2.6.1, node-fetch@^2.1.2, node-fetch@^2.6.1:
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
node-gyp-build@^3.8.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25"
integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==
node-htmldiff@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/node-htmldiff/-/node-htmldiff-0.9.3.tgz#020704e381597e5e449a4708996edf23eebb7fcc"
integrity sha1-AgcE44FZfl5EmkcImW7fI+67f8w=
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -10362,6 +10377,16 @@ polished@3.6.5:
dependencies:
"@babel/runtime" "^7.9.2"
popmotion@9.3.6:
version "9.3.6"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.3.6.tgz#b5236fa28f242aff3871b9e23721f093133248d1"
integrity sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==
dependencies:
framesync "5.3.0"
hey-listen "^1.0.8"
style-value-types "4.1.4"
tslib "^2.1.0"
popper.js@^1.14.7:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
@@ -10488,6 +10513,11 @@ promise-map-series@^0.2.1:
dependencies:
rsvp "^3.0.14"
promise-polyfill@^8.1.3:
version "8.2.0"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0"
integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==
promise.prototype.finally@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067"
@@ -11706,7 +11736,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -12632,6 +12662,14 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
style-value-types@4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"
integrity sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==
dependencies:
hey-listen "^1.0.8"
tslib "^2.1.0"
styled-components-breakpoint@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/styled-components-breakpoint/-/styled-components-breakpoint-2.1.1.tgz#37c1b92b0e96c1bbc5d293724d7a114daaa15fca"
@@ -13108,7 +13146,7 @@ tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3, tslib@^2.2.0:
tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
@@ -13130,12 +13168,12 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
turndown@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/turndown/-/turndown-6.0.0.tgz#c083d6109a9366be1b84b86b20af09140ea4b413"
integrity sha512-UVJBhSyRHCpNKtQ00mNWlYUM/i+tcipkb++F0PrOpt0L7EhNd0AX9mWEpL2dRFBu7LWXMp4HgAMA4OeKKnN7og==
turndown@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.1.1.tgz#96992f2d9b40a1a03d3ea61ad31b5a5c751ef77f"
integrity sha512-BEkXaWH7Wh7e9bd2QumhfAXk5g34+6QUmmWx+0q6ThaVOLuLUqsnkq35HQ5SBHSaxjSfSM7US5o4lhJNH7B9MA==
dependencies:
jsdom "^16.2.0"
domino "^2.1.6"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
@@ -13524,6 +13562,11 @@ utf8@^2.1.0:
resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96"
integrity sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=
utf8@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
utif@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759"
@@ -13902,7 +13945,7 @@ whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@>=0.10.0, whatwg-fetch@^3.4.1:
whatwg-fetch@^3.4.1:
version "3.5.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"
integrity sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==