De#B?t`tc+_a5u@*dfD^U8EwXx{yDWUyi
zrpQe8<4Zkz)QtMuOzN)jw#?iWI;k--8;Qg<)U)ffK;U_|$qBf;-Y-u14NnPohP(uI
zpFDRxyWq2~oRes*Mrvz8>HsW$!3{@P9V%nacfD|TZ28QYDdMi?&n29jhS2s(&qq?{T2xbDCJRD-5<)lJdPSMx{V@TZ!WwsV
z+`!YFb;u~O7D4>1PU{WNFA+RMRXt)Jx6y#+xY2VVx#4vz2C*xE)ub(PWh67Rf^VFl
ztXPK;_7}w_9574u{#%kd^@^&Xe3&C@#+7VZ!t*blbJYQKR$Tyi5kKMzG|^JEf&_g8
zaEc8qrKqa4esiTxUEeoyXyZ1x=_?!h^JG1zw014L!NePtZwzj4KLEenBTEnhOnGAxpWHPXP%k)dF{UgZ2v&t
zW4ko9kKK)6JtKYqmfqqtHO!{nWu0T+@+z>{vp6rewM?T@h@ANAE7H=QsFkNc2
z6F(1wlJwF;CYHcbUu5}EzDzJZ)abH(r*x!#EvOH>i9<<)xWl9ZW>EGORB;NVaz0xg
zNZ7r^kxk#(3@A`v$4z%3Nw~9qrI|DmpQ(AZq(m6=7%8M1&|eSU#YETpW5#=Xu|6BxD&f@8G
zv$CnR*`b9`o2bDr`4(pyD8z}2JC)|Qj#SDY0>{tdh7NVHa*y2j^o|*LTnr>S|R3hkq
z!z~T->{{pE@^~1&EMq$_ubo_e)}Lwb#{B}Rx-VR*P|!!=?L3jr@7x`^S=Bi(XbLR&
z08vph)FwgRJWPUB{LUmqvgL7bp?JZ&&=}0i(TIbgwFmZZAWi}bt_q;79}6spqAbg^
z()M@FzFtC&PIFsLjJ3a{%FC1Ngs>ty&
z$(^XmmATilMDcd4Zb#+qp{cy(4~F%2ES_xtJ*N8lJ2x8cgDG1F{wr;L>@cj1<75)_
zzNDCbjoJv;AyEAOa}WVN8w*#Zr-I7NCG9GX?>m}g$u3b-WIZ+cndRp|jfvK;-+If)
za_`M&`=wKJer8Z`emRMuagzxnf<>h_vQEL>JE{LeTmH|A9(+P`#qAlYXJdS
z5VBm67RRCOWs@JH9SVwlG_Kj{938p-+wJt;;{2U}O7~}!VAklVU!l{Xypb+0
za61cP<=)Oba8&;MFAqkjs7HthYM}PrNMfOzmhRUSa$SAwWg-w{xgY(_hRl9>I$xxZ
z!0#2_+K|42Yc#kqleI%23dCG%S*CTmrOdd>h+Pv$s9=T9gdsQEO|?3fg}wQ1_&GtS
zRn!+X=eq#95h(QQ&R3xK`0;^WW7WLoSRrZAV}$=tc+#*pO#Q|gO{8;0zs0Nv)TKJ;
z*vcuO?}^rP+ZFaQrJa#@t%YXw!_-erS2#hhyqfyFKiEN{xro0mN1R}4F_e63`D`VbN(KlD!
zgS5%g)eO~%kT;(&u;l$r`Ki=`fH?!kKl&d`T`n(2nQwkm$PshxqJfH7@kdeaK5KPs
zFJ??bVas6t-yyq!-|mL(4u8!H#8HdJtF(TOp&a|;&GD($iIW;EZxjIxewH{atFUn$3gAZYgSGr@0;Uoh$C{bRQzk4KXv2&^fnt
zPNE@29#rMnkDqB$jiQHbsIU-QKAo?UNMV3DzOb_BVBk8;mxnO_xWOnSmJRXh1ny;a
zlUFih4@jDB;BWTCF8a+MMe$ey}Q~?v5oG-!UsP
zQKCZs^PG}lA?mH$E2ck_83&}w%=OrUqqNDhL}r7LK;Ofg?9@2P!}AwI+CM5LPJ(=K
zxCQStQriLwlsdij`Aul-bi$N`*3w?NC}7^wXgois!_*H@LWpe=zO0bD;RetIsEHKEHo%bf3+5dw7)!(}wSp;xf$ylBV>RsIr=$}LiOGhM
zTc1M==oPQhX2srxrNQ%*^eq^KuyFL#mnmgGHZA;2H6Vy^>olluhosTt&
zYE9tlLcm#z?c{^ilDMi~mOr1nXoBEAf9Bt-Kk+fWAH=LRdwP;YGz6kDX+?U+^X?~%
z?Mne=uka)O&w_a&^O$Ifr2itZ8OTTN$EyRVP2KpV)*G;-fF=`
zgz-~Wz#Q2#RE4*Ic`30?%f_Ntl7j9ch9hF5e!i4^GR@mQ5Q#laC#b0HC3p^uGhbGM
z=6bm9cW}ks`IEt{(y$xgVwG}c+Z!%~%HI%ewuR8>2b!4Dy`QT?6
zHLePFUO5}U`VmkTmY$gNA&*y-yTNZK$`&50$`x-e%B>YX+b|esMM@V`-ioO7yRysL
znJHEE>fofWW{A>y!+`#N3B@XgwpR;nrpPyXKw9NtlEpWERP*3uxMoO&b3ADPUdq4|<6|@tdx^Z}auJfsR{L|Kc!j)CuA@cC;
zVCF(=b@ei12rCvX=xfm
literal 0
HcmV?d00001
From c8d305aeca918b71c57184a6acbf11089fcca7dc Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Thu, 14 Jan 2021 19:50:10 -0800
Subject: [PATCH 041/109] fix: Unintended scroll reset when switching between
view / edit (#1807)
* fix: Don't remount document when switching between edit/read-only
fix: Button vertical alignment when using as=Link
* fix: Bump RME, fixes issue with image behavior changing between read-only/edit without editor remount
* fix: Heading anchor positioning
---
app/components/Button.js | 2 +-
app/components/Editor.js | 4 +++
app/scenes/Document/components/DataLoader.js | 2 --
app/scenes/Document/components/Document.js | 10 ++++----
app/scenes/Document/components/Header.js | 26 ++++++--------------
package.json | 2 +-
yarn.lock | 12 ++++-----
7 files changed, 25 insertions(+), 33 deletions(-)
diff --git a/app/components/Button.js b/app/components/Button.js
index 14b878a90..7f434f37c 100644
--- a/app/components/Button.js
+++ b/app/components/Button.js
@@ -102,7 +102,7 @@ export const Inner = styled.span`
line-height: ${(props) => (props.hasIcon ? 24 : 32)}px;
justify-content: center;
align-items: center;
- min-height: 30px;
+ min-height: 32px;
${(props) => props.hasIcon && props.hasText && "padding-left: 4px;"};
${(props) => props.hasIcon && !props.hasText && "padding: 0 4px;"};
diff --git a/app/components/Editor.js b/app/components/Editor.js
index ee5f1c900..67d0cce4b 100644
--- a/app/components/Editor.js
+++ b/app/components/Editor.js
@@ -172,6 +172,10 @@ const StyledEditor = styled(RichMarkdownEditor)`
font-weight: 500;
}
+ .heading-anchor {
+ box-sizing: border-box;
+ }
+
.heading-name {
pointer-events: none;
}
diff --git a/app/scenes/Document/components/DataLoader.js b/app/scenes/Document/components/DataLoader.js
index 46cdcdca9..91278f432 100644
--- a/app/scenes/Document/components/DataLoader.js
+++ b/app/scenes/Document/components/DataLoader.js
@@ -240,13 +240,11 @@ class DataLoader extends React.Component {
}
const abilities = policies.abilities(document.id);
- const key = this.isEditing ? "editing" : "read-only";
return (
{this.isEditing && }
{
@observable title: string = this.props.document.title;
getEditorText: () => string = () => this.props.document.text;
- componentDidMount() {
- this.updateIsDirty();
- }
-
componentDidUpdate(prevProps) {
const { auth, document } = this.props;
+ if (prevProps.readOnly && !this.props.readOnly) {
+ this.updateIsDirty();
+ }
+
if (this.props.readOnly) {
this.lastRevision = document.revision;
@@ -440,7 +440,7 @@ class DocumentScene extends React.Component {
ui={this.props.ui}
/>
- {readOnly && !isShare && !revision && (
+ {!isShare && !revision && (
<>
diff --git a/app/scenes/Document/components/Header.js b/app/scenes/Document/components/Header.js
index 5c272c4a6..c775ea8a3 100644
--- a/app/scenes/Document/components/Header.js
+++ b/app/scenes/Document/components/Header.js
@@ -12,7 +12,7 @@ import {
import { transparentize, darken } from "polished";
import * as React from "react";
import { withTranslation, Trans, type TFunction } from "react-i18next";
-import { Redirect } from "react-router-dom";
+import { Link } from "react-router-dom";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import AuthStore from "stores/AuthStore";
@@ -63,7 +63,6 @@ type Props = {
class Header extends React.Component {
@observable isScrolled = false;
@observable showShareModal = false;
- @observable redirectTo: ?string;
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
@@ -79,18 +78,6 @@ class Header extends React.Component {
handleScroll = throttle(this.updateIsScrolled, 50);
- handleEdit = () => {
- this.redirectTo = editDocumentUrl(this.props.document);
- };
-
- handleNewFromTemplate = () => {
- const { document } = this.props;
-
- this.redirectTo = newDocumentUrl(document.collectionId, {
- templateId: document.id,
- });
- };
-
handleSave = () => {
this.props.onSave({ done: true });
};
@@ -118,8 +105,6 @@ class Header extends React.Component {
};
render() {
- if (this.redirectTo) return ;
-
const {
shares,
document,
@@ -276,8 +261,9 @@ class Header extends React.Component {
placement="bottom"
>
}
- onClick={this.handleEdit}
+ to={editDocumentUrl(this.props.document)}
neutral
small
>
@@ -309,7 +295,10 @@ class Header extends React.Component {
}
- onClick={this.handleNewFromTemplate}
+ as={Link}
+ to={newDocumentUrl(document.collectionId, {
+ templateId: document.id,
+ })}
primary
small
>
@@ -423,6 +412,7 @@ const Title = styled.div`
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
+ cursor: pointer;
display: none;
width: 0;
diff --git a/package.json b/package.json
index 990dca856..10408602f 100644
--- a/package.json
+++ b/package.json
@@ -153,7 +153,7 @@
"react-waypoint": "^9.0.2",
"react-window": "^1.8.6",
"reakit": "^1.3.4",
- "rich-markdown-editor": "^11.0.11",
+ "rich-markdown-editor": "^11.1.2",
"semver": "^7.3.2",
"sequelize": "^6.3.4",
"sequelize-cli": "^6.2.0",
diff --git a/yarn.lock b/yarn.lock
index 5932e3471..9eac4d379 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8871,7 +8871,7 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
-outline-icons@^1.21.0, outline-icons@^1.23.0:
+outline-icons@^1.23.0:
version "1.23.0"
resolved "https://registry.yarnpkg.com/outline-icons/-/outline-icons-1.23.0.tgz#46c9655f7f7f77900349ca39d4676dd99550e3ea"
integrity sha512-p4QZ1KIOnkB3qju1+NNJxQPsmVOibqOBp7AksFas7Igchg94plR/fnDbs9Oe2uBzD64PApR4ypU/pe6mkMGong==
@@ -10472,15 +10472,15 @@ retry-as-promised@^3.2.0:
dependencies:
any-promise "^1.3.0"
-rich-markdown-editor@^11.0.11:
- version "11.0.11"
- resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.0.11.tgz#7f29f0956a81618966866d4c05219910e317a7d1"
- integrity sha512-2p7kETZSDCene8DqV0WgGNeSgaK3V8uRIXUK9gJAORl6zaieuY0TQu3GKIYkblxxeJYeMGp/kQeDJRrBYnRDBg==
+rich-markdown-editor@^11.1.2:
+ version "11.1.2"
+ resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.1.2.tgz#2c31cf23ed9e580be6706b980beeaab8ffae13c0"
+ integrity sha512-g5j6OAl2mHgbf+m8/RbuBP3ozNHBFpFxclx0AQjfC4m53h+HcGVznBUIZaXeOPu0DqAB2Rrzt+3fVsBxoRCT7w==
dependencies:
copy-to-clipboard "^3.0.8"
lodash "^4.17.11"
markdown-it-container "^3.0.0"
- outline-icons "^1.21.0"
+ outline-icons "^1.23.0"
prismjs "^1.19.0"
prosemirror-commands "^1.1.4"
prosemirror-dropcursor "^1.3.2"
From ab40545a01d1b7ea85c5a75a45bc0c369c1621a3 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Thu, 14 Jan 2021 20:11:04 -0800
Subject: [PATCH 042/109] lint
---
app/components/LocaleTime.js | 8 +++++++-
app/embeds/index.js | 2 +-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/app/components/LocaleTime.js b/app/components/LocaleTime.js
index 4097465b3..a16d6ffb9 100644
--- a/app/components/LocaleTime.js
+++ b/app/components/LocaleTime.js
@@ -38,7 +38,13 @@ type Props = {
shorten?: boolean,
};
-function LocaleTime({ addSuffix, children, dateTime, shorten, tooltipDelay }: Props) {
+function LocaleTime({
+ addSuffix,
+ children,
+ dateTime,
+ shorten,
+ tooltipDelay,
+}: Props) {
const userLocale = useUserLocale();
const [_, setMinutesMounted] = React.useState(0); // eslint-disable-line no-unused-vars
const callback = React.useRef();
diff --git a/app/embeds/index.js b/app/embeds/index.js
index 2cd0522c6..97d8b8c94 100644
--- a/app/embeds/index.js
+++ b/app/embeds/index.js
@@ -8,8 +8,8 @@ import Codepen from "./Codepen";
import Figma from "./Figma";
import Framer from "./Framer";
import Gist from "./Gist";
-import GoogleDrive from "./GoogleDrive";
import GoogleDocs from "./GoogleDocs";
+import GoogleDrive from "./GoogleDrive";
import GoogleSheets from "./GoogleSheets";
import GoogleSlides from "./GoogleSlides";
import InVision from "./InVision";
From 1af00a0b3d30546e02f789ac346de3732d1c1c79 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Thu, 14 Jan 2021 20:15:46 -0800
Subject: [PATCH 043/109] test
---
app/embeds/GoogleDrive.test.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/embeds/GoogleDrive.test.js b/app/embeds/GoogleDrive.test.js
index d672fe47d..3339c4262 100644
--- a/app/embeds/GoogleDrive.test.js
+++ b/app/embeds/GoogleDrive.test.js
@@ -31,7 +31,7 @@ describe("GoogleDrive", () => {
"https://drive.google.com/file/d/1ohkOgmE8MiNx68u6ynBfYkgjeKu_x3ZK/view?usp=restricted".match(
match
)
- ).toBeTruthy();
+ ).toBe(null);
expect("https://drive.google.com/file".match(match)).toBe(null);
expect("https://drive.google.com".match(match)).toBe(null);
expect("https://www.google.com".match(match)).toBe(null);
From 1fd2ec31fd9cba2d5d6c090db2ea79d6875fede7 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Fri, 15 Jan 2021 08:50:19 -0800
Subject: [PATCH 044/109] fix: Heading positioning changing between
edit/read-only fix: List items beyond #9 chopped
---
app/components/Editor.js | 13 ++++---------
package.json | 2 +-
yarn.lock | 8 ++++----
3 files changed, 9 insertions(+), 14 deletions(-)
diff --git a/app/components/Editor.js b/app/components/Editor.js
index 67d0cce4b..b16effbc9 100644
--- a/app/components/Editor.js
+++ b/app/components/Editor.js
@@ -178,15 +178,10 @@ const StyledEditor = styled(RichMarkdownEditor)`
.heading-name {
pointer-events: none;
- }
-
- /* pseudo element allows us to add spacing for fixed header */
- /* ref: https://stackoverflow.com/a/28824157 */
- .heading-name::before {
- content: "";
- display: ${(props) => (props.readOnly ? "block" : "none")};
- height: 60px;
- margin: -60px 0 0;
+ display: block;
+ position: relative;
+ top: -60px;
+ visibility: hidden;
}
.heading-name:first-child {
diff --git a/package.json b/package.json
index 10408602f..4de5f386b 100644
--- a/package.json
+++ b/package.json
@@ -153,7 +153,7 @@
"react-waypoint": "^9.0.2",
"react-window": "^1.8.6",
"reakit": "^1.3.4",
- "rich-markdown-editor": "^11.1.2",
+ "rich-markdown-editor": "^11.1.3",
"semver": "^7.3.2",
"sequelize": "^6.3.4",
"sequelize-cli": "^6.2.0",
diff --git a/yarn.lock b/yarn.lock
index 9eac4d379..472a862fe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10472,10 +10472,10 @@ retry-as-promised@^3.2.0:
dependencies:
any-promise "^1.3.0"
-rich-markdown-editor@^11.1.2:
- version "11.1.2"
- resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.1.2.tgz#2c31cf23ed9e580be6706b980beeaab8ffae13c0"
- integrity sha512-g5j6OAl2mHgbf+m8/RbuBP3ozNHBFpFxclx0AQjfC4m53h+HcGVznBUIZaXeOPu0DqAB2Rrzt+3fVsBxoRCT7w==
+rich-markdown-editor@^11.1.3:
+ version "11.1.3"
+ resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.1.3.tgz#19873d1489e159543691131f387882712f0c709e"
+ integrity sha512-ivN/gs/M14KtrViP+MYAWn127JBTfMWgqbn5HWWUPc/el/vUbdBwquOvzjJ/kFppbdolFQMzz+xmdnoP7Zxqog==
dependencies:
copy-to-clipboard "^3.0.8"
lodash "^4.17.11"
From 522df125aaed8d4d01dfe957a59488e0ef24feca Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Sat, 16 Jan 2021 11:12:10 -0800
Subject: [PATCH 045/109] feat: Add CDN support (#1817)
* chore: CSP
* chore: Optionally use CDN for serving images
---
.env.sample | 7 +++++
app/components/Editor.js | 2 +-
app/components/HoverPreview.js | 2 +-
app/components/Image.js | 12 +++++++++
app/components/PageTitle.js | 19 ++++++-------
app/embeds/GoogleDocs.js | 3 ++-
app/embeds/GoogleDrive.js | 3 ++-
app/embeds/GoogleSheets.js | 3 ++-
app/embeds/GoogleSlides.js | 3 ++-
app/embeds/index.js | 3 ++-
app/scenes/Document/components/DataLoader.js | 2 +-
app/utils/{isInternalUrl.js => urls.js} | 11 +++++++-
server/app.js | 28 ++++++++++++++------
server/emails/components/Header.js | 4 ++-
server/env.js | 1 +
webpack.config.prod.js | 2 +-
16 files changed, 77 insertions(+), 28 deletions(-)
create mode 100644 app/components/Image.js
rename app/utils/{isInternalUrl.js => urls.js} (61%)
diff --git a/.env.sample b/.env.sample
index e4c505d05..dc8f8011d 100644
--- a/.env.sample
+++ b/.env.sample
@@ -10,9 +10,14 @@ DATABASE_URL=postgres://user:pass@localhost:5532/outline
DATABASE_URL_TEST=postgres://user:pass@localhost:5532/outline-test
REDIS_URL=redis://localhost:6479
+# Must point to the publicly accessible URL for the installation
URL=http://localhost:3000
PORT=3000
+# Optional. If using a Cloudfront distribution or similar the origin server
+# should be set to the same as URL.
+CDN_URL=
+
# enforce (auto redirect to) https in production, (optional) default is true.
# set to false if your SSL is terminated at a loadbalancer, for example
FORCE_HTTPS=true
@@ -66,4 +71,6 @@ SMTP_REPLY_EMAIL=
# Custom logo that displays on the authentication screen, scaled to height: 60px
# TEAM_LOGO=https://example.com/images/logo.png
+# See translate.getoutline.com for a list of available language codes and their
+# percentage translated.
DEFAULT_LANGUAGE=en_US
\ No newline at end of file
diff --git a/app/components/Editor.js b/app/components/Editor.js
index b16effbc9..378d41bd3 100644
--- a/app/components/Editor.js
+++ b/app/components/Editor.js
@@ -8,9 +8,9 @@ import UiStore from "stores/UiStore";
import ErrorBoundary from "components/ErrorBoundary";
import Tooltip from "components/Tooltip";
import embeds from "../embeds";
-import isInternalUrl from "utils/isInternalUrl";
import { isMetaKey } from "utils/keyboard";
import { uploadFile } from "utils/uploadFile";
+import { isInternalUrl } from "utils/urls";
const RichMarkdownEditor = React.lazy(() => import("rich-markdown-editor"));
diff --git a/app/components/HoverPreview.js b/app/components/HoverPreview.js
index ba73224a5..39b8a3d84 100644
--- a/app/components/HoverPreview.js
+++ b/app/components/HoverPreview.js
@@ -8,7 +8,7 @@ import { fadeAndSlideIn } from "shared/styles/animations";
import parseDocumentSlug from "shared/utils/parseDocumentSlug";
import DocumentsStore from "stores/DocumentsStore";
import HoverPreviewDocument from "components/HoverPreviewDocument";
-import isInternalUrl from "utils/isInternalUrl";
+import { isInternalUrl } from "utils/urls";
const DELAY_OPEN = 300;
const DELAY_CLOSE = 300;
diff --git a/app/components/Image.js b/app/components/Image.js
new file mode 100644
index 000000000..13eb4f588
--- /dev/null
+++ b/app/components/Image.js
@@ -0,0 +1,12 @@
+// @flow
+import * as React from "react";
+import { cdnPath } from "utils/urls";
+
+type Props = {
+ alt: string,
+ src: string,
+};
+
+export default function Image({ src, alt, ...rest }: Props) {
+ return
;
+}
diff --git a/app/components/PageTitle.js b/app/components/PageTitle.js
index a91ef21b9..537d8690d 100644
--- a/app/components/PageTitle.js
+++ b/app/components/PageTitle.js
@@ -1,16 +1,17 @@
// @flow
-import { observer, inject } from "mobx-react";
+import { observer } from "mobx-react";
import * as React from "react";
import { Helmet } from "react-helmet";
-import AuthStore from "stores/AuthStore";
+import useStores from "hooks/useStores";
+import { cdnPath } from "utils/urls";
-type Props = {
+type Props = {|
title: string,
favicon?: string,
- auth: AuthStore,
-};
+|};
-const PageTitle = observer(({ auth, title, favicon }: Props) => {
+const PageTitle = ({ title, favicon }: Props) => {
+ const { auth } = useStores();
const { team } = auth;
return (
@@ -21,12 +22,12 @@ const PageTitle = observer(({ auth, title, favicon }: Props) => {
);
-});
+};
-export default inject("auth")(PageTitle);
+export default observer(PageTitle);
diff --git a/app/embeds/GoogleDocs.js b/app/embeds/GoogleDocs.js
index 491a8a57d..fd5a42633 100644
--- a/app/embeds/GoogleDocs.js
+++ b/app/embeds/GoogleDocs.js
@@ -1,5 +1,6 @@
// @flow
import * as React from "react";
+import Image from "components/Image";
import Frame from "./components/Frame";
const URL_REGEX = new RegExp("^https?://docs.google.com/document/(.*)$");
@@ -20,7 +21,7 @@ export default class GoogleDocs extends React.Component {
{...this.props}
src={this.props.attrs.href.replace("/edit", "/preview")}
icon={
-
{
{
{...this.props}
src={this.props.attrs.href.replace("/edit", "/preview")}
icon={
-
{
.replace("/edit", "/preview")
.replace("/pub", "/embed")}
icon={
-
{
return (
@@ -12,7 +14,7 @@ export default () => {
diff --git a/server/env.js b/server/env.js
index 1f74cd8d6..f4d26c063 100644
--- a/server/env.js
+++ b/server/env.js
@@ -1,6 +1,7 @@
// @flow
export default {
URL: process.env.URL,
+ CDN_URL: process.env.CDN_URL || "",
DEPLOYMENT: process.env.DEPLOYMENT,
SENTRY_DSN: process.env.SENTRY_DSN,
TEAM_LOGO: process.env.TEAM_LOGO,
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index 2a6ccfc15..ee61f9016 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -10,7 +10,7 @@ productionWebpackConfig = Object.assign(commonWebpackConfig, {
output: {
path: path.join(__dirname, 'build/app'),
filename: '[name].[contenthash].js',
- publicPath: '/static/',
+ publicPath: `${process.env.CDN_URL || ""}/static/`,
},
cache: true,
mode: "production",
From 51b75fa09d4d63bcaeead1aabce37e4ae22ea450 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Sat, 16 Jan 2021 14:12:35 -0800
Subject: [PATCH 046/109] 0.52.0
---
package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 4de5f386b..de64599a2 100644
--- a/package.json
+++ b/package.json
@@ -209,5 +209,5 @@
"dot-prop": "^5.2.0",
"js-yaml": "^3.13.1"
},
- "version": "0.51.0"
-}
\ No newline at end of file
+ "version": "0.52.0"
+}
From f33495dddc4d7d0f336d8b68e794b74510f23cc2 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Sun, 17 Jan 2021 18:32:32 -0800
Subject: [PATCH 047/109] fix: Editor mod shortcuts not working on Windows
closes #1745
---
package.json | 4 ++--
yarn.lock | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index de64599a2..dee247aeb 100644
--- a/package.json
+++ b/package.json
@@ -153,7 +153,7 @@
"react-waypoint": "^9.0.2",
"react-window": "^1.8.6",
"reakit": "^1.3.4",
- "rich-markdown-editor": "^11.1.3",
+ "rich-markdown-editor": "^11.1.4",
"semver": "^7.3.2",
"sequelize": "^6.3.4",
"sequelize-cli": "^6.2.0",
@@ -210,4 +210,4 @@
"js-yaml": "^3.13.1"
},
"version": "0.52.0"
-}
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 472a862fe..1ded221c6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10472,10 +10472,10 @@ retry-as-promised@^3.2.0:
dependencies:
any-promise "^1.3.0"
-rich-markdown-editor@^11.1.3:
- version "11.1.3"
- resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.1.3.tgz#19873d1489e159543691131f387882712f0c709e"
- integrity sha512-ivN/gs/M14KtrViP+MYAWn127JBTfMWgqbn5HWWUPc/el/vUbdBwquOvzjJ/kFppbdolFQMzz+xmdnoP7Zxqog==
+rich-markdown-editor@^11.1.4:
+ version "11.1.4"
+ resolved "https://registry.yarnpkg.com/rich-markdown-editor/-/rich-markdown-editor-11.1.4.tgz#a43e2ca0e198b06bdb4b5c9000f8fe9d47bab2cc"
+ integrity sha512-RWXI5XJ0HwM/zYTZuKy9pJvROS7PbOk3mcjCEltNTcW2bH/xWRDu8vhtLchdX5fSQsp9JsmL2QW+koTiUtby5g==
dependencies:
copy-to-clipboard "^3.0.8"
lodash "^4.17.11"
From afcce7a0efca59459c9be85260c93ca755b97170 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Sun, 17 Jan 2021 21:49:51 -0800
Subject: [PATCH 048/109] fix: Add missing width/height tags to img
---
app/components/Sidebar/components/HeaderBlock.js | 7 ++++++-
app/components/TeamLogo.js | 6 ++++--
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/app/components/Sidebar/components/HeaderBlock.js b/app/components/Sidebar/components/HeaderBlock.js
index d0cd1e12e..1786c2fcf 100644
--- a/app/components/Sidebar/components/HeaderBlock.js
+++ b/app/components/Sidebar/components/HeaderBlock.js
@@ -16,7 +16,12 @@ const HeaderBlock = React.forwardRef(
({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => {
return (
-
+
{teamName}{" "}
diff --git a/app/components/TeamLogo.js b/app/components/TeamLogo.js
index 400c7f2f4..ae703ac77 100644
--- a/app/components/TeamLogo.js
+++ b/app/components/TeamLogo.js
@@ -2,8 +2,10 @@
import styled from "styled-components";
const TeamLogo = styled.img`
- width: ${(props) => props.size || "auto"};
- height: ${(props) => props.size || "38px"};
+ width: ${(props) =>
+ props.width ? `${props.width}px` : props.size || "auto"};
+ height: ${(props) =>
+ props.height ? `${props.height}px` : props.size || "38px"};
border-radius: 4px;
background: ${(props) => props.theme.background};
border: 1px solid ${(props) => props.theme.divider};
From 27fca284509878475e9b3a5fffde289d05b58a71 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Sun, 17 Jan 2021 22:19:54 -0800
Subject: [PATCH 049/109] fix: Account for rehydrated old users before language
closes #1819
---
app/hooks/useUserLocale.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/hooks/useUserLocale.js b/app/hooks/useUserLocale.js
index 721fca6e4..e786dff1e 100644
--- a/app/hooks/useUserLocale.js
+++ b/app/hooks/useUserLocale.js
@@ -4,7 +4,7 @@ import useStores from "./useStores";
export default function useUserLocale() {
const { auth } = useStores();
- if (!auth.user) {
+ if (!auth.user || !auth.user.language) {
return undefined;
}
From 3bace8c9e463043e08c2481f3e16e5d88b6a9868 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Mon, 18 Jan 2021 15:48:46 -0800
Subject: [PATCH 050/109] fix: Restore DNS prefetching for static resources
(#1820)
* fix: Restore DNS prefetching for static resources
* fix: CDN paths
feat: preload instead of prefetch for key bundles
* csp
* fix: Turns out prefetch-src is still behind a flag in Chrome, not publicly available yet
---
package.json | 2 +-
server/app.js | 8 ++--
server/routes.js | 2 +
server/static/index.html | 1 +
server/utils/prefetchTags.js | 79 ++++++++++++++++++++----------------
webpack.config.prod.js | 4 +-
yarn.lock | 33 ++++++++++-----
7 files changed, 76 insertions(+), 53 deletions(-)
diff --git a/package.json b/package.json
index dee247aeb..713983869 100644
--- a/package.json
+++ b/package.json
@@ -203,7 +203,7 @@
"url-loader": "^0.6.2",
"webpack": "4.44.1",
"webpack-cli": "^3.3.12",
- "webpack-manifest-plugin": "^2.2.0"
+ "webpack-manifest-plugin": "^3.0.0"
},
"resolutions": {
"dot-prop": "^5.2.0",
diff --git a/server/app.js b/server/app.js
index 505519076..56b86b77f 100644
--- a/server/app.js
+++ b/server/app.js
@@ -23,21 +23,21 @@ const isProduction = process.env.NODE_ENV === "production";
const isTest = process.env.NODE_ENV === "test";
// Construct scripts CSP based on services in use by this installation
+const defaultSrc = ["'self'"];
const scriptSrc = [
"'self'",
"'unsafe-inline'",
"'unsafe-eval'",
"gist.github.com",
+ "browser.sentry-cdn.com",
];
if (env.GOOGLE_ANALYTICS_ID) {
scriptSrc.push("www.google-analytics.com");
}
-if (env.SENTRY_DSN) {
- scriptSrc.push("browser.sentry-cdn.com");
-}
if (env.CDN_URL) {
scriptSrc.push(env.CDN_URL);
+ defaultSrc.push(env.CDN_URL);
}
app.use(compress());
@@ -167,7 +167,7 @@ app.use(helmet());
app.use(
contentSecurityPolicy({
directives: {
- defaultSrc: ["'self'"],
+ defaultSrc,
scriptSrc,
styleSrc: ["'self'", "'unsafe-inline'", "github.githubassets.com"],
imgSrc: ["*", "data:", "blob:"],
diff --git a/server/routes.js b/server/routes.js
index b17920196..0efae32c9 100644
--- a/server/routes.js
+++ b/server/routes.js
@@ -10,6 +10,7 @@ import { languages } from "../shared/i18n";
import environment from "./env";
import apexRedirect from "./middlewares/apexRedirect";
import { opensearchResponse } from "./utils/opensearch";
+import prefetchTags from "./utils/prefetchTags";
import { robotsResponse } from "./utils/robots";
const isProduction = process.env.NODE_ENV === "production";
@@ -50,6 +51,7 @@ const renderApp = async (ctx, next) => {
ctx.body = page
.toString()
.replace(/\/\/inject-env\/\//g, env)
+ .replace(/\/\/inject-prefetch\/\//g, prefetchTags)
.replace(/\/\/inject-sentry-dsn\/\//g, process.env.SENTRY_DSN || "")
.replace(/\/\/inject-slack-app-id\/\//g, process.env.SLACK_APP_ID || "");
};
diff --git a/server/static/index.html b/server/static/index.html
index db53cc129..4b6c67ecd 100644
--- a/server/static/index.html
+++ b/server/static/index.html
@@ -4,6 +4,7 @@
Outline
+ //inject-prefetch//
,
-];
+if (process.env.AWS_S3_UPLOAD_BUCKET_URL) {
+ prefetchTags.push(
+
+ );
+}
+let manifestData = {};
try {
const manifest = fs.readFileSync(
path.join(__dirname, "../../app/manifest.json"),
"utf8"
);
- const manifestData = JSON.parse(manifest);
- Object.values(manifestData).forEach((filename) => {
- if (typeof filename !== "string") return;
- if (filename.endsWith(".js")) {
- prefetchTags.push(
-
- );
- } else if (filename.endsWith(".css")) {
- prefetchTags.push(
-
- );
- }
- });
-} catch (_e) {
- // no-op
+ manifestData = JSON.parse(manifest);
+} catch (err) {
+ console.warn(err);
}
-export default prefetchTags;
+Object.values(manifestData).forEach((filename) => {
+ if (typeof filename !== "string") return;
+ if (!env.CDN_URL) return;
+
+ if (filename.endsWith(".js")) {
+ // Preload resources you have high-confidence will be used in the current
+ // page.Prefetch resources likely to be used for future navigations
+ const shouldPreload =
+ filename.includes("/main") ||
+ filename.includes("/runtime") ||
+ filename.includes("/vendors");
+
+ prefetchTags.push(
+
+ );
+ } else if (filename.endsWith(".css")) {
+ prefetchTags.push(
+
+ );
+ }
+});
+
+export default ReactDOMServer.renderToString(prefetchTags);
diff --git a/webpack.config.prod.js b/webpack.config.prod.js
index ee61f9016..60a285b6c 100644
--- a/webpack.config.prod.js
+++ b/webpack.config.prod.js
@@ -1,7 +1,7 @@
/* eslint-disable */
const path = require('path');
const webpack = require('webpack');
-const ManifestPlugin = require('webpack-manifest-plugin');
+const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const TerserPlugin = require('terser-webpack-plugin');
commonWebpackConfig = require('./webpack.config');
@@ -42,7 +42,7 @@ productionWebpackConfig = Object.assign(commonWebpackConfig, {
productionWebpackConfig.plugins = [
...productionWebpackConfig.plugins,
- new ManifestPlugin()
+ new WebpackManifestPlugin()
];
module.exports = productionWebpackConfig;
diff --git a/yarn.lock b/yarn.lock
index 1ded221c6..aa15eec21 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7946,7 +7946,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5:
+lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
@@ -8728,7 +8728,7 @@ object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.1:
has-symbols "^1.0.1"
object-keys "^1.1.1"
-object.entries@^1.1.0, object.entries@^1.1.2:
+object.entries@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add"
integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==
@@ -11071,7 +11071,7 @@ sort-keys@^2.0.0:
dependencies:
is-plain-obj "^1.0.0"
-source-list-map@^2.0.0:
+source-list-map@^2.0.0, source-list-map@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
@@ -11552,6 +11552,11 @@ tapable@^1.0.0, tapable@^1.1.3:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
+tapable@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b"
+ integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==
+
tar@^6.0.2:
version "6.0.5"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
@@ -12599,15 +12604,13 @@ webpack-hot-middleware@2.x:
querystring "^0.2.0"
strip-ansi "^3.0.0"
-webpack-manifest-plugin@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz#19ca69b435b0baec7e29fbe90fb4015de2de4f16"
- integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==
+webpack-manifest-plugin@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-3.0.0.tgz#426644300e5dc41a75a9c996c4d4f876eb3c2b5b"
+ integrity sha512-nbORTdky2HxD8XSaaT+zrsHb30AAgyWAWgCLWaAeQO21VGCScGb52ipqlHA/njix1Z8OW8IOlo4+XK0OKr1fkw==
dependencies:
- fs-extra "^7.0.0"
- lodash ">=3.5 <5"
- object.entries "^1.1.0"
- tapable "^1.0.0"
+ tapable "^2.0.0"
+ webpack-sources "^2.2.0"
webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
version "1.4.3"
@@ -12617,6 +12620,14 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
source-list-map "^2.0.0"
source-map "~0.6.1"
+webpack-sources@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac"
+ integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==
+ dependencies:
+ source-list-map "^2.0.1"
+ source-map "^0.6.1"
+
webpack@4.44.1:
version "4.44.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.1.tgz#17e69fff9f321b8f117d1fda714edfc0b939cc21"
From 22fb464b879a748485aa37f532831be5a73fd2e6 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Mon, 18 Jan 2021 16:11:48 -0800
Subject: [PATCH 051/109] lint
---
server/utils/prefetchTags.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/utils/prefetchTags.js b/server/utils/prefetchTags.js
index 73066b218..8fdcda291 100644
--- a/server/utils/prefetchTags.js
+++ b/server/utils/prefetchTags.js
@@ -25,7 +25,7 @@ try {
);
manifestData = JSON.parse(manifest);
} catch (err) {
- console.warn(err);
+ // no-op
}
Object.values(manifestData).forEach((filename) => {
From eff9544ef9ab04c8e8e200028c8b62a4c9557bf2 Mon Sep 17 00:00:00 2001
From: Translate-O-Tron
<75237327+outline-translations@users.noreply.github.com>
Date: Wed, 20 Jan 2021 21:36:03 -0800
Subject: [PATCH 052/109] New Crowdin updates (#1810)
* fix: New Chinese Simplified translations from Crowdin [ci skip]
* fix: New Thai translations from Crowdin [ci skip]
* fix: New Italian translations from Crowdin [ci skip]
---
shared/i18n/locales/it_IT/translation.json | 321 +++++++++++++++++++++
shared/i18n/locales/th_TH/translation.json | 321 +++++++++++++++++++++
shared/i18n/locales/zh_CN/translation.json | 26 +-
3 files changed, 655 insertions(+), 13 deletions(-)
create mode 100644 shared/i18n/locales/it_IT/translation.json
create mode 100644 shared/i18n/locales/th_TH/translation.json
diff --git a/shared/i18n/locales/it_IT/translation.json b/shared/i18n/locales/it_IT/translation.json
new file mode 100644
index 000000000..43e6e3927
--- /dev/null
+++ b/shared/i18n/locales/it_IT/translation.json
@@ -0,0 +1,321 @@
+{
+ "currently editing": "attualmente in modifica",
+ "currently viewing": "attualmente visualizzato",
+ "viewed {{ timeAgo }} ago": "visualizzato {{ timeAgo }} fa",
+ "You": "Tu",
+ "Trash": "Cestino",
+ "Archive": "Archivio",
+ "Drafts": "Bozze",
+ "Templates": "Templates",
+ "Deleted Collection": "Raccolte Eliminate",
+ "Submenu": "Sottomenu",
+ "New": "Nuovo",
+ "Only visible to you": "Visibile solo a te",
+ "Draft": "Bozza",
+ "Template": "Template",
+ "New doc": "Nuovo documento",
+ "deleted": "cancellato",
+ "archived": "archiviato",
+ "created": "creato",
+ "published": "pubblicato",
+ "saved": "salvato",
+ "updated": "aggiornato",
+ "Never viewed": "Mai visualizzati",
+ "Viewed": "Visti",
+ "in": "in",
+ "Insert column after": "Inserisci colonna dopo",
+ "Insert column before": "Inserisci colonna prima",
+ "Insert row after": "Inserisci riga sotto",
+ "Insert row before": "Inserisci riga sopra",
+ "Align center": "Allinea al centro",
+ "Align left": "Allinea a sinistra",
+ "Align right": "Allinea a destra",
+ "Bulleted list": "Elenco puntato",
+ "Todo list": "Cose da fare",
+ "Code block": "Blocco di codice",
+ "Copied to clipboard": "Copiato negli appunti",
+ "Code": "Codice",
+ "Create link": "Crea collegamento",
+ "Sorry, an error occurred creating the link": "Spiacenti, si è verificato un errore durante la creazione del collegamento",
+ "Create a new doc": "Crea un nuovo documento",
+ "Delete column": "Elimina colonna",
+ "Delete row": "Elimina riga",
+ "Delete table": "Elimina tabella",
+ "Italic": "Corsivo",
+ "Sorry, that link won’t work for this embed type": "Siamo spiacenti, questo link non funzionerà per questo tipo di embed",
+ "Find or create a doc": "Trova o crea un documento",
+ "Big heading": "Titolo grande",
+ "Medium heading": "Titolo medio",
+ "Small heading": "Titolo piccolo",
+ "Heading": "Titolo",
+ "Divider": "Divisore",
+ "Image": "Immagine",
+ "Sorry, an error occurred uploading the image": "Spiacenti, si è verificato un errore durante il caricamento dell'immagine",
+ "Info": "Informazioni",
+ "Info notice": "Nota informativa",
+ "Link": "Collegamento",
+ "Link copied to clipboard": "Link copiato negli appunti",
+ "Highlight": "In evidenza",
+ "Type '/' to insert": "Digita \"/\" per inserire",
+ "Keep typing to filter": "Continua a digitare per filtrare",
+ "No results": "Nessun risultato",
+ "Open link": "Apri collegamento",
+ "Ordered list": "Elenco ordinato",
+ "Paste a link": "Incolla un link",
+ "Paste a {{service}} link…": "Incolla un link {{service}}…",
+ "Placeholder": "Segnaposto",
+ "Quote": "Citazione",
+ "Remove link": "Elimina link",
+ "Search or paste a link": "Cerca o incolla un link",
+ "Strikethrough": "Testo barrato",
+ "Bold": "Grassetto",
+ "Subheading": "Sottotitolo",
+ "Table": "Tabella",
+ "Tip": "Suggerimento",
+ "Tip notice": "Nota di suggerimento",
+ "Warning": "Avviso",
+ "Warning notice": "Nota di avviso",
+ "Icon": "Icona",
+ "Choose icon": "Scegli icona",
+ "Loading": "Caricamento in corso",
+ "Search": "Cerca",
+ "Outline is available in your language {{optionLabel}}, would you like to change?": "Outline è disponibile nella tua lingua {{optionLabel}}, vuoi cambiare?",
+ "Change Language": "Cambia Lingua",
+ "Dismiss": "Chiudi",
+ "Keyboard shortcuts": "Scorciatoie tastiera",
+ "Expand": "Espandi",
+ "Collapse": "Raggruppa",
+ "New collection": "Nuova raccolta",
+ "Collections": "Raccolta",
+ "Untitled": "Senza titolo",
+ "Home": "Home",
+ "Starred": "Preferiti",
+ "Invite people…": "Invita altri…",
+ "Invite people": "Invita persone",
+ "Create a collection": "Crea raccolta",
+ "Return to App": "Torna all'app",
+ "Profile": "Profilo",
+ "Notifications": "Notifiche",
+ "API Tokens": "Token API",
+ "Details": "Dettagli",
+ "Security": "Sicurezza",
+ "People": "Utenti",
+ "Groups": "Gruppi",
+ "Share Links": "Condividi link",
+ "Export Data": "Esporta Dati",
+ "Integrations": "Integrazioni",
+ "Installation": "Installazione",
+ "Appearance": "Aspetto",
+ "System": "Sistema",
+ "Light": "Chiaro",
+ "Dark": "Scuro",
+ "Account": "Account",
+ "Settings": "Impostazioni",
+ "API documentation": "Documentazione API",
+ "Changelog": "Log delle modifiche",
+ "Send us feedback": "Inviaci il tuo feedback",
+ "Report a bug": "Segnala un bug",
+ "Log out": "Log out",
+ "Path to document": "Percorso del documento",
+ "Group member options": "Opzioni membri del gruppo",
+ "Members": "Membri",
+ "Remove": "Elimina",
+ "Collection": "Raccolta",
+ "New document": "Nuovo documento",
+ "Import document": "Importa documento",
+ "Edit": "Modifica",
+ "Permissions": "Permessi",
+ "Export": "Esporta",
+ "Delete": "Cancella",
+ "Collection permissions": "Permessi raccolta",
+ "Edit collection": "Modifica raccolta",
+ "Delete collection": "Elimina raccolta",
+ "Export collection": "Esporta raccolta",
+ "Sort in sidebar": "Ordina nella barra laterale",
+ "Alphabetical sort": "Ordine Alfabetico",
+ "Manual sort": "Ordinamento manuale",
+ "Document duplicated": "Documento duplicato",
+ "Document archived": "Documento archiviato",
+ "Document restored": "Documento ripristinato",
+ "Document unpublished": "Documento depubblicato",
+ "Document options": "Opzioni documento",
+ "Restore": "Ripristina",
+ "Choose a collection": "Scegli una raccolta",
+ "Unpin": "Rimuovi contrassegno",
+ "Pin to collection": "Fissa alla raccolta",
+ "Unstar": "Rimuovi dai Preferiti",
+ "Star": "Preferito",
+ "Share link": "Condividi link",
+ "Enable embeds": "Abilita l'embedding",
+ "Disable embeds": "Disabilita l'embedding",
+ "New nested document": "Nuovo documento annidato",
+ "Create template": "Crea un modello",
+ "Duplicate": "Duplica",
+ "Unpublish": "Depubblica",
+ "Move": "Sposta",
+ "History": "Cronologia",
+ "Download": "Download",
+ "Print": "Stampa",
+ "Delete {{ documentName }}": "Elimina {{ documentName }}",
+ "Share document": "Condividi documento",
+ "Edit group": "Modifica gruppo",
+ "Delete group": "Elimina gruppo",
+ "Group options": "Opzioni del gruppo",
+ "Member options": "Opzioni membro",
+ "collection": "raccolta",
+ "New child document": "Nuovo documento annidato",
+ "New document in <1>{{collectionName}}1>": "Nuovo documento in <1>{{collectionName}}1>",
+ "New template": "Nuovo modello",
+ "Link copied": "Collegamento copiato",
+ "Revision options": "Opzioni revisione",
+ "Restore version": "Ripristina versione",
+ "Copy link": "Copia link",
+ "Share link revoked": "Link condivisione revocato",
+ "Share link copied": "Link di condivisione copiato",
+ "Share options": "Opzioni di condivisione",
+ "Go to document": "Vai al documento",
+ "Revoke link": "Revoca il link",
+ "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?",
+ "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "Sei sicuro di voler sospendere questo account? Agli utenti sospesi verrà impedito l'accesso.",
+ "User options": "Opzioni utente",
+ "Make {{ userName }} a member…": "Rendi {{ userName }} un membro…",
+ "Make {{ userName }} an admin…": "Rendi {{ userName }} un amministratore…",
+ "Revoke invite": "Revoca invito",
+ "Activate account": "Attiva account",
+ "Suspend account": "Sospendi account",
+ "Documents": "Documenti",
+ "The document archive is empty at the moment.": "L'archivio documenti è vuoto al momento.",
+ "Search in collection": "Cerca nella raccolta",
+ "<0>{{collectionName}}0> doesn’t contain any documents yet.": "<0>{{collectionName}}0> non contiene ancora nessun documento.",
+ "Get started by creating a new one!": "Inizia creandone uno nuovo!",
+ "Create a document": "Crea documento",
+ "Manage members": "Gestisci membri",
+ "Pinned": "In Evidenza",
+ "Recently updated": "Aggiornati di recente",
+ "Recently published": "Pubblicati di recente",
+ "Least recently updated": "Aggiornati meno recentemente",
+ "A–Z": "A – Z",
+ "The collection was updated": "La raccolta è stata aggiornata",
+ "You can edit the name and other details at any time, however doing so often might confuse your team mates.": "Puoi modificare il nome e altri dettagli in qualsiasi momento, tuttavia farlo spesso potrebbe confondere i membri del tuo team.",
+ "Name": "Nome",
+ "Description": "Descrizione",
+ "More details about this collection…": "Maggiori dettagli su questa raccolta…",
+ "Alphabetical": "Alfabetico",
+ "Private collection": "Raccolta privata",
+ "A private collection will only be visible to invited team members.": "Una raccolta privata sarà visibile solo ai membri del team invitati.",
+ "Saving": "Salvataggio",
+ "Save": "Salva",
+ "{{ groupName }} was added to the collection": "{{ groupName }} stato aggiunto alla raccolta",
+ "Could not add user": "Impossibile aggiungere l'utente",
+ "Can’t find the group you’re looking for?": "Non trovi il gruppo che stai cercando?",
+ "Create a group": "Crea un gruppo",
+ "Search by group name": "Cerca nome gruppo",
+ "Search groups": "Cerca gruppi",
+ "No groups matching your search": "Nessun gruppo corrisponde alla tua ricerca",
+ "No groups left to add": "Nessun gruppo rimasto da aggiungere",
+ "Add": "Aggiungi",
+ "{{ userName }} was added to the collection": "{{ userName }} stato aggiunto alla raccolta",
+ "Need to add someone who’s not yet on the team yet?": "Hai bisogno di aggiungere qualcuno che non fa ancora parte del team?",
+ "Invite people to {{ teamName }}": "Invita utenti a {{ teamName }}",
+ "Search by name": "Cerca per nome",
+ "Search people": "Cerca persone",
+ "No people matching your search": "Nessuna persona corrisponde alla tua ricerca",
+ "No people left to add": "Non sono rimaste persone da aggiungere",
+ "Read only": "Sola lettura",
+ "Read & Edit": "Leggi & Modifica",
+ "Active <1>1> ago": "Attivo <1>1> fa",
+ "Never signed in": "Mai effettuato l'accesso",
+ "Invited": "Invitato",
+ "Admin": "Amministratore",
+ "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.": "Le collezioni servono a raggruppare la tua knowledge base. Funzionano meglio se organizzati intorno a un argomento o ad un team interno — ad esempio prodotti o ingegneria.",
+ "Creating": "Creazione",
+ "Create": "Crea",
+ "Recently viewed": "Visualizzati di recente",
+ "Created by me": "Creato da me",
+ "Hide contents": "Nascondi contenuti",
+ "Show contents": "Mostra contenuti",
+ "Archived": "Archiviati",
+ "Anyone with the link <1>1>can view this document": "Chiunque abbia il link <1>1> può visualizzare questo contenuto",
+ "Share": "Condividi",
+ "Save Draft": "Salva Bozza",
+ "Done Editing": "Modifica Completata",
+ "Edit {{noun}}": "Modifica {{noun}}",
+ "New from template": "Nuovo da template",
+ "Publish": "Pubblica",
+ "Publish document": "Pubblica documento",
+ "Publishing": "In fase di pubblicazione",
+ "Are you sure you want to delete the <2>{{documentTitle}}2> template?": "Sei sicuro di voler eliminare il template <2>{{documentTitle}}2>?",
+ "Are you sure about that? Deleting the <2>{{documentTitle}}2> document will delete all of its history and any nested documents.": "Sei sicuro di questo? L'eliminazione del documento <2>{{documentTitle}}2> eliminerà tutta la sua cronologia e tutti i documenti nidificati.",
+ "If you’d like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.": "Se desideri l'opzione di fare riferimento o ripristinare {{noun}} in futuro, considera l'archiviazione.",
+ "Deleting": "Sto eliminando",
+ "I’m sure – Delete": "Sono sicuro - Elimina",
+ "Archiving": "Archiviazione in corso",
+ "No documents found for your filters.": "Nessun documento trovato per i tuoi filtri.",
+ "You’ve not got any drafts at the moment.": "Al momento non hai bozze.",
+ "Not found": "Non trovato",
+ "We were unable to find the page you’re looking for. Go to the <2>homepage2>?": "Non siamo riusciti a trovare la pagina che stai cercando. Vai alla <2>home page2>?",
+ "Offline": "Non in linea",
+ "We were unable to load the document while offline.": "Impossibile caricare il documento offline.",
+ "Your account has been suspended": "Il tuo account è stato sospeso",
+ "A team admin (<1>{{suspendedContactEmail}}1>) has suspended your account. To re-activate your account, please reach out to them directly.": "Un amministratore del team (<1>{{suspendedContactEmail}}1>) ha sospeso il tuo account. Per riattivare il tuo account, contattalo direttamente.",
+ "{{userName}} was added to the group": "{{userName}} stato aggiunto al gruppo",
+ "Add team members below to give them access to the group. Need to add someone who’s not yet on the team yet?": "Aggiungi i membri del team di seguito per concedere loro l'accesso al gruppo. Hai bisogno di aggiungere qualcuno che non fa ancora parte del team?",
+ "Invite them to {{teamName}}": "Invitali a {{teamName}}",
+ "{{userName}} was removed from the group": "{{userName}} è stato rimosso dal gruppo",
+ "Could not remove user": "Impossibile eliminare l'utente",
+ "Add people": "Aggiungi utenti",
+ "This group has no members.": "Questo gruppo non ha membri.",
+ "Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.": "Outline è progettato per essere veloce e facile da usare. Puoi utilizzare tutte le tue solite scorciatoie da tastiera, e c'è anche Markdown.",
+ "Navigation": "Navigazione",
+ "New document in current collection": "Nuovo documento nella raccolta corrente",
+ "Edit current document": "Modifica il documento corrente",
+ "Move current document": "Sposta il documento corrente",
+ "Jump to search": "Vai alla ricerca",
+ "Jump to dashboard": "Vai alla dashboard",
+ "Table of contents": "Indice contenuti",
+ "Toggle sidebar": "Attiva/Disattiva barra laterale",
+ "Open this guide": "Apri questa guida",
+ "Editor": "Editor",
+ "Save and exit document edit mode": "Salvare e uscire dalla modalità di modifica del documento",
+ "Publish and exit document edit mode": "Pubblica ed esci dalla modalità di modifica del documento",
+ "Save document and continue editing": "Salva e continua a modificare",
+ "Cancel editing": "Annulla modifica",
+ "Underline": "Sottolineato",
+ "Undo": "Annulla",
+ "Redo": "Ripristina",
+ "Markdown": "Markdown",
+ "Large header": "Intestazione grande",
+ "Medium header": "Intestazione media",
+ "Small header": "Intestazione piccola",
+ "Numbered list": "Elenco numerato",
+ "Blockquote": "Citazione",
+ "Horizontal divider": "Separatore orizzontale",
+ "Inline code": "Codice inline",
+ "Not Found": "Non trovato",
+ "We were unable to find the page you’re looking for.": "Non abbiamo trovato la pagina che stavi cercando.",
+ "Use the <1>{{meta}}+K1> shortcut to search from anywhere in your knowledge base": "Usa la scorciatoia <1>{{meta}}+K1> per cercare ovunque nella tua knowledge base",
+ "No documents found for your search filters. <1>1>Create a new document?": "Nessun documento trovato per i tuoi filtri di ricerca. <1>1> Creare un nuovo documento?",
+ "Clear filters": "Rimuovi filtri",
+ "Profile saved": "Profilo salvato",
+ "Profile picture updated": "Immagine del profilo aggiornata",
+ "Unable to upload new profile picture": "Impossibile caricare la foto profilo",
+ "Photo": "Foto",
+ "Upload": "Carica",
+ "Full name": "Nome completo",
+ "Language": "Lingua",
+ "Please note that translations are currently in early access.<1>1>Community contributions are accepted though our <4>translation portal4>": "Tieni presente che le traduzioni sono attualmente in early access. <1>1> I contributi della comunità sono accettati tramite il nostro <4> portale di traduzione4>",
+ "Delete Account": "Elimina Account",
+ "You may delete your account at any time, note that this is unrecoverable": "Puoi cancellare il tuo account in qualsiasi momento, tieni presente che l'eliminazione non è reversibile",
+ "Delete account": "Elimina l'account",
+ "You’ve not starred any documents yet.": "Non hai ancora contrassegnato alcun documento.",
+ "There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.": "Non ci sono ancora templates. Puoi creare templates per aiutare il tuo team a creare una documentazione coerente e accurata.",
+ "Trash is empty at the moment.": "Al momento il cestino è vuoto.",
+ "You joined": "Ti sei unito",
+ "Joined": "Iscritto",
+ "{{ time }} ago.": "{{ time }} fa.",
+ "Suspended": "Sospeso",
+ "Edit Profile": "Modifica profilo",
+ "{{ userName }} hasn’t updated any documents yet.": "{{ userName }} non ha ancora aggiornato nessun documento."
+}
diff --git a/shared/i18n/locales/th_TH/translation.json b/shared/i18n/locales/th_TH/translation.json
new file mode 100644
index 000000000..15089cc35
--- /dev/null
+++ b/shared/i18n/locales/th_TH/translation.json
@@ -0,0 +1,321 @@
+{
+ "currently editing": "กำลังแก้ไขอยู่",
+ "currently viewing": "กำลังดูอยู่",
+ "viewed {{ timeAgo }} ago": "viewed {{ timeAgo }} ago",
+ "You": "You",
+ "Trash": "ถังขยะ",
+ "Archive": "Archive",
+ "Drafts": "ฉบับร่าง",
+ "Templates": "Templates",
+ "Deleted Collection": "Deleted Collection",
+ "Submenu": "Submenu",
+ "New": "สร้าง",
+ "Only visible to you": "Only visible to you",
+ "Draft": "ฉบับร่าง",
+ "Template": "Template",
+ "New doc": "New doc",
+ "deleted": "deleted",
+ "archived": "archived",
+ "created": "created",
+ "published": "published",
+ "saved": "saved",
+ "updated": "updated",
+ "Never viewed": "Never viewed",
+ "Viewed": "Viewed",
+ "in": "in",
+ "Insert column after": "Insert column after",
+ "Insert column before": "Insert column before",
+ "Insert row after": "Insert row after",
+ "Insert row before": "Insert row before",
+ "Align center": "จัดตำแหน่งกึ่งกลาง",
+ "Align left": "จัดตำแหน่งชิดซ้าย",
+ "Align right": "จัดตำแหน่งชิดขวา",
+ "Bulleted list": "Bulleted list",
+ "Todo list": "Todo list",
+ "Code block": "Code block",
+ "Copied to clipboard": "คัดลอกไปยังคลิปบอร์ดแล้ว",
+ "Code": "Code",
+ "Create link": "สร้างลิงก์",
+ "Sorry, an error occurred creating the link": "Sorry, an error occurred creating the link",
+ "Create a new doc": "สร้างเอกสารใหม่",
+ "Delete column": "ลบคอลัมน์",
+ "Delete row": "ลบแถว",
+ "Delete table": "ลบตาราง",
+ "Italic": "ตัวเอียง",
+ "Sorry, that link won’t work for this embed type": "Sorry, that link won’t work for this embed type",
+ "Find or create a doc": "ค้นหาหรือสร้างเอกสาร",
+ "Big heading": "Big heading",
+ "Medium heading": "Medium heading",
+ "Small heading": "Small heading",
+ "Heading": "หัวเรื่อง",
+ "Divider": "Divider",
+ "Image": "Image",
+ "Sorry, an error occurred uploading the image": "Sorry, an error occurred uploading the image",
+ "Info": "Info",
+ "Info notice": "Info notice",
+ "Link": "Link",
+ "Link copied to clipboard": "คัดลอกลิงก์ไปยังคลิปบอร์ดแล้ว",
+ "Highlight": "Highlight",
+ "Type '/' to insert": "Type '/' to insert",
+ "Keep typing to filter": "Keep typing to filter",
+ "No results": "No results",
+ "Open link": "เปิดลิงก์",
+ "Ordered list": "Ordered list",
+ "Paste a link": "วางลิงก์",
+ "Paste a {{service}} link…": "Paste a {{service}} link…",
+ "Placeholder": "Placeholder",
+ "Quote": "Quote",
+ "Remove link": "ลบลิงก์",
+ "Search or paste a link": "ค้นหาหรือวางลิงก์",
+ "Strikethrough": "ขีดเส้นทับข้อความ",
+ "Bold": "ตัวหนา",
+ "Subheading": "หัวเรื่องย่อย",
+ "Table": "ตาราง",
+ "Tip": "เคล็ดลับ",
+ "Tip notice": "Tip notice",
+ "Warning": "คำเตือน",
+ "Warning notice": "Warning notice",
+ "Icon": "ไอคอน",
+ "Choose icon": "เลือกไอคอน",
+ "Loading": "กำลังโหลด",
+ "Search": "ค้นหา",
+ "Outline is available in your language {{optionLabel}}, would you like to change?": "Outline มีให้บริการในภาษาของคุณ ({{optionLabel}}) คุณต้องการเปลี่ยนแปลงหรือไม่",
+ "Change Language": "เปลี่ยนภาษา",
+ "Dismiss": "ปิด",
+ "Keyboard shortcuts": "ปุ่มลัดแป้นพิมพ์",
+ "Expand": "ขยาย",
+ "Collapse": "ย่อ",
+ "New collection": "คอลเลกชันใหม่",
+ "Collections": "Collections",
+ "Untitled": "ไม่มีชื่อ",
+ "Home": "หน้าแรก",
+ "Starred": "ติดดาว",
+ "Invite people…": "เชิญผู้คน…",
+ "Invite people": "เชิญผู้คน",
+ "Create a collection": "สร้างคอลเลกชัน",
+ "Return to App": "กลับไปที่แอพ",
+ "Profile": "โปรไฟล์",
+ "Notifications": "การแจ้งเตือน",
+ "API Tokens": "API Tokens",
+ "Details": "รายละเอียด",
+ "Security": "ความปลอดภัย",
+ "People": "People",
+ "Groups": "กลุ่ม",
+ "Share Links": "Share Links",
+ "Export Data": "ส่งออกข้อมูล",
+ "Integrations": "Integration",
+ "Installation": "การติดตั้ง",
+ "Appearance": "รูปร่างหน้าตา",
+ "System": "System",
+ "Light": "สว่าง",
+ "Dark": "มืด",
+ "Account": "บัญชีผู้ใช้",
+ "Settings": "การตั้งค่า",
+ "API documentation": "API documentation",
+ "Changelog": "Changelog",
+ "Send us feedback": "ส่งคำติชมถึงเรา",
+ "Report a bug": "รายงานข้อผิดพลาด",
+ "Log out": "ออกจากระบบ",
+ "Path to document": "Path to document",
+ "Group member options": "Group member options",
+ "Members": "Members",
+ "Remove": "Remove",
+ "Collection": "Collection",
+ "New document": "New document",
+ "Import document": "Import document",
+ "Edit": "Edit",
+ "Permissions": "Permissions",
+ "Export": "Export",
+ "Delete": "Delete",
+ "Collection permissions": "Collection permissions",
+ "Edit collection": "Edit collection",
+ "Delete collection": "Delete collection",
+ "Export collection": "Export collection",
+ "Sort in sidebar": "Sort in sidebar",
+ "Alphabetical sort": "Alphabetical sort",
+ "Manual sort": "Manual sort",
+ "Document duplicated": "Document duplicated",
+ "Document archived": "Document archived",
+ "Document restored": "Document restored",
+ "Document unpublished": "Document unpublished",
+ "Document options": "Document options",
+ "Restore": "Restore",
+ "Choose a collection": "Choose a collection",
+ "Unpin": "Unpin",
+ "Pin to collection": "Pin to collection",
+ "Unstar": "Unstar",
+ "Star": "Star",
+ "Share link": "แชร์ลิงก์",
+ "Enable embeds": "Enable embeds",
+ "Disable embeds": "Disable embeds",
+ "New nested document": "New nested document",
+ "Create template": "Create template",
+ "Duplicate": "Duplicate",
+ "Unpublish": "Unpublish",
+ "Move": "Move",
+ "History": "History",
+ "Download": "Download",
+ "Print": "Print",
+ "Delete {{ documentName }}": "Delete {{ documentName }}",
+ "Share document": "Share document",
+ "Edit group": "Edit group",
+ "Delete group": "Delete group",
+ "Group options": "Group options",
+ "Member options": "Member options",
+ "collection": "คอลเลกชัน",
+ "New child document": "New child document",
+ "New document in <1>{{collectionName}}1>": "New document in <1>{{collectionName}}1>",
+ "New template": "เทมเพลตใหม่",
+ "Link copied": "คัดลอกลิงก์แล้ว",
+ "Revision options": "Revision options",
+ "Restore version": "Restore version",
+ "Copy link": "คัดลอกลิงก์",
+ "Share link revoked": "Share link revoked",
+ "Share link copied": "Share link copied",
+ "Share options": "Share options",
+ "Go to document": "Go to document",
+ "Revoke link": "Revoke link",
+ "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?",
+ "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.",
+ "User options": "User options",
+ "Make {{ userName }} a member…": "Make {{ userName }} a member…",
+ "Make {{ userName }} an admin…": "Make {{ userName }} an admin…",
+ "Revoke invite": "Revoke invite",
+ "Activate account": "Activate account",
+ "Suspend account": "Suspend account",
+ "Documents": "Documents",
+ "The document archive is empty at the moment.": "The document archive is empty at the moment.",
+ "Search in collection": "Search in collection",
+ "<0>{{collectionName}}0> doesn’t contain any documents yet.": "<0>{{collectionName}}0> doesn’t contain any documents yet.",
+ "Get started by creating a new one!": "Get started by creating a new one!",
+ "Create a document": "Create a document",
+ "Manage members": "Manage members",
+ "Pinned": "Pinned",
+ "Recently updated": "Recently updated",
+ "Recently published": "Recently published",
+ "Least recently updated": "Least recently updated",
+ "A–Z": "A–Z",
+ "The collection was updated": "The collection was updated",
+ "You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.",
+ "Name": "Name",
+ "Description": "Description",
+ "More details about this collection…": "More details about this collection…",
+ "Alphabetical": "Alphabetical",
+ "Private collection": "Private collection",
+ "A private collection will only be visible to invited team members.": "A private collection will only be visible to invited team members.",
+ "Saving": "Saving",
+ "Save": "Save",
+ "{{ groupName }} was added to the collection": "{{ groupName }} was added to the collection",
+ "Could not add user": "Could not add user",
+ "Can’t find the group you’re looking for?": "Can’t find the group you’re looking for?",
+ "Create a group": "Create a group",
+ "Search by group name": "Search by group name",
+ "Search groups": "Search groups",
+ "No groups matching your search": "No groups matching your search",
+ "No groups left to add": "No groups left to add",
+ "Add": "Add",
+ "{{ userName }} was added to the collection": "{{ userName }} was added to the collection",
+ "Need to add someone who’s not yet on the team yet?": "Need to add someone who’s not yet on the team yet?",
+ "Invite people to {{ teamName }}": "Invite people to {{ teamName }}",
+ "Search by name": "Search by name",
+ "Search people": "Search people",
+ "No people matching your search": "No people matching your search",
+ "No people left to add": "No people left to add",
+ "Read only": "Read only",
+ "Read & Edit": "Read & Edit",
+ "Active <1>1> ago": "Active <1>1> ago",
+ "Never signed in": "Never signed in",
+ "Invited": "Invited",
+ "Admin": "Admin",
+ "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.",
+ "Creating": "Creating",
+ "Create": "Create",
+ "Recently viewed": "Recently viewed",
+ "Created by me": "Created by me",
+ "Hide contents": "Hide contents",
+ "Show contents": "Show contents",
+ "Archived": "Archived",
+ "Anyone with the link <1>1>can view this document": "Anyone with the link <1>1>can view this document",
+ "Share": "Share",
+ "Save Draft": "Save Draft",
+ "Done Editing": "Done Editing",
+ "Edit {{noun}}": "Edit {{noun}}",
+ "New from template": "New from template",
+ "Publish": "Publish",
+ "Publish document": "Publish document",
+ "Publishing": "Publishing",
+ "Are you sure you want to delete the <2>{{documentTitle}}2> template?": "Are you sure you want to delete the <2>{{documentTitle}}2> template?",
+ "Are you sure about that? Deleting the <2>{{documentTitle}}2> document will delete all of its history and any nested documents.": "Are you sure about that? Deleting the <2>{{documentTitle}}2> document will delete all of its history and any nested documents.",
+ "If you’d like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.": "If you’d like the option of referencing or restoring the {{noun}} in the future, consider archiving it instead.",
+ "Deleting": "Deleting",
+ "I’m sure – Delete": "I’m sure – Delete",
+ "Archiving": "Archiving",
+ "No documents found for your filters.": "No documents found for your filters.",
+ "You’ve not got any drafts at the moment.": "You’ve not got any drafts at the moment.",
+ "Not found": "Not found",
+ "We were unable to find the page you’re looking for. Go to the <2>homepage2>?": "We were unable to find the page you’re looking for. Go to the <2>homepage2>?",
+ "Offline": "Offline",
+ "We were unable to load the document while offline.": "We were unable to load the document while offline.",
+ "Your account has been suspended": "Your account has been suspended",
+ "A team admin (<1>{{suspendedContactEmail}}1>) has suspended your account. To re-activate your account, please reach out to them directly.": "A team admin (<1>{{suspendedContactEmail}}1>) has suspended your account. To re-activate your account, please reach out to them directly.",
+ "{{userName}} was added to the group": "{{userName}} was added to the group",
+ "Add team members below to give them access to the group. Need to add someone who’s not yet on the team yet?": "Add team members below to give them access to the group. Need to add someone who’s not yet on the team yet?",
+ "Invite them to {{teamName}}": "Invite them to {{teamName}}",
+ "{{userName}} was removed from the group": "{{userName}} was removed from the group",
+ "Could not remove user": "Could not remove user",
+ "Add people": "Add people",
+ "This group has no members.": "This group has no members.",
+ "Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.": "Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.",
+ "Navigation": "Navigation",
+ "New document in current collection": "New document in current collection",
+ "Edit current document": "Edit current document",
+ "Move current document": "Move current document",
+ "Jump to search": "Jump to search",
+ "Jump to dashboard": "Jump to dashboard",
+ "Table of contents": "Table of contents",
+ "Toggle sidebar": "Toggle sidebar",
+ "Open this guide": "Open this guide",
+ "Editor": "Editor",
+ "Save and exit document edit mode": "Save and exit document edit mode",
+ "Publish and exit document edit mode": "Publish and exit document edit mode",
+ "Save document and continue editing": "Save document and continue editing",
+ "Cancel editing": "Cancel editing",
+ "Underline": "Underline",
+ "Undo": "Undo",
+ "Redo": "Redo",
+ "Markdown": "Markdown",
+ "Large header": "Large header",
+ "Medium header": "Medium header",
+ "Small header": "Small header",
+ "Numbered list": "Numbered list",
+ "Blockquote": "Blockquote",
+ "Horizontal divider": "Horizontal divider",
+ "Inline code": "Inline code",
+ "Not Found": "Not Found",
+ "We were unable to find the page you’re looking for.": "We were unable to find the page you’re looking for.",
+ "Use the <1>{{meta}}+K1> shortcut to search from anywhere in your knowledge base": "Use the <1>{{meta}}+K1> shortcut to search from anywhere in your knowledge base",
+ "No documents found for your search filters. <1>1>Create a new document?": "No documents found for your search filters. <1>1>Create a new document?",
+ "Clear filters": "Clear filters",
+ "Profile saved": "Profile saved",
+ "Profile picture updated": "Profile picture updated",
+ "Unable to upload new profile picture": "Unable to upload new profile picture",
+ "Photo": "Photo",
+ "Upload": "Upload",
+ "Full name": "Full name",
+ "Language": "Language",
+ "Please note that translations are currently in early access.<1>1>Community contributions are accepted though our <4>translation portal4>": "Please note that translations are currently in early access.<1>1>Community contributions are accepted though our <4>translation portal4>",
+ "Delete Account": "Delete Account",
+ "You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable",
+ "Delete account": "Delete account",
+ "You’ve not starred any documents yet.": "You’ve not starred any documents yet.",
+ "There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.": "There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.",
+ "Trash is empty at the moment.": "Trash is empty at the moment.",
+ "You joined": "You joined",
+ "Joined": "Joined",
+ "{{ time }} ago.": "{{ time }} ago.",
+ "Suspended": "Suspended",
+ "Edit Profile": "Edit Profile",
+ "{{ userName }} hasn’t updated any documents yet.": "{{ userName }} hasn’t updated any documents yet."
+}
diff --git a/shared/i18n/locales/zh_CN/translation.json b/shared/i18n/locales/zh_CN/translation.json
index 45578bb11..c260b292f 100644
--- a/shared/i18n/locales/zh_CN/translation.json
+++ b/shared/i18n/locales/zh_CN/translation.json
@@ -8,7 +8,7 @@
"Drafts": "草稿箱",
"Templates": "文档模板",
"Deleted Collection": "删除文档集",
- "Submenu": "Submenu",
+ "Submenu": "子菜单",
"New": "新",
"Only visible to you": "只对您可见",
"Draft": "草稿",
@@ -76,7 +76,7 @@
"Warning": "警告",
"Warning notice": "警告信息",
"Icon": "图标",
- "Choose icon": "Choose icon",
+ "Choose icon": "选择图标",
"Loading": "加载中",
"Search": "搜索",
"Outline is available in your language {{optionLabel}}, would you like to change?": "Outline 当前支持 {{optionLabel}},您想要更改吗?",
@@ -109,18 +109,18 @@
"System": "系统信息",
"Light": "浅色主题",
"Dark": "深色主题",
- "Account": "Account",
+ "Account": "账号",
"Settings": "设置",
"API documentation": "API 文档",
"Changelog": "更新日志",
"Send us feedback": "反馈给我们",
"Report a bug": "提交 Bug",
"Log out": "退出登录",
- "Path to document": "Path to document",
- "Group member options": "Group member options",
+ "Path to document": "文件路径",
+ "Group member options": "小组成员选项",
"Members": "成员",
"Remove": "移除",
- "Collection": "Collection",
+ "Collection": "文档集",
"New document": "新建文档",
"Import document": "导入文档",
"Edit": "编辑",
@@ -138,7 +138,7 @@
"Document archived": "文件已归档",
"Document restored": "文档已恢复",
"Document unpublished": "未发布的文档",
- "Document options": "Document options",
+ "Document options": "文档选项",
"Restore": "恢复",
"Choose a collection": "选择一个文档集",
"Unpin": "取消置顶",
@@ -160,26 +160,26 @@
"Share document": "共享文档",
"Edit group": "编辑群组",
"Delete group": "刪除群組",
- "Group options": "Group options",
- "Member options": "Member options",
+ "Group options": "分组选项",
+ "Member options": "成员选项",
"collection": "文档集",
- "New child document": "New child document",
+ "New child document": "新的嵌套文档",
"New document in <1>{{collectionName}}1>": "在<1>{{collectionName}}1>中创建新文档",
"New template": "新建模板",
"Link copied": "链接已复制",
- "Revision options": "Revision options",
+ "Revision options": "修订选项",
"Restore version": "恢复此版本",
"Copy link": "复制链接",
"Share link revoked": "已撤销共享链接",
"Share link copied": "分享链接已复制",
- "Share options": "Share options",
+ "Share options": "分享选项",
"Go to document": "转到文档",
"Revoke link": "撤消链接",
"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 suspend this account? Suspended users will be prevented from logging in.": "您确定要冻结此帐户吗?被冻结的用户将无法登录。",
- "User options": "User options",
+ "User options": "用户选项",
"Make {{ userName }} a member…": "将 {{ userName }} 设为成员…",
"Make {{ userName }} an admin…": "将 {{ userName }} 设为管理员…",
"Revoke invite": "撤消邀请",
From f608872c1151236568a1a1765e4a0d356258f18d Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Wed, 20 Jan 2021 22:09:36 -0800
Subject: [PATCH 053/109] chore: Add Chinese and Italian translations
---
app/components/LocaleTime.js | 2 ++
shared/i18n/index.js | 2 ++
2 files changed, 4 insertions(+)
diff --git a/app/components/LocaleTime.js b/app/components/LocaleTime.js
index a16d6ffb9..71e4242c1 100644
--- a/app/components/LocaleTime.js
+++ b/app/components/LocaleTime.js
@@ -10,8 +10,10 @@ const locales = {
de: require(`date-fns/locale/de`),
es: require(`date-fns/locale/es`),
fr: require(`date-fns/locale/fr`),
+ it: require(`date-fns/locale/it`),
ko: require(`date-fns/locale/ko`),
pt: require(`date-fns/locale/pt`),
+ zh: require(`date-fns/locale/zh_cn`),
};
let callbacks = [];
diff --git a/shared/i18n/index.js b/shared/i18n/index.js
index 2f42984fa..f3fa744db 100644
--- a/shared/i18n/index.js
+++ b/shared/i18n/index.js
@@ -36,9 +36,11 @@ export const initI18n = () => {
export const languageOptions = [
{ label: "English (US)", value: "en_US" },
+ { label: "Chinese, Simplified (简体中文)", value: "zh_CN" },
{ label: "Deutsch (Deutschland)", value: "de_DE" },
{ label: "Español (España)", value: "es_ES" },
{ label: "Français (France)", value: "fr_FR" },
+ { label: "Italiano (Italia)", value: "it_IT" },
{ label: "한국어 (Korean)", value: "ko_KR" },
{ label: "Português (Portugal)", value: "pt_PT" },
];
From 9759227d73646ff63ce6c911b044ea1e2159ed51 Mon Sep 17 00:00:00 2001
From: Malek Hijazi
Date: Thu, 21 Jan 2021 08:19:44 +0200
Subject: [PATCH 054/109] fix: upgrade command (#1830)
I tested this on the server. Running yarn upgrade will result in yarn self updating. To solve this issue we need to run yarn run upgrade.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 83a103760..f92128148 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,7 @@ docker run --rm outlinewiki/outline:latest yarn sequelize:migrate
If you're running Outline by cloning this repository, run the following command to upgrade:
```
-yarn upgrade
+yarn run upgrade
```
## Development
From 774c3534d8b6608894ecb98f6ebcd998ebf3b76f Mon Sep 17 00:00:00 2001
From: Translate-O-Tron
<75237327+outline-translations@users.noreply.github.com>
Date: Wed, 20 Jan 2021 22:23:30 -0800
Subject: [PATCH 055/109] fix: New Chinese Simplified translations from Crowdin
[ci skip] (#1832)
---
shared/i18n/locales/zh_CN/translation.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shared/i18n/locales/zh_CN/translation.json b/shared/i18n/locales/zh_CN/translation.json
index c260b292f..14f43c369 100644
--- a/shared/i18n/locales/zh_CN/translation.json
+++ b/shared/i18n/locales/zh_CN/translation.json
@@ -89,7 +89,7 @@
"Collections": "文档集",
"Untitled": "无标题",
"Home": "主页",
- "Starred": "收藏夹",
+ "Starred": "已加星标",
"Invite people…": "邀请其他人…",
"Invite people": "邀请其他人",
"Create a collection": "创建文档集",
From 111212b03807cd2befc61926eebcac98cb8850ba Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Wed, 20 Jan 2021 23:00:14 -0800
Subject: [PATCH 056/109] feat: Resizable sidebar (#1827)
* wip: First round on sidebar resizing
* feat: Saving setting, animation
* all requirements, refactoring needed
* lint
* refactor useResize
* some mobile improvements
* fix
* refactor
---
app/components/Layout.js | 47 +++-
app/components/Sidebar/Sidebar.js | 221 +++++++++++++-----
.../Sidebar/components/CollapseToggle.js | 4 +-
.../Sidebar/components/HeaderBlock.js | 17 +-
.../Sidebar/components/ResizeBorder.js | 28 +++
.../Sidebar/components/ResizeHandle.js | 39 ++++
app/components/Sidebar/components/Section.js | 1 +
.../Sidebar/components/SidebarLink.js | 8 +-
app/components/SkipNavContent.js | 8 +
app/components/SkipNavLink.js | 34 +++
app/components/TeamLogo.js | 1 +
app/scenes/Document/components/Header.js | 7 -
app/stores/UiStore.js | 9 +
shared/styles/theme.js | 8 +-
14 files changed, 342 insertions(+), 90 deletions(-)
create mode 100644 app/components/Sidebar/components/ResizeBorder.js
create mode 100644 app/components/Sidebar/components/ResizeHandle.js
create mode 100644 app/components/SkipNavContent.js
create mode 100644 app/components/SkipNavLink.js
diff --git a/app/components/Layout.js b/app/components/Layout.js
index 3a0210a7c..6a2c5957b 100644
--- a/app/components/Layout.js
+++ b/app/components/Layout.js
@@ -1,6 +1,7 @@
// @flow
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
+import { MenuIcon } from "outline-icons";
import * as React from "react";
import { Helmet } from "react-helmet";
import { withTranslation, type TFunction } from "react-i18next";
@@ -14,13 +15,15 @@ import UiStore from "stores/UiStore";
import ErrorSuspended from "scenes/ErrorSuspended";
import KeyboardShortcuts from "scenes/KeyboardShortcuts";
import Analytics from "components/Analytics";
+import Button from "components/Button";
import DocumentHistory from "components/DocumentHistory";
import Flex from "components/Flex";
-
import { LoadingIndicatorBar } from "components/LoadingIndicator";
import Modal from "components/Modal";
import Sidebar from "components/Sidebar";
import SettingsSidebar from "components/Sidebar/Settings";
+import SkipNavContent from "components/SkipNavContent";
+import SkipNavLink from "components/SkipNavLink";
import { type Theme } from "types";
import { meta } from "utils/keyboard";
import {
@@ -99,6 +102,7 @@ class Layout extends React.Component {
const { auth, t, ui } = this.props;
const { user, team } = auth;
const showSidebar = auth.authenticated && user && team;
+ const sidebarCollapsed = ui.editMode || ui.sidebarCollapsed;
if (auth.isSuspended) return ;
if (this.redirectTo) return ;
@@ -112,11 +116,19 @@ class Layout extends React.Component {
content="width=device-width, initial-scale=1.0"
/>
+
{this.props.ui.progressBarVisible && }
{this.props.notifications}
+ }
+ iconColor="currentColor"
+ neutral
+ />
+
{showSidebar && (
@@ -125,10 +137,16 @@ class Layout extends React.Component {
)}
+
{this.props.children}
@@ -160,19 +178,34 @@ const Container = styled(Flex)`
min-height: 100%;
`;
+const MobileMenuButton = styled(Button)`
+ position: fixed;
+ top: 12px;
+ left: 12px;
+ z-index: ${(props) => props.theme.depths.sidebar - 1};
+
+ ${breakpoint("tablet")`
+ display: none;
+ `};
+`;
+
const Content = styled(Flex)`
margin: 0;
- transition: margin-left 100ms ease-out;
+ transition: ${(props) =>
+ props.$sidebarCollapsed ? `margin-left 100ms ease-out` : "none"};
@media print {
margin: 0;
}
+ ${breakpoint("mobile", "tablet")`
+ margin-left: 0 !important;
+ `}
+
${breakpoint("tablet")`
- margin-left: ${(props) =>
- props.sidebarCollapsed
- ? props.theme.sidebarCollapsedWidth
- : props.theme.sidebarWidth};
+ ${(props) =>
+ props.$sidebarCollapsed &&
+ `margin-left: ${props.theme.sidebarCollapsedWidth}px;`}
`};
`;
diff --git a/app/components/Sidebar/Sidebar.js b/app/components/Sidebar/Sidebar.js
index d4ceaccc1..afc6332b7 100644
--- a/app/components/Sidebar/Sidebar.js
+++ b/app/components/Sidebar/Sidebar.js
@@ -1,55 +1,165 @@
// @flow
import { observer } from "mobx-react";
-import { CloseIcon, MenuIcon } from "outline-icons";
import * as React from "react";
+import { Portal } from "react-portal";
import { withRouter } from "react-router-dom";
import type { Location } from "react-router-dom";
-import styled from "styled-components";
+import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Fade from "components/Fade";
import Flex from "components/Flex";
-import CollapseToggle, { Button } from "./components/CollapseToggle";
+import CollapseToggle, {
+ Button as CollapseButton,
+} from "./components/CollapseToggle";
+import ResizeBorder from "./components/ResizeBorder";
+import ResizeHandle from "./components/ResizeHandle";
import usePrevious from "hooks/usePrevious";
import useStores from "hooks/useStores";
let firstRender = true;
+let BOUNCE_ANIMATION_MS = 250;
type Props = {
children: React.Node,
location: Location,
};
+const useResize = ({ width, minWidth, maxWidth, setWidth }) => {
+ const [offset, setOffset] = React.useState(0);
+ const [isAnimating, setAnimating] = React.useState(false);
+ const [isResizing, setResizing] = React.useState(false);
+ const isSmallerThanMinimum = width < minWidth;
+
+ const handleDrag = React.useCallback(
+ (event: MouseEvent) => {
+ // suppresses text selection
+ event.preventDefault();
+
+ // this is simple because the sidebar is always against the left edge
+ const width = Math.min(event.pageX - offset, maxWidth);
+ setWidth(width);
+ },
+ [offset, maxWidth, setWidth]
+ );
+
+ const handleStopDrag = React.useCallback(() => {
+ setResizing(false);
+
+ if (isSmallerThanMinimum) {
+ setWidth(minWidth);
+ setAnimating(true);
+ } else {
+ setWidth(width);
+ }
+ }, [isSmallerThanMinimum, minWidth, width, setWidth]);
+
+ const handleStartDrag = React.useCallback(
+ (event) => {
+ setOffset(event.pageX - width);
+ setResizing(true);
+ setAnimating(false);
+ },
+ [width]
+ );
+
+ React.useEffect(() => {
+ if (isAnimating) {
+ setTimeout(() => setAnimating(false), BOUNCE_ANIMATION_MS);
+ }
+ }, [isAnimating]);
+
+ React.useEffect(() => {
+ if (isResizing) {
+ document.addEventListener("mousemove", handleDrag);
+ document.addEventListener("mouseup", handleStopDrag);
+ }
+
+ return () => {
+ document.removeEventListener("mousemove", handleDrag);
+ document.removeEventListener("mouseup", handleStopDrag);
+ };
+ }, [isResizing, handleDrag, handleStopDrag]);
+
+ return { isAnimating, isSmallerThanMinimum, isResizing, handleStartDrag };
+};
+
function Sidebar({ location, children }: Props) {
+ const theme = useTheme();
const { ui } = useStores();
const previousLocation = usePrevious(location);
+ const width = ui.sidebarWidth;
+ const maxWidth = theme.sidebarMaxWidth;
+ const minWidth = theme.sidebarMinWidth + 16; // padding
+ const collapsed = ui.editMode || ui.sidebarCollapsed;
+
+ const {
+ isAnimating,
+ isSmallerThanMinimum,
+ isResizing,
+ handleStartDrag,
+ } = useResize({
+ width,
+ minWidth,
+ maxWidth,
+ setWidth: ui.setSidebarWidth,
+ });
+
+ const handleReset = React.useCallback(() => {
+ ui.setSidebarWidth(theme.sidebarWidth);
+ }, [ui, theme.sidebarWidth]);
+
React.useEffect(() => {
if (location !== previousLocation) {
ui.hideMobileSidebar();
}
}, [ui, location, previousLocation]);
+ const style = React.useMemo(
+ () => ({
+ width: `${width}px`,
+ left:
+ collapsed && !ui.mobileSidebarVisible
+ ? `${-width + theme.sidebarCollapsedWidth}px`
+ : 0,
+ }),
+ [width, collapsed, theme.sidebarCollapsedWidth, ui.mobileSidebarVisible]
+ );
+
const content = (
-
-
- {ui.mobileSidebarVisible ? (
-
- ) : (
-
- )}
-
+ {!isResizing && (
+
+ )}
+ {ui.mobileSidebarVisible && (
+
+
+
+
+
+ )}
+
{children}
+ {!ui.sidebarCollapsed && (
+
+
+
+ )}
);
@@ -62,82 +172,67 @@ function Sidebar({ location, children }: Props) {
return content;
}
+const Background = styled.a`
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ cursor: default;
+ z-index: ${(props) => props.theme.depths.sidebar - 1};
+ background: rgba(0, 0, 0, 0.5);
+`;
+
const Container = styled(Flex)`
position: fixed;
top: 0;
bottom: 0;
width: 100%;
background: ${(props) => props.theme.sidebarBackground};
- transition: box-shadow, 100ms, ease-in-out, left 100ms ease-out,
- ${(props) => props.theme.backgroundTransition};
- margin-left: ${(props) => (props.mobileSidebarVisible ? 0 : "-100%")};
+ transition: box-shadow, 100ms, ease-in-out, margin-left 100ms ease-out,
+ left 100ms ease-out,
+ ${(props) => props.theme.backgroundTransition}
+ ${(props) =>
+ props.$isAnimating ? `,width ${BOUNCE_ANIMATION_MS}ms ease-out` : ""};
+ margin-left: ${(props) => (props.$mobileSidebarVisible ? 0 : "-100%")};
z-index: ${(props) => props.theme.depths.sidebar};
+ max-width: 70%;
+ min-width: 280px;
@media print {
display: none;
left: 0;
}
- &:before,
- &:after {
- content: "";
- background: ${(props) => props.theme.sidebarBackground};
- position: absolute;
- top: -50vh;
- left: 0;
- width: 100%;
- height: 50vh;
- }
-
- &:after {
- top: auto;
- bottom: -50vh;
- }
-
${breakpoint("tablet")`
- left: ${(props) =>
- props.collapsed
- ? `calc(-${props.theme.sidebarWidth} + ${props.theme.sidebarCollapsedWidth})`
- : 0};
- width: ${(props) => props.theme.sidebarWidth};
margin: 0;
z-index: 3;
+ min-width: 0;
&:hover,
&:focus-within {
- left: 0;
+ left: 0 !important;
box-shadow: ${(props) =>
- props.collapsed ? "rgba(0, 0, 0, 0.2) 1px 0 4px" : "none"};
+ props.$collapsed
+ ? "rgba(0, 0, 0, 0.2) 1px 0 4px"
+ : props.$isSmallerThanMinimum
+ ? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
+ : "none"};
- & ${Button} {
+ & ${CollapseButton} {
opacity: .75;
}
- & ${Button}:hover {
+ & ${CollapseButton}:hover {
opacity: 1;
}
}
&:not(:hover):not(:focus-within) > div {
- opacity: ${(props) => (props.collapsed ? "0" : "1")};
+ opacity: ${(props) => (props.$collapsed ? "0" : "1")};
transition: opacity 100ms ease-in-out;
}
`};
`;
-const Toggle = styled.a`
- display: flex;
- align-items: center;
- position: fixed;
- top: 0;
- left: ${(props) => (props.mobileSidebarVisible ? "auto" : 0)};
- right: ${(props) => (props.mobileSidebarVisible ? 0 : "auto")};
- z-index: 1;
- margin: 12px;
-
- ${breakpoint("tablet")`
- display: none;
- `};
-`;
-
export default withRouter(observer(Sidebar));
diff --git a/app/components/Sidebar/components/CollapseToggle.js b/app/components/Sidebar/components/CollapseToggle.js
index 8de701be6..60989a44a 100644
--- a/app/components/Sidebar/components/CollapseToggle.js
+++ b/app/components/Sidebar/components/CollapseToggle.js
@@ -8,7 +8,7 @@ import { meta } from "utils/keyboard";
type Props = {|
collapsed: boolean,
- onClick?: () => void,
+ onClick?: (event: SyntheticEvent<>) => void,
|};
function CollapseToggle({ collapsed, ...rest }: Props) {
@@ -43,7 +43,7 @@ export const Button = styled.button`
z-index: 1;
font-weight: 600;
color: ${(props) => props.theme.sidebarText};
- background: ${(props) => props.theme.sidebarItemBackground};
+ background: transparent;
transition: opacity 100ms ease-in-out;
border-radius: 4px;
opacity: 0;
diff --git a/app/components/Sidebar/components/HeaderBlock.js b/app/components/Sidebar/components/HeaderBlock.js
index 1786c2fcf..cea45ac47 100644
--- a/app/components/Sidebar/components/HeaderBlock.js
+++ b/app/components/Sidebar/components/HeaderBlock.js
@@ -13,8 +13,8 @@ type Props = {
};
const HeaderBlock = React.forwardRef(
- ({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => {
- return (
+ ({ showDisclosure, teamName, subheading, logoUrl, ...rest }: Props, ref) => (
+
- );
- }
+
+ )
);
const StyledExpandedIcon = styled(ExpandedIcon)`
@@ -45,6 +45,7 @@ const Subheading = styled.div`
font-size: 11px;
text-transform: uppercase;
font-weight: 500;
+ white-space: nowrap;
color: ${(props) => props.theme.sidebarText};
`;
@@ -54,16 +55,20 @@ const TeamName = styled.div`
padding-right: 24px;
font-weight: 600;
color: ${(props) => props.theme.text};
+ white-space: nowrap;
text-decoration: none;
font-size: 16px;
`;
+const Wrapper = styled.div`
+ flex-shrink: 0;
+ overflow: hidden;
+`;
+
const Header = styled.button`
display: flex;
align-items: center;
- flex-shrink: 0;
padding: 20px 24px;
- position: relative;
background: none;
line-height: inherit;
border: 0;
diff --git a/app/components/Sidebar/components/ResizeBorder.js b/app/components/Sidebar/components/ResizeBorder.js
new file mode 100644
index 000000000..979907c57
--- /dev/null
+++ b/app/components/Sidebar/components/ResizeBorder.js
@@ -0,0 +1,28 @@
+// @flow
+import styled from "styled-components";
+import ResizeHandle from "./ResizeHandle";
+
+const ResizeBorder = styled.div`
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: -6px;
+ width: 12px;
+ cursor: ew-resize;
+
+ ${(props) =>
+ props.$isResizing &&
+ `
+ ${ResizeHandle} {
+ opacity: 1;
+ }
+ `}
+
+ &:hover {
+ ${ResizeHandle} {
+ opacity: 1;
+ }
+ }
+`;
+
+export default ResizeBorder;
diff --git a/app/components/Sidebar/components/ResizeHandle.js b/app/components/Sidebar/components/ResizeHandle.js
new file mode 100644
index 000000000..c85c9749d
--- /dev/null
+++ b/app/components/Sidebar/components/ResizeHandle.js
@@ -0,0 +1,39 @@
+// @flow
+import styled from "styled-components";
+import breakpoint from "styled-components-breakpoint";
+
+const ResizeHandle = styled.button`
+ opacity: 0;
+ transition: opacity 100ms ease-in-out;
+ transform: translateY(-50%);
+ position: absolute;
+ top: 50%;
+ height: 40px;
+ right: -10px;
+ width: 8px;
+ padding: 0;
+ border: 0;
+ background: ${(props) => props.theme.sidebarBackground};
+ border-radius: 8px;
+ pointer-events: none;
+
+ &:after {
+ content: "";
+ position: absolute;
+ top: -24px;
+ bottom: -24px;
+ left: -12px;
+ right: -12px;
+ }
+
+ &:active {
+ background: ${(props) => props.theme.sidebarText};
+ }
+
+ ${breakpoint("tablet")`
+ pointer-events: all;
+ cursor: ew-resize;
+ `}
+`;
+
+export default ResizeHandle;
diff --git a/app/components/Sidebar/components/Section.js b/app/components/Sidebar/components/Section.js
index 31d0c807f..5214b694d 100644
--- a/app/components/Sidebar/components/Section.js
+++ b/app/components/Sidebar/components/Section.js
@@ -6,6 +6,7 @@ const Section = styled(Flex)`
position: relative;
flex-direction: column;
margin: 24px 8px;
+ min-width: ${(props) => props.theme.sidebarMinWidth}px;
`;
export default Section;
diff --git a/app/components/Sidebar/components/SidebarLink.js b/app/components/Sidebar/components/SidebarLink.js
index 06e4efab4..9fb5d4b86 100644
--- a/app/components/Sidebar/components/SidebarLink.js
+++ b/app/components/Sidebar/components/SidebarLink.js
@@ -7,6 +7,7 @@ import {
type Match,
} from "react-router-dom";
import styled, { withTheme } from "styled-components";
+import breakpoint from "styled-components-breakpoint";
import EventBoundary from "components/EventBoundary";
import { type Theme } from "types";
@@ -96,6 +97,7 @@ const IconWrapper = styled.span`
margin-right: 4px;
height: 24px;
overflow: hidden;
+ flex-shrink: 0;
`;
const Actions = styled(EventBoundary)`
@@ -123,7 +125,7 @@ const StyledNavLink = styled(NavLink)`
display: flex;
position: relative;
text-overflow: ellipsis;
- padding: 4px 16px;
+ padding: 6px 16px;
border-radius: 4px;
transition: background 50ms, color 50ms;
background: ${(props) =>
@@ -159,6 +161,10 @@ const StyledNavLink = styled(NavLink)`
}
}
}
+
+ ${breakpoint("tablet")`
+ padding: 4px 16px;
+ `}
`;
const Label = styled.div`
diff --git a/app/components/SkipNavContent.js b/app/components/SkipNavContent.js
new file mode 100644
index 000000000..dfb86b0e7
--- /dev/null
+++ b/app/components/SkipNavContent.js
@@ -0,0 +1,8 @@
+// @flow
+import * as React from "react";
+
+export const id = "skip-nav";
+
+export default function SkipNavContent() {
+ return ;
+}
diff --git a/app/components/SkipNavLink.js b/app/components/SkipNavLink.js
new file mode 100644
index 000000000..6cdb1a234
--- /dev/null
+++ b/app/components/SkipNavLink.js
@@ -0,0 +1,34 @@
+// @flow
+import * as React from "react";
+import styled from "styled-components";
+import { id } from "components/SkipNavContent";
+
+export default function SkipNavLink() {
+ return Skip navigation;
+}
+
+const Anchor = styled.a`
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ width: 1px;
+ margin: -1px;
+ padding: 0;
+ overflow: hidden;
+ position: absolute;
+ z-index: 1;
+
+ &:focus {
+ padding: 1rem;
+ position: fixed;
+ top: 12px;
+ left: 12px;
+ background: ${(props) => props.theme.background};
+ color: ${(props) => props.theme.text};
+ outline-color: ${(props) => props.theme.primary};
+ z-index: ${(props) => props.theme.depths.popover};
+ width: auto;
+ height: auto;
+ clip: auto;
+ }
+`;
diff --git a/app/components/TeamLogo.js b/app/components/TeamLogo.js
index ae703ac77..30a4c26ce 100644
--- a/app/components/TeamLogo.js
+++ b/app/components/TeamLogo.js
@@ -10,6 +10,7 @@ const TeamLogo = styled.img`
background: ${(props) => props.theme.background};
border: 1px solid ${(props) => props.theme.divider};
overflow: hidden;
+ flex-shrink: 0;
`;
export default TeamLogo;
diff --git a/app/scenes/Document/components/Header.js b/app/scenes/Document/components/Header.js
index c775ea8a3..f33161112 100644
--- a/app/scenes/Document/components/Header.js
+++ b/app/scenes/Document/components/Header.js
@@ -171,7 +171,6 @@ class Header extends React.Component {
iconColor="currentColor"
borderOnHover
neutral
- small
/>
>
@@ -223,7 +222,6 @@ class Header extends React.Component {
icon={isPubliclyShared ? : undefined}
onClick={this.handleShareLink}
neutral
- small
>
{t("Share")}
@@ -244,7 +242,6 @@ class Header extends React.Component {
disabled={savingIsDisabled}
isSaving={isSaving}
neutral={isDraft}
- small
>
{isDraft ? t("Save Draft") : t("Done Editing")}
@@ -265,7 +262,6 @@ class Header extends React.Component {
icon={}
to={editDocumentUrl(this.props.document)}
neutral
- small
>
{t("Edit")}
@@ -300,7 +296,6 @@ class Header extends React.Component {
templateId: document.id,
})}
primary
- small
>
{t("New from template")}
@@ -318,7 +313,6 @@ class Header extends React.Component {
onClick={this.handlePublish}
title={t("Publish document")}
disabled={publishingIsDisabled}
- small
>
{isPublishing ? `${t("Publishing")}…` : t("Publish")}
@@ -339,7 +333,6 @@ class Header extends React.Component {
{...props}
borderOnHover
neutral
- small
/>
)}
showToggleEmbeds={canToggleEmbeds}
diff --git a/app/stores/UiStore.js b/app/stores/UiStore.js
index b5e65d0ef..d5c702e26 100644
--- a/app/stores/UiStore.js
+++ b/app/stores/UiStore.js
@@ -2,6 +2,7 @@
import { orderBy } from "lodash";
import { observable, action, autorun, computed } from "mobx";
import { v4 } from "uuid";
+import { light as defaultTheme } from "shared/styles/theme";
import Collection from "models/Collection";
import Document from "models/Document";
import type { Toast } from "types";
@@ -23,6 +24,7 @@ class UiStore {
@observable editMode: boolean = false;
@observable tocVisible: boolean = false;
@observable mobileSidebarVisible: boolean = false;
+ @observable sidebarWidth: number;
@observable sidebarCollapsed: boolean = false;
@observable toasts: Map = new Map();
lastToastId: string;
@@ -54,6 +56,7 @@ class UiStore {
// persisted keys
this.languagePromptDismissed = data.languagePromptDismissed;
this.sidebarCollapsed = data.sidebarCollapsed;
+ this.sidebarWidth = data.sidebarWidth || defaultTheme.sidebarWidth;
this.tocVisible = data.tocVisible;
this.theme = data.theme || "system";
@@ -110,6 +113,11 @@ class UiStore {
this.activeCollectionId = undefined;
};
+ @action
+ setSidebarWidth = (sidebarWidth: number): void => {
+ this.sidebarWidth = sidebarWidth;
+ };
+
@action
collapseSidebar = () => {
this.sidebarCollapsed = true;
@@ -219,6 +227,7 @@ class UiStore {
return JSON.stringify({
tocVisible: this.tocVisible,
sidebarCollapsed: this.sidebarCollapsed,
+ sidebarWidth: this.sidebarWidth,
languagePromptDismissed: this.languagePromptDismissed,
theme: this.theme,
});
diff --git a/shared/styles/theme.js b/shared/styles/theme.js
index 8d6c3a34b..16ff7252b 100644
--- a/shared/styles/theme.js
+++ b/shared/styles/theme.js
@@ -47,10 +47,10 @@ const spacing = {
padding: "1.5vw 1.875vw",
vpadding: "1.5vw",
hpadding: "1.875vw",
- sidebarWidth: "280px",
- sidebarCollapsedWidth: "16px",
- sidebarMinWidth: "250px",
- sidebarMaxWidth: "350px",
+ sidebarWidth: 280,
+ sidebarCollapsedWidth: 16,
+ sidebarMinWidth: 200,
+ sidebarMaxWidth: 400,
};
export const base = {
From 40491fafe9da883da13e580d69fd0be42e67256f Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Wed, 20 Jan 2021 23:07:39 -0800
Subject: [PATCH 057/109] fix: Document star/unstar from list item navigates to
document (regression)
---
app/menus/DocumentMenu.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/menus/DocumentMenu.js b/app/menus/DocumentMenu.js
index 5602cf045..d27e7bd07 100644
--- a/app/menus/DocumentMenu.js
+++ b/app/menus/DocumentMenu.js
@@ -106,6 +106,7 @@ function DocumentMenu({
const handleStar = React.useCallback(
(ev: SyntheticEvent<>) => {
+ ev.preventDefault();
ev.stopPropagation();
document.star();
},
@@ -114,6 +115,7 @@ function DocumentMenu({
const handleUnstar = React.useCallback(
(ev: SyntheticEvent<>) => {
+ ev.preventDefault();
ev.stopPropagation();
document.unstar();
},
From 24ecaa8ce40ceac961a4f52e7fe0dab54bad8e3d Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Wed, 20 Jan 2021 23:07:48 -0800
Subject: [PATCH 058/109] chore: Reduce default menu width
---
shared/styles/theme.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shared/styles/theme.js b/shared/styles/theme.js
index 16ff7252b..0d59bbeca 100644
--- a/shared/styles/theme.js
+++ b/shared/styles/theme.js
@@ -47,7 +47,7 @@ const spacing = {
padding: "1.5vw 1.875vw",
vpadding: "1.5vw",
hpadding: "1.875vw",
- sidebarWidth: 280,
+ sidebarWidth: 260,
sidebarCollapsedWidth: 16,
sidebarMinWidth: 200,
sidebarMaxWidth: 400,
From 836b2e310aed4158467c68b92ce4a5d394d76c1c Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Wed, 20 Jan 2021 23:13:51 -0800
Subject: [PATCH 059/109] chore: Missing translation hooks in settings sidebar
---
app/components/Sidebar/Settings.js | 236 ++++++++++++++---------------
app/hooks/useCurrentTeam.js | 9 ++
2 files changed, 121 insertions(+), 124 deletions(-)
create mode 100644 app/hooks/useCurrentTeam.js
diff --git a/app/components/Sidebar/Settings.js b/app/components/Sidebar/Settings.js
index dc93d5fb6..0d0e74782 100644
--- a/app/components/Sidebar/Settings.js
+++ b/app/components/Sidebar/Settings.js
@@ -1,5 +1,5 @@
// @flow
-import { observer, inject } from "mobx-react";
+import { observer } from "mobx-react";
import {
DocumentIcon,
EmailIcon,
@@ -13,11 +13,9 @@ import {
ExpandedIcon,
} from "outline-icons";
import * as React from "react";
-import { withTranslation, type TFunction } from "react-i18next";
-import type { RouterHistory } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+import { useHistory } from "react-router-dom";
import styled from "styled-components";
-import AuthStore from "stores/AuthStore";
-import PoliciesStore from "stores/PoliciesStore";
import Flex from "components/Flex";
import Scrollable from "components/Scrollable";
@@ -30,131 +28,123 @@ import Version from "./components/Version";
import SlackIcon from "./icons/Slack";
import ZapierIcon from "./icons/Zapier";
import env from "env";
+import useCurrentTeam from "hooks/useCurrentTeam";
+import useStores from "hooks/useStores";
const isHosted = env.DEPLOYMENT === "hosted";
-type Props = {
- history: RouterHistory,
- policies: PoliciesStore,
- auth: AuthStore,
- t: TFunction,
-};
+function SettingsSidebar() {
+ const { t } = useTranslation();
+ const history = useHistory();
+ const team = useCurrentTeam();
+ const { policies } = useStores();
+ const can = policies.abilities(team.id);
-@observer
-class SettingsSidebar extends React.Component {
- returnToDashboard = () => {
- this.props.history.push("/home");
- };
+ const returnToDashboard = React.useCallback(() => {
+ history.push("/home");
+ }, [history]);
- render() {
- const { policies, t, auth } = this.props;
- const { team } = auth;
- if (!team) return null;
+ return (
+
+
+ {t("Return to App")}
+
+ }
+ teamName={team.name}
+ logoUrl={team.avatarUrl}
+ onClick={returnToDashboard}
+ />
- const can = policies.abilities(team.id);
-
- return (
-
-
- {t("Return to App")}
-
- }
- teamName={team.name}
- logoUrl={team.avatarUrl}
- onClick={this.returnToDashboard}
- />
-
-
-
-
-
- }
- label={t("Profile")}
- />
- }
- label={t("Notifications")}
- />
- }
- label={t("API Tokens")}
- />
-
-
-
- {can.update && (
- }
- label={t("Details")}
- />
- )}
- {can.update && (
- }
- label={t("Security")}
- />
- )}
- }
- exact={false}
- label={t("People")}
- />
- }
- exact={false}
- label={t("Groups")}
- />
- }
- label={t("Share Links")}
- />
- {can.export && (
- }
- label={t("Export Data")}
- />
- )}
-
+
+
+
+
+ }
+ label={t("Profile")}
+ />
+ }
+ label={t("Notifications")}
+ />
+ }
+ label={t("API Tokens")}
+ />
+
+
+
{can.update && (
-
-
+ }
+ label={t("Details")}
+ />
+ )}
+ {can.update && (
+ }
+ label={t("Security")}
+ />
+ )}
+ }
+ exact={false}
+ label={t("People")}
+ />
+ }
+ exact={false}
+ label={t("Groups")}
+ />
+ }
+ label={t("Share Links")}
+ />
+ {can.export && (
+ }
+ label={t("Export Data")}
+ />
+ )}
+
+ {can.update && (
+
+
+ }
+ label="Slack"
+ />
+ {isHosted && (
}
- label="Slack"
+ to="/settings/integrations/zapier"
+ icon={}
+ label="Zapier"
/>
- {isHosted && (
- }
- label="Zapier"
- />
- )}
-
- )}
- {can.update && !isHosted && (
-
- )}
-
-
-
- );
- }
+ )}
+
+ )}
+ {can.update && !isHosted && (
+
+ )}
+
+
+
+ );
}
const BackIcon = styled(ExpandedIcon)`
@@ -166,6 +156,4 @@ const ReturnToApp = styled(Flex)`
height: 16px;
`;
-export default withTranslation()(
- inject("auth", "policies")(SettingsSidebar)
-);
+export default observer(SettingsSidebar);
diff --git a/app/hooks/useCurrentTeam.js b/app/hooks/useCurrentTeam.js
new file mode 100644
index 000000000..e03b0ade9
--- /dev/null
+++ b/app/hooks/useCurrentTeam.js
@@ -0,0 +1,9 @@
+// @flow
+import invariant from "invariant";
+import useStores from "./useStores";
+
+export default function useCurrentTeam() {
+ const { auth } = useStores();
+ invariant(auth.team, "team required");
+ return auth.team;
+}
From 6fa9e700c873d53363256feca03c0c4f3cf7de05 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Wed, 20 Jan 2021 23:20:06 -0800
Subject: [PATCH 060/109] chore: Flip chinese label in language select
---
shared/i18n/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shared/i18n/index.js b/shared/i18n/index.js
index f3fa744db..03fad40e9 100644
--- a/shared/i18n/index.js
+++ b/shared/i18n/index.js
@@ -36,7 +36,7 @@ export const initI18n = () => {
export const languageOptions = [
{ label: "English (US)", value: "en_US" },
- { label: "Chinese, Simplified (简体中文)", value: "zh_CN" },
+ { label: "简体中文 (Chinese, Simplified)", value: "zh_CN" },
{ label: "Deutsch (Deutschland)", value: "de_DE" },
{ label: "Español (España)", value: "es_ES" },
{ label: "Français (France)", value: "fr_FR" },
From 993aad004e8c3c81d559a540a45496034be46cd1 Mon Sep 17 00:00:00 2001
From: Translate-O-Tron
<75237327+outline-translations@users.noreply.github.com>
Date: Thu, 21 Jan 2021 07:21:23 -0800
Subject: [PATCH 061/109] fix: New Korean translations from Crowdin [ci skip]
(#1833)
---
shared/i18n/locales/ko_KR/translation.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/shared/i18n/locales/ko_KR/translation.json b/shared/i18n/locales/ko_KR/translation.json
index d6d2109ac..760082d7a 100644
--- a/shared/i18n/locales/ko_KR/translation.json
+++ b/shared/i18n/locales/ko_KR/translation.json
@@ -2,14 +2,14 @@
"currently editing": "현재 편집 중",
"currently viewing": "현재 조회 중",
"viewed {{ timeAgo }} ago": "{{ timeAgo }} 전 조회",
- "You": "귀하",
+ "You": "본인",
"Trash": "휴지통",
"Archive": "보관",
"Drafts": "임시 보관함",
- "Templates": "탬플릿",
+ "Templates": "템플릿",
"Deleted Collection": "삭제 된 콜렉션",
"Submenu": "Submenu",
- "New": "새",
+ "New": "신규",
"Only visible to you": "나에게만 보임",
"Draft": "임시보관",
"Template": "템플릿",
From 70626ffff07c5bcd54cd320c9fdfe34deae941e8 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Thu, 21 Jan 2021 07:22:20 -0800
Subject: [PATCH 062/109] feat: Organize sidebar (#1834)
* chore: Flip chinese label in language select
* feat: Add settings to sidebar, organize secondary items to bottom
---
app/components/Scrollable.js | 73 +++--
app/components/Sidebar/Main.js | 324 ++++++++++---------
app/components/Sidebar/Settings.js | 2 +-
app/components/Sidebar/components/Section.js | 1 +
app/hooks/useWindowSize.js | 31 ++
shared/i18n/locales/en_US/translation.json | 3 +-
6 files changed, 253 insertions(+), 181 deletions(-)
create mode 100644 app/hooks/useWindowSize.js
diff --git a/app/components/Scrollable.js b/app/components/Scrollable.js
index 179bee869..762301a6b 100644
--- a/app/components/Scrollable.js
+++ b/app/components/Scrollable.js
@@ -1,28 +1,52 @@
// @flow
-import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
+import useWindowSize from "hooks/useWindowSize";
-type Props = {
+type Props = {|
shadow?: boolean,
-};
+ topShadow?: boolean,
+ bottomShadow?: boolean,
+|};
-@observer
-class Scrollable extends React.Component {
- @observable shadow: boolean = false;
+function Scrollable({ shadow, topShadow, bottomShadow, ...rest }: Props) {
+ const ref = React.useRef();
+ const [topShadowVisible, setTopShadow] = React.useState(false);
+ const [bottomShadowVisible, setBottomShadow] = React.useState(false);
+ const { height } = useWindowSize();
- handleScroll = (ev: SyntheticMouseEvent) => {
- this.shadow = !!(this.props.shadow && ev.currentTarget.scrollTop > 0);
- };
+ const updateShadows = React.useCallback(() => {
+ const c = ref.current;
+ if (!c) return;
- render() {
- const { shadow, ...rest } = this.props;
+ const scrollTop = c.scrollTop;
+ const tsv = !!((shadow || topShadow) && scrollTop > 0);
+ if (tsv !== topShadowVisible) {
+ setTopShadow(tsv);
+ }
- return (
-
- );
- }
+ const wrapperHeight = c.scrollHeight - c.clientHeight;
+ const bsv = !!((shadow || bottomShadow) && wrapperHeight - scrollTop !== 0);
+
+ if (bsv !== bottomShadowVisible) {
+ setBottomShadow(bsv);
+ }
+ }, [shadow, topShadow, bottomShadow, topShadowVisible, bottomShadowVisible]);
+
+ React.useEffect(() => {
+ updateShadows();
+ }, [height, updateShadows]);
+
+ return (
+
+ );
}
const Wrapper = styled.div`
@@ -31,9 +55,20 @@ const Wrapper = styled.div`
overflow-x: hidden;
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
- box-shadow: ${(props) =>
- props.shadow ? "0 1px inset rgba(0,0,0,.1)" : "none"};
- transition: all 250ms ease-in-out;
+ box-shadow: ${(props) => {
+ if (props.$topShadowVisible && props.$bottomShadowVisible) {
+ return "0 1px inset rgba(0,0,0,.1), 0 -1px inset rgba(0,0,0,.1)";
+ }
+ if (props.$topShadowVisible) {
+ return "0 1px inset rgba(0,0,0,.1)";
+ }
+ if (props.$bottomShadowVisible) {
+ return "0 -1px inset rgba(0,0,0,.1)";
+ }
+
+ return "none";
+ }};
+ transition: all 100ms ease-in-out;
`;
-export default Scrollable;
+export default observer(Scrollable);
diff --git a/app/components/Sidebar/Main.js b/app/components/Sidebar/Main.js
index 44b3794ce..61d06cdd7 100644
--- a/app/components/Sidebar/Main.js
+++ b/app/components/Sidebar/Main.js
@@ -1,6 +1,5 @@
// @flow
-import { observable } from "mobx";
-import { observer, inject } from "mobx-react";
+import { observer } from "mobx-react";
import {
ArchiveIcon,
HomeIcon,
@@ -10,14 +9,11 @@ import {
ShapesIcon,
TrashIcon,
PlusIcon,
+ SettingsIcon,
} from "outline-icons";
import * as React from "react";
-import { withTranslation, type TFunction } from "react-i18next";
+import { useTranslation } from "react-i18next";
import styled from "styled-components";
-
-import AuthStore from "stores/AuthStore";
-import DocumentsStore from "stores/DocumentsStore";
-import PoliciesStore from "stores/PoliciesStore";
import CollectionNew from "scenes/CollectionNew";
import Invite from "scenes/Invite";
import Flex from "components/Flex";
@@ -29,176 +25,184 @@ import Collections from "./components/Collections";
import HeaderBlock from "./components/HeaderBlock";
import Section from "./components/Section";
import SidebarLink from "./components/SidebarLink";
+import useStores from "hooks/useStores";
import AccountMenu from "menus/AccountMenu";
-type Props = {
- auth: AuthStore,
- documents: DocumentsStore,
- policies: PoliciesStore,
- t: TFunction,
-};
+function MainSidebar() {
+ const { t } = useTranslation();
+ const { policies, auth, documents } = useStores();
+ const [inviteModalOpen, setInviteModalOpen] = React.useState(false);
+ const [
+ createCollectionModalOpen,
+ setCreateCollectionModalOpen,
+ ] = React.useState(false);
-@observer
-class MainSidebar extends React.Component {
- @observable inviteModalOpen = false;
- @observable createCollectionModalOpen = false;
+ React.useEffect(() => {
+ documents.fetchDrafts();
+ documents.fetchTemplates();
+ }, [documents]);
- componentDidMount() {
- this.props.documents.fetchDrafts();
- this.props.documents.fetchTemplates();
- }
+ const handleCreateCollectionModalOpen = React.useCallback(
+ (ev: SyntheticEvent<>) => {
+ ev.preventDefault();
+ setCreateCollectionModalOpen(true);
+ },
+ []
+ );
- handleCreateCollectionModalOpen = (ev: SyntheticEvent<>) => {
+ const handleCreateCollectionModalClose = React.useCallback(
+ (ev: SyntheticEvent<>) => {
+ ev.preventDefault();
+ setCreateCollectionModalOpen(false);
+ },
+ []
+ );
+
+ const handleInviteModalOpen = React.useCallback((ev: SyntheticEvent<>) => {
ev.preventDefault();
- this.createCollectionModalOpen = true;
- };
+ setInviteModalOpen(true);
+ }, []);
- handleCreateCollectionModalClose = (ev: SyntheticEvent<>) => {
- this.createCollectionModalOpen = false;
- };
-
- handleInviteModalOpen = (ev: SyntheticEvent<>) => {
+ const handleInviteModalClose = React.useCallback((ev: SyntheticEvent<>) => {
ev.preventDefault();
- this.inviteModalOpen = true;
- };
+ setInviteModalOpen(false);
+ }, []);
- handleInviteModalClose = () => {
- this.inviteModalOpen = false;
- };
+ const { user, team } = auth;
+ if (!user || !team) return null;
- render() {
- const { auth, documents, policies, t } = this.props;
- const { user, team } = auth;
- if (!user || !team) return null;
+ const can = policies.abilities(team.id);
- const can = policies.abilities(team.id);
-
- return (
-
-
- {(props) => (
-
+
+ {(props) => (
+
+ )}
+
+
+
+
+ }
+ exact={false}
+ label={t("Home")}
/>
- )}
-
-
-
-
+ }
+ label={t("Search")}
+ exact={false}
+ />
+ }
+ exact={false}
+ label={t("Starred")}
+ />
+ }
+ exact={false}
+ label={t("Templates")}
+ active={documents.active ? documents.active.template : undefined}
+ />
+ }
+ label={
+
+ {t("Drafts")}
+ {documents.totalDrafts > 0 && (
+
+ )}
+
+ }
+ active={
+ documents.active
+ ? !documents.active.publishedAt &&
+ !documents.active.isDeleted &&
+ !documents.active.isTemplate
+ : undefined
+ }
+ />
+
+
+
+
+
+ }
+ exact={false}
+ label={t("Archive")}
+ active={
+ documents.active
+ ? documents.active.isArchived && !documents.active.isDeleted
+ : undefined
+ }
+ />
+ }
+ exact={false}
+ label={t("Trash")}
+ active={documents.active ? documents.active.isDeleted : undefined}
+ />
+ }
+ exact={false}
+ label={t("Settings")}
+ />
+ {can.invite && (
}
- exact={false}
- label={t("Home")}
+ to="/settings/people"
+ onClick={handleInviteModalOpen}
+ icon={}
+ label={t("Invite people…")}
/>
- }
- label={t("Search")}
- exact={false}
- />
- }
- exact={false}
- label={t("Starred")}
- />
- }
- exact={false}
- label={t("Templates")}
- active={
- documents.active ? documents.active.template : undefined
- }
- />
- }
- label={
-
- {t("Drafts")}
- {documents.totalDrafts > 0 && (
-
- )}
-
- }
- active={
- documents.active
- ? !documents.active.publishedAt &&
- !documents.active.isDeleted &&
- !documents.active.isTemplate
- : undefined
- }
- />
-
-
-
- }
- exact={false}
- label={t("Archive")}
- active={
- documents.active
- ? documents.active.isArchived && !documents.active.isDeleted
- : undefined
- }
- />
- }
- exact={false}
- label={t("Trash")}
- active={
- documents.active ? documents.active.isDeleted : undefined
- }
- />
- {can.invite && (
- }
- label={t("Invite people…")}
- />
- )}
-
-
-
-
-
-
-
-
-
-
- );
- }
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
}
+const Secondary = styled.div`
+ overflow-x: hidden;
+ flex-shrink: 0;
+`;
+
const Drafts = styled(Flex)`
height: 24px;
`;
-export default withTranslation()(
- inject("documents", "policies", "auth")(MainSidebar)
-);
+export default observer(MainSidebar);
diff --git a/app/components/Sidebar/Settings.js b/app/components/Sidebar/Settings.js
index 0d0e74782..94f732e4c 100644
--- a/app/components/Sidebar/Settings.js
+++ b/app/components/Sidebar/Settings.js
@@ -58,7 +58,7 @@ function SettingsSidebar() {
/>
-
+
props.theme.sidebarMinWidth}px;
+ flex-shrink: 0;
`;
export default Section;
diff --git a/app/hooks/useWindowSize.js b/app/hooks/useWindowSize.js
new file mode 100644
index 000000000..8c2111b24
--- /dev/null
+++ b/app/hooks/useWindowSize.js
@@ -0,0 +1,31 @@
+// @flow
+import { debounce } from "lodash";
+import * as React from "react";
+
+export default function useWindowSize() {
+ const [windowSize, setWindowSize] = React.useState({
+ width: undefined,
+ height: undefined,
+ });
+
+ React.useEffect(() => {
+ // Handler to call on window resize
+ const handleResize = debounce(() => {
+ // Set window width/height to state
+ setWindowSize({
+ width: window.innerWidth,
+ height: window.innerHeight,
+ });
+ }, 100);
+
+ // Add event listener
+ window.addEventListener("resize", handleResize);
+
+ // Call handler right away so state gets updated with initial window size
+ handleResize();
+
+ return () => window.removeEventListener("resize", handleResize);
+ }, []);
+
+ return windowSize;
+}
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index b0c41d233..3d4e8cbf6 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -94,9 +94,11 @@
"Invite people": "Invite people",
"Create a collection": "Create a collection",
"Return to App": "Return to App",
+ "Account": "Account",
"Profile": "Profile",
"Notifications": "Notifications",
"API Tokens": "API Tokens",
+ "Team": "Team",
"Details": "Details",
"Security": "Security",
"People": "People",
@@ -109,7 +111,6 @@
"System": "System",
"Light": "Light",
"Dark": "Dark",
- "Account": "Account",
"Settings": "Settings",
"API documentation": "API documentation",
"Changelog": "Changelog",
From 6e9c456147a85f803128dd72d622cafa23124bc3 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Thu, 21 Jan 2021 07:28:10 -0800
Subject: [PATCH 063/109] isMetaKey -> isModKey
---
app/components/Editor.js | 4 ++--
app/scenes/Document/components/Editor.js | 8 ++++----
app/utils/keyboard.js | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/app/components/Editor.js b/app/components/Editor.js
index 378d41bd3..82e2dc820 100644
--- a/app/components/Editor.js
+++ b/app/components/Editor.js
@@ -8,7 +8,7 @@ import UiStore from "stores/UiStore";
import ErrorBoundary from "components/ErrorBoundary";
import Tooltip from "components/Tooltip";
import embeds from "../embeds";
-import { isMetaKey } from "utils/keyboard";
+import { isModKey } from "utils/keyboard";
import { uploadFile } from "utils/uploadFile";
import { isInternalUrl } from "utils/urls";
@@ -50,7 +50,7 @@ function Editor(props: PropsWithRef) {
return;
}
- if (isInternalUrl(href) && !isMetaKey(event) && !event.shiftKey) {
+ if (isInternalUrl(href) && !isModKey(event) && !event.shiftKey) {
// relative
let navigateTo = href;
diff --git a/app/scenes/Document/components/Editor.js b/app/scenes/Document/components/Editor.js
index b80810557..35403ee9a 100644
--- a/app/scenes/Document/components/Editor.js
+++ b/app/scenes/Document/components/Editor.js
@@ -13,7 +13,7 @@ import Editor from "components/Editor";
import Flex from "components/Flex";
import HoverPreview from "components/HoverPreview";
import Star, { AnimatedStar } from "components/Star";
-import { isMetaKey } from "utils/keyboard";
+import { isModKey } from "utils/keyboard";
import { documentHistoryUrl } from "utils/routeHelpers";
type Props = {
@@ -55,7 +55,7 @@ class DocumentEditor extends React.Component {
handleTitleKeyDown = (event: SyntheticKeyboardEvent<>) => {
if (event.key === "Enter") {
event.preventDefault();
- if (isMetaKey(event)) {
+ if (isModKey(event)) {
this.props.onSave({ done: true });
return;
}
@@ -69,12 +69,12 @@ class DocumentEditor extends React.Component {
this.focusAtStart();
return;
}
- if (event.key === "p" && isMetaKey(event) && event.shiftKey) {
+ if (event.key === "p" && isModKey(event) && event.shiftKey) {
event.preventDefault();
this.props.onSave({ publish: true, done: true });
return;
}
- if (event.key === "s" && isMetaKey(event)) {
+ if (event.key === "s" && isModKey(event)) {
event.preventDefault();
this.props.onSave({});
return;
diff --git a/app/utils/keyboard.js b/app/utils/keyboard.js
index ff4b3b98e..09e8f1c0c 100644
--- a/app/utils/keyboard.js
+++ b/app/utils/keyboard.js
@@ -5,7 +5,7 @@ export const metaDisplay = isMac ? "⌘" : "Ctrl";
export const meta = isMac ? "cmd" : "ctrl";
-export function isMetaKey(
+export function isModKey(
event: KeyboardEvent | MouseEvent | SyntheticKeyboardEvent<>
) {
return isMac ? event.metaKey : event.ctrlKey;
From c69b393776e65d19fcde0c8d05ef992963a32c73 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Fri, 22 Jan 2021 08:57:52 -0800
Subject: [PATCH 064/109] fix: JS error when submitting invites from
sidebar-triggered modal
---
app/components/Sidebar/Main.js | 3 +--
shared/i18n/locales/en_US/translation.json | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/app/components/Sidebar/Main.js b/app/components/Sidebar/Main.js
index 61d06cdd7..ae385e95c 100644
--- a/app/components/Sidebar/Main.js
+++ b/app/components/Sidebar/Main.js
@@ -63,8 +63,7 @@ function MainSidebar() {
setInviteModalOpen(true);
}, []);
- const handleInviteModalClose = React.useCallback((ev: SyntheticEvent<>) => {
- ev.preventDefault();
+ const handleInviteModalClose = React.useCallback(() => {
setInviteModalOpen(false);
}, []);
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index 3d4e8cbf6..b722ca282 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -90,6 +90,7 @@
"Untitled": "Untitled",
"Home": "Home",
"Starred": "Starred",
+ "Settings": "Settings",
"Invite people…": "Invite people…",
"Invite people": "Invite people",
"Create a collection": "Create a collection",
@@ -111,7 +112,6 @@
"System": "System",
"Light": "Light",
"Dark": "Dark",
- "Settings": "Settings",
"API documentation": "API documentation",
"Changelog": "Changelog",
"Send us feedback": "Send us feedback",
From 6a206de6cd3b47686a9eb3ae7b9bd02dcbc39a24 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Fri, 22 Jan 2021 19:12:39 -0800
Subject: [PATCH 065/109] chore: Add meta description
---
server/static/index.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/server/static/index.html b/server/static/index.html
index 4b6c67ecd..285907fd2 100644
--- a/server/static/index.html
+++ b/server/static/index.html
@@ -4,6 +4,7 @@
Outline
+
//inject-prefetch//
Date: Fri, 22 Jan 2021 19:31:30 -0800
Subject: [PATCH 066/109] chore: Add missing labels to buttons without text and
search inputs
---
app/components/IconPicker.js | 4 ++--
app/components/Sidebar/Sidebar.js | 4 +++-
app/components/Sidebar/components/CollapseToggle.js | 2 +-
app/menus/BreadcrumbMenu.js | 2 +-
app/menus/CollectionGroupMemberMenu.js | 2 +-
app/menus/CollectionMenu.js | 2 +-
app/menus/CollectionSortMenu.js | 2 +-
app/menus/DocumentMenu.js | 6 +++++-
app/menus/GroupMemberMenu.js | 2 +-
app/menus/GroupMenu.js | 2 +-
app/menus/MemberMenu.js | 2 +-
app/menus/RevisionMenu.js | 1 +
app/menus/ShareMenu.js | 2 +-
app/menus/UserMenu.js | 2 +-
app/scenes/Collection.js | 2 ++
app/scenes/Dashboard.js | 6 +++++-
app/scenes/Drafts.js | 6 +++++-
app/scenes/Starred.js | 6 +++++-
shared/i18n/locales/en_US/translation.json | 5 +++++
19 files changed, 43 insertions(+), 17 deletions(-)
diff --git a/app/components/IconPicker.js b/app/components/IconPicker.js
index 1c297b400..2b6b09407 100644
--- a/app/components/IconPicker.js
+++ b/app/components/IconPicker.js
@@ -145,8 +145,8 @@ function IconPicker({ onOpen, icon, color, onChange }: Props) {
{(props) => (
-
diff --git a/app/components/Sidebar/Sidebar.js b/app/components/Sidebar/Sidebar.js
index afc6332b7..1e2682ca5 100644
--- a/app/components/Sidebar/Sidebar.js
+++ b/app/components/Sidebar/Sidebar.js
@@ -1,6 +1,7 @@
// @flow
import { observer } from "mobx-react";
import * as React from "react";
+import { useTranslation } from "react-i18next";
import { Portal } from "react-portal";
import { withRouter } from "react-router-dom";
import type { Location } from "react-router-dom";
@@ -85,6 +86,7 @@ const useResize = ({ width, minWidth, maxWidth, setWidth }) => {
function Sidebar({ location, children }: Props) {
const theme = useTheme();
+ const { t } = useTranslation();
const { ui } = useStores();
const previousLocation = usePrevious(location);
@@ -157,7 +159,7 @@ function Sidebar({ location, children }: Props) {
onDoubleClick={handleReset}
$isResizing={isResizing}
>
-
+
)}
diff --git a/app/components/Sidebar/components/CollapseToggle.js b/app/components/Sidebar/components/CollapseToggle.js
index 60989a44a..d7dd88a8b 100644
--- a/app/components/Sidebar/components/CollapseToggle.js
+++ b/app/components/Sidebar/components/CollapseToggle.js
@@ -21,7 +21,7 @@ function CollapseToggle({ collapsed, ...rest }: Props) {
delay={500}
placement="bottom"
>
-
+
{collapsed ? (
) : (
diff --git a/app/menus/BreadcrumbMenu.js b/app/menus/BreadcrumbMenu.js
index 5e5a584f9..097acdc02 100644
--- a/app/menus/BreadcrumbMenu.js
+++ b/app/menus/BreadcrumbMenu.js
@@ -19,7 +19,7 @@ export default function BreadcrumbMenu({ path }: Props) {
return (
<>
-
+
-
+
{label}
) : (
-
+
)}
{(props) => (
-
+
{alphabeticalSort ? : }
)}
diff --git a/app/menus/DocumentMenu.js b/app/menus/DocumentMenu.js
index d27e7bd07..a013bfc37 100644
--- a/app/menus/DocumentMenu.js
+++ b/app/menus/DocumentMenu.js
@@ -140,7 +140,11 @@ function DocumentMenu({
{label ? (
{label}
) : (
-
+
)}
-
+
setDeleteModalOpen(false)} />
-
+
-
+
diff --git a/app/menus/ShareMenu.js b/app/menus/ShareMenu.js
index b94684fcb..e5cb11b84 100644
--- a/app/menus/ShareMenu.js
+++ b/app/menus/ShareMenu.js
@@ -49,7 +49,7 @@ function ShareMenu({ share }: Props) {
return (
<>
-
+
diff --git a/app/menus/UserMenu.js b/app/menus/UserMenu.js
index 420ea8c4e..94b01aebe 100644
--- a/app/menus/UserMenu.js
+++ b/app/menus/UserMenu.js
@@ -88,7 +88,7 @@ function UserMenu({ user }: Props) {
return (
<>
-
+
{
diff --git a/app/scenes/Dashboard.js b/app/scenes/Dashboard.js
index 8b00bb04a..c3a721f10 100644
--- a/app/scenes/Dashboard.js
+++ b/app/scenes/Dashboard.js
@@ -64,7 +64,11 @@ function Dashboard() {
-
+
diff --git a/app/scenes/Drafts.js b/app/scenes/Drafts.js
index 887b8226f..3645ea85c 100644
--- a/app/scenes/Drafts.js
+++ b/app/scenes/Drafts.js
@@ -113,7 +113,11 @@ class Drafts extends React.Component {
-
+
diff --git a/app/scenes/Starred.js b/app/scenes/Starred.js
index 6b9477d3d..a41f14c0a 100644
--- a/app/scenes/Starred.js
+++ b/app/scenes/Starred.js
@@ -48,7 +48,11 @@ function Starred(props: Props) {
-
+
diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json
index b722ca282..4a5046b7e 100644
--- a/shared/i18n/locales/en_US/translation.json
+++ b/shared/i18n/locales/en_US/translation.json
@@ -76,6 +76,7 @@
"Warning": "Warning",
"Warning notice": "Warning notice",
"Icon": "Icon",
+ "Show menu": "Show menu",
"Choose icon": "Choose icon",
"Loading": "Loading",
"Search": "Search",
@@ -108,6 +109,7 @@
"Export Data": "Export Data",
"Integrations": "Integrations",
"Installation": "Installation",
+ "Resize sidebar": "Resize sidebar",
"Appearance": "Appearance",
"System": "System",
"Light": "Light",
@@ -117,6 +119,7 @@
"Send us feedback": "Send us feedback",
"Report a bug": "Report a bug",
"Log out": "Log out",
+ "Show path to document": "Show path to document",
"Path to document": "Path to document",
"Group member options": "Group member options",
"Members": "Members",
@@ -132,6 +135,7 @@
"Edit collection": "Edit collection",
"Delete collection": "Delete collection",
"Export collection": "Export collection",
+ "Show sort menu": "Show sort menu",
"Sort in sidebar": "Sort in sidebar",
"Alphabetical sort": "Alphabetical sort",
"Manual sort": "Manual sort",
@@ -235,6 +239,7 @@
"Create": "Create",
"Recently viewed": "Recently viewed",
"Created by me": "Created by me",
+ "Search documents": "Search documents",
"Hide contents": "Hide contents",
"Show contents": "Show contents",
"Archived": "Archived",
From 11e1108f4aa9e681e8f94554330359cb7c50afb9 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Fri, 22 Jan 2021 20:40:26 -0800
Subject: [PATCH 067/109] fix: Unneccessary ev.preventDefault
---
app/components/Sidebar/Main.js | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/app/components/Sidebar/Main.js b/app/components/Sidebar/Main.js
index ae385e95c..18eac32a3 100644
--- a/app/components/Sidebar/Main.js
+++ b/app/components/Sidebar/Main.js
@@ -50,13 +50,9 @@ function MainSidebar() {
[]
);
- const handleCreateCollectionModalClose = React.useCallback(
- (ev: SyntheticEvent<>) => {
- ev.preventDefault();
- setCreateCollectionModalOpen(false);
- },
- []
- );
+ const handleCreateCollectionModalClose = React.useCallback(() => {
+ setCreateCollectionModalOpen(false);
+ }, []);
const handleInviteModalOpen = React.useCallback((ev: SyntheticEvent<>) => {
ev.preventDefault();
From f6370ccf6defe5681bab642b591cdd4b5e9670b6 Mon Sep 17 00:00:00 2001
From: Tom Moor
Date: Fri, 22 Jan 2021 20:42:45 -0800
Subject: [PATCH 068/109] chore: Sentry performance monitoring (#1841)
* Hook up performance monitoring
* lint
---
app/index.js | 11 +++++-
app/utils/sentry.js | 28 ++++++++++++++
package.json | 4 +-
server/routes.js | 1 -
server/static/index.html | 22 -----------
shared/i18n/index.js | 2 +-
yarn.lock | 79 +++++++++++++++++++++++++++++++++++++++-
7 files changed, 118 insertions(+), 29 deletions(-)
create mode 100644 app/utils/sentry.js
diff --git a/app/index.js b/app/index.js
index ac495d0e3..c991c9cdc 100644
--- a/app/index.js
+++ b/app/index.js
@@ -1,11 +1,12 @@
// @flow
import "focus-visible";
+import { createBrowserHistory } from "history";
import { Provider } from "mobx-react";
import * as React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { render } from "react-dom";
-import { BrowserRouter as Router } from "react-router-dom";
+import { Router } from "react-router-dom";
import { initI18n } from "shared/i18n";
import stores from "stores";
import ErrorBoundary from "components/ErrorBoundary";
@@ -14,10 +15,16 @@ import Theme from "components/Theme";
import Toasts from "components/Toasts";
import Routes from "./routes";
import env from "env";
+import { initSentry } from "utils/sentry";
initI18n();
const element = document.getElementById("root");
+const history = createBrowserHistory();
+
+if (env.SENTRY_DSN) {
+ initSentry(history);
+}
if (element) {
render(
@@ -25,7 +32,7 @@ if (element) {
-
+
<>
diff --git a/app/utils/sentry.js b/app/utils/sentry.js
new file mode 100644
index 000000000..069ca7489
--- /dev/null
+++ b/app/utils/sentry.js
@@ -0,0 +1,28 @@
+// @flow
+import * as Sentry from "@sentry/react";
+import { Integrations } from "@sentry/tracing";
+import { type RouterHistory } from "react-router-dom";
+import env from "env";
+
+export function initSentry(history: RouterHistory) {
+ Sentry.init({
+ dsn: env.SENTRY_DSN,
+ integrations: [
+ new Integrations.BrowserTracing({
+ routingInstrumentation: Sentry.reactRouterV5Instrumentation(history),
+ }),
+ ],
+ tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1,
+ ignoreErrors: [
+ "ResizeObserver loop limit exceeded",
+ "AuthorizationError",
+ "BadRequestError",
+ "NetworkError",
+ "NotFoundError",
+ "OfflineError",
+ "ServiceUnavailableError",
+ "UpdateRequiredError",
+ "ChunkLoadError",
+ ],
+ });
+}
diff --git a/package.json b/package.json
index 713983869..375b5ecaa 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,8 @@
"@babel/preset-react": "^7.10.4",
"@rehooks/window-scroll-position": "^1.0.1",
"@sentry/node": "^5.23.0",
+ "@sentry/react": "^6.0.1",
+ "@sentry/tracing": "^6.0.1",
"@tippy.js/react": "^2.2.2",
"@tommoor/remove-markdown": "0.3.1",
"autotrack": "^2.4.1",
@@ -148,7 +150,7 @@
"react-keydown": "^1.7.3",
"react-modal": "^3.1.2",
"react-portal": "^4.0.0",
- "react-router-dom": "^5.1.2",
+ "react-router-dom": "^5.2.0",
"react-virtualized-auto-sizer": "^1.0.2",
"react-waypoint": "^9.0.2",
"react-window": "^1.8.6",
diff --git a/server/routes.js b/server/routes.js
index 0efae32c9..37077ccfc 100644
--- a/server/routes.js
+++ b/server/routes.js
@@ -52,7 +52,6 @@ const renderApp = async (ctx, next) => {
.toString()
.replace(/\/\/inject-env\/\//g, env)
.replace(/\/\/inject-prefetch\/\//g, prefetchTags)
- .replace(/\/\/inject-sentry-dsn\/\//g, process.env.SENTRY_DSN || "")
.replace(/\/\/inject-slack-app-id\/\//g, process.env.SLACK_APP_ID || "");
};
diff --git a/server/static/index.html b/server/static/index.html
index 285907fd2..f7a0d1f87 100644
--- a/server/static/index.html
+++ b/server/static/index.html
@@ -44,29 +44,7 @@
-