chore(deps-dev): bump prettier from 2.1.2 to 2.8.8 (#5372)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
dependabot[bot]
2023-05-22 19:14:56 -07:00
committed by GitHub
parent 3317bf2396
commit fbd16d4b9a
73 changed files with 556 additions and 645 deletions

View File

@@ -25,9 +25,9 @@ function CommandBar() {
const { rootAction } = useKBar((state) => ({ const { rootAction } = useKBar((state) => ({
rootAction: state.currentRootActionId rootAction: state.currentRootActionId
? ((state.actions[ ? (state.actions[
state.currentRootActionId state.currentRootActionId
] as unknown) as CommandBarAction) ] as unknown as CommandBarAction)
: undefined, : undefined,
})); }));

View File

@@ -85,27 +85,33 @@ const ContentEditable = React.forwardRef(
}, },
})); }));
const wrappedEvent = ( const wrappedEvent =
callback: (
| React.FocusEventHandler<HTMLSpanElement> callback:
| React.FormEventHandler<HTMLSpanElement> | React.FocusEventHandler<HTMLSpanElement>
| React.KeyboardEventHandler<HTMLSpanElement> | React.FormEventHandler<HTMLSpanElement>
| undefined | React.KeyboardEventHandler<HTMLSpanElement>
) => (event: any) => { | undefined
const text = contentRef.current?.innerText || ""; ) =>
(event: any) => {
const text = contentRef.current?.innerText || "";
if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) { if (
event?.preventDefault(); maxLength &&
return; isPrintableKeyEvent(event) &&
} text.length >= maxLength
) {
event?.preventDefault();
return;
}
if (text !== lastValue.current) { if (text !== lastValue.current) {
lastValue.current = text; lastValue.current = text;
onChange && onChange(text); onChange && onChange(text);
} }
callback?.(event); callback?.(event);
}; };
// This is to account for being within a React.Suspense boundary, in this // This is to account for being within a React.Suspense boundary, in this
// case the component may be rendered with display: none. React 18 may solve // case the component may be rendered with display: none. React 18 may solve

View File

@@ -24,8 +24,12 @@ type Positions = {
export default function MouseSafeArea(props: { export default function MouseSafeArea(props: {
parentRef: React.RefObject<HTMLElement | null>; parentRef: React.RefObject<HTMLElement | null>;
}) { }) {
const { x = 0, y = 0, height: h = 0, width: w = 0 } = const {
props.parentRef.current?.getBoundingClientRect() || {}; x = 0,
y = 0,
height: h = 0,
width: w = 0,
} = props.parentRef.current?.getBoundingClientRect() || {};
const [mouseX, mouseY] = useMousePosition(); const [mouseX, mouseY] = useMousePosition();
const positions = { x, y, h, w, mouseX, mouseY }; const positions = { x, y, h, w, mouseX, mouseY };

View File

@@ -45,9 +45,8 @@ function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
const [selectedNode, selectNode] = React.useState<NavigationNode | null>( const [selectedNode, selectNode] = React.useState<NavigationNode | null>(
null null
); );
const [initialScrollOffset, setInitialScrollOffset] = React.useState<number>( const [initialScrollOffset, setInitialScrollOffset] =
0 React.useState<number>(0);
);
const [activeNode, setActiveNode] = React.useState<number>(0); const [activeNode, setActiveNode] = React.useState<number>(0);
const [expandedNodes, setExpandedNodes] = React.useState<string[]>([]); const [expandedNodes, setExpandedNodes] = React.useState<string[]>([]);
const [itemRefs, setItemRefs] = React.useState< const [itemRefs, setItemRefs] = React.useState<

View File

@@ -181,7 +181,7 @@ const Actions = styled(EventBoundary)`
color: ${s("textSecondary")}; color: ${s("textSecondary")};
${NudeButton} { ${NudeButton} {
&: ${hover}, &[aria-expanded= "true" ] { &: ${hover}, &[aria-expanded= "true"] {
background: ${s("sidebarControlHoverBackground")}; background: ${s("sidebarControlHoverBackground")};
} }
} }

View File

@@ -33,9 +33,10 @@ function DocumentViews({ document, isOpen }: Props) {
documentViews, documentViews,
(view) => !presentIds.includes(view.user.id) (view) => !presentIds.includes(view.user.id)
); );
const users = React.useMemo(() => sortedViews.map((v) => v.user), [ const users = React.useMemo(
sortedViews, () => sortedViews.map((v) => v.user),
]); [sortedViews]
);
return ( return (
<> <>

View File

@@ -67,10 +67,8 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
const localRef = React.useRef<SharedEditor>(); const localRef = React.useRef<SharedEditor>();
const preferences = auth.user?.preferences; const preferences = auth.user?.preferences;
const previousHeadings = React.useRef<Heading[] | null>(null); const previousHeadings = React.useRef<Heading[] | null>(null);
const [ const [activeLinkElement, setActiveLink] =
activeLinkElement, React.useState<HTMLAnchorElement | null>(null);
setActiveLink,
] = React.useState<HTMLAnchorElement | null>(null);
const previousCommentIds = React.useRef<string[]>(); const previousCommentIds = React.useRef<string[]>();
const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => { const handleLinkActive = React.useCallback((element: HTMLAnchorElement) => {

View File

@@ -28,11 +28,8 @@ type Props = {
function GroupListItem({ group, showFacepile, renderActions }: Props) { function GroupListItem({ group, showFacepile, renderActions }: Props) {
const { groupMemberships } = useStores(); const { groupMemberships } = useStores();
const { t } = useTranslation(); const { t } = useTranslation();
const [ const [membersModalOpen, setMembersModalOpen, setMembersModalClosed] =
membersModalOpen, useBoolean();
setMembersModalOpen,
setMembersModalClosed,
] = useBoolean();
const memberCount = group.memberCount; const memberCount = group.memberCount;
const membershipsInGroup = groupMemberships.inGroup(group.id); const membershipsInGroup = groupMemberships.inGroup(group.id);
const users = membershipsInGroup const users = membershipsInGroup

View File

@@ -76,9 +76,8 @@ function SearchPopover({ shareId }: Props) {
[popover, cachedQuery] [popover, cachedQuery]
); );
const searchInputRef = popover.unstable_referenceRef as React.RefObject< const searchInputRef =
HTMLInputElement popover.unstable_referenceRef as React.RefObject<HTMLInputElement>;
>;
const firstSearchItem = React.useRef<HTMLAnchorElement>(null); const firstSearchItem = React.useRef<HTMLAnchorElement>(null);

View File

@@ -42,9 +42,10 @@ function DocumentLink(
!!node.children.length || activeDocument?.parentDocumentId === node.id; !!node.children.length || activeDocument?.parentDocumentId === node.id;
const document = documents.get(node.id); const document = documents.get(node.id);
const showChildren = React.useMemo(() => !!hasChildDocuments, [ const showChildren = React.useMemo(
hasChildDocuments, () => !!hasChildDocuments,
]); [hasChildDocuments]
);
const [expanded, setExpanded] = React.useState(showChildren); const [expanded, setExpanded] = React.useState(showChildren);

View File

@@ -30,9 +30,8 @@ type SocketWithAuthentication = Socket & {
authenticated?: boolean; authenticated?: boolean;
}; };
export const WebsocketContext = React.createContext<SocketWithAuthentication | null>( export const WebsocketContext =
null React.createContext<SocketWithAuthentication | null>(null);
);
type Props = RootStore; type Props = RootStore;

View File

@@ -272,16 +272,15 @@ class LinkEditor extends React.Component<Props, State> {
view.focus(); view.focus();
}; };
handleSelectLink = (url: string, title: string) => ( handleSelectLink =
event: React.MouseEvent (url: string, title: string) => (event: React.MouseEvent) => {
) => { event.preventDefault();
event.preventDefault(); this.save(url, title);
this.save(url, title);
if (this.initialSelectionLength) { if (this.initialSelectionLength) {
this.moveSelectionToEnd(); this.moveSelectionToEnd();
} }
}; };
moveSelectionToEnd = () => { moveSelectionToEnd = () => {
const { to, view } = this.props; const { to, view } = this.props;

View File

@@ -197,10 +197,8 @@ export default function SelectionToolbar(props: Props) {
return null; return null;
} }
const colIndex = getColumnIndex( const colIndex = getColumnIndex(state.selection as unknown as CellSelection);
(state.selection as unknown) as CellSelection const rowIndex = getRowIndex(state.selection as unknown as CellSelection);
);
const rowIndex = getRowIndex((state.selection as unknown) as CellSelection);
const isTableSelection = colIndex !== undefined && rowIndex !== undefined; const isTableSelection = colIndex !== undefined && rowIndex !== undefined;
const link = isMarkActive(state.schema.marks.link)(state); const link = isMarkActive(state.schema.marks.link)(state);
const range = getMarkRange(selection.$from, state.schema.marks.link); const range = getMarkRange(selection.$from, state.schema.marks.link);

View File

@@ -464,9 +464,8 @@ export class Editor extends React.PureComponent<
nodeViews: this.nodeViews, nodeViews: this.nodeViews,
dispatchTransaction(transaction) { dispatchTransaction(transaction) {
// callback is bound to have the view instance as its this binding // callback is bound to have the view instance as its this binding
const { state, transactions } = this.state.applyTransaction( const { state, transactions } =
transaction this.state.applyTransaction(transaction);
);
this.updateState(state); this.updateState(state);
@@ -520,9 +519,8 @@ export class Editor extends React.PureComponent<
return trim ? content.trim() : content; return trim ? content.trim() : content;
} }
return (trim return (
? ProsemirrorHelper.trim(this.view.state.doc) trim ? ProsemirrorHelper.trim(this.view.state.doc) : this.view.state.doc
: this.view.state.doc
).toJSON(); ).toJSON();
}; };

View File

@@ -1,8 +1,9 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
export default function useComponentSize( export default function useComponentSize(ref: React.RefObject<HTMLElement>): {
ref: React.RefObject<HTMLElement> width: number;
): { width: number; height: number } { height: number;
} {
const [size, setSize] = useState({ const [size, setSize] = useState({
width: ref.current?.clientWidth || 0, width: ref.current?.clientWidth || 0,
height: ref.current?.clientHeight || 0, height: ref.current?.clientHeight || 0,

View File

@@ -16,8 +16,7 @@ type MousePosition = [number, number];
export const useMousePosition = () => { export const useMousePosition = () => {
const isMounted = useIsMounted(); const isMounted = useIsMounted();
const [mousePosition, setMousePosition] = React.useState<MousePosition>([ const [mousePosition, setMousePosition] = React.useState<MousePosition>([
0, 0, 0,
0,
]); ]);
const updateMousePosition = React.useMemo( const updateMousePosition = React.useMemo(

View File

@@ -25,20 +25,12 @@ type Props = {
function AddGroupsToCollection(props: Props) { function AddGroupsToCollection(props: Props) {
const { collection } = props; const { collection } = props;
const [ const [newGroupModalOpen, handleNewGroupModalOpen, handleNewGroupModalClose] =
newGroupModalOpen, useBoolean(false);
handleNewGroupModalOpen,
handleNewGroupModalClose,
] = useBoolean(false);
const [query, setQuery] = React.useState(""); const [query, setQuery] = React.useState("");
const { const { auth, collectionGroupMemberships, groups, policies, toasts } =
auth, useStores();
collectionGroupMemberships,
groups,
policies,
toasts,
} = useStores();
const { fetchPage: fetchGroups } = groups; const { fetchPage: fetchGroups } = groups;
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -27,11 +27,8 @@ function AddPeopleToCollection({ collection }: Props) {
const { showToast } = useToasts(); const { showToast } = useToasts();
const team = useCurrentTeam(); const team = useCurrentTeam();
const { t } = useTranslation(); const { t } = useTranslation();
const [ const [inviteModalOpen, setInviteModalOpen, setInviteModalClosed] =
inviteModalOpen, useBoolean();
setInviteModalOpen,
setInviteModalClosed,
] = useBoolean();
const [query, setQuery] = React.useState(""); const [query, setQuery] = React.useState("");
const handleFilter = (ev: React.ChangeEvent<HTMLInputElement>) => { const handleFilter = (ev: React.ChangeEvent<HTMLInputElement>) => {

View File

@@ -44,11 +44,8 @@ function CollectionPermissions({ collectionId }: Props) {
const collection = collections.get(collectionId); const collection = collections.get(collectionId);
invariant(collection, "Collection not found"); invariant(collection, "Collection not found");
const [ const [addGroupModalOpen, handleAddGroupModalOpen, handleAddGroupModalClose] =
addGroupModalOpen, useBoolean();
handleAddGroupModalOpen,
handleAddGroupModalClose,
] = useBoolean();
const [ const [
addMemberModalOpen, addMemberModalOpen,

View File

@@ -245,7 +245,7 @@ const Menu = styled(CommentMenu)<{ dir?: "rtl" | "ltr" }>`
transition: opacity 100ms ease-in-out; transition: opacity 100ms ease-in-out;
color: ${s("textSecondary")}; color: ${s("textSecondary")};
&: ${hover}, &[aria-expanded= "true" ] { &: ${hover}, &[aria-expanded= "true"] {
opacity: 1; opacity: 1;
background: ${s("sidebarActiveBackground")}; background: ${s("sidebarActiveBackground")};
} }

View File

@@ -376,16 +376,8 @@ class DocumentScene extends React.Component<Props> {
}; };
render() { render() {
const { const { document, revision, readOnly, abilities, auth, ui, shareId, t } =
document, this.props;
revision,
readOnly,
abilities,
auth,
ui,
shareId,
t,
} = this.props;
const team = auth.team; const team = auth.team;
const isShare = !!shareId; const isShare = !!shareId;
const embedsDisabled = const embedsDisabled =

View File

@@ -48,10 +48,8 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
const { presence, ui } = useStores(); const { presence, ui } = useStores();
const token = useCurrentToken(); const token = useCurrentToken();
const [showCursorNames, setShowCursorNames] = React.useState(false); const [showCursorNames, setShowCursorNames] = React.useState(false);
const [ const [remoteProvider, setRemoteProvider] =
remoteProvider, React.useState<HocuspocusProvider | null>(null);
setRemoteProvider,
] = React.useState<HocuspocusProvider | null>(null);
const [isLocalSynced, setLocalSynced] = React.useState(false); const [isLocalSynced, setLocalSynced] = React.useState(false);
const [isRemoteSynced, setRemoteSynced] = React.useState(false); const [isRemoteSynced, setRemoteSynced] = React.useState(false);
const [ydoc] = React.useState(() => new Y.Doc()); const [ydoc] = React.useState(() => new Y.Doc());

View File

@@ -28,11 +28,8 @@ function AddPeopleToGroup(props: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
const [query, setQuery] = React.useState(""); const [query, setQuery] = React.useState("");
const [ const [inviteModalOpen, handleInviteModalOpen, handleInviteModalClose] =
inviteModalOpen, useBoolean(false);
handleInviteModalOpen,
handleInviteModalClose,
] = useBoolean(false);
const { fetchPage: fetchUsers } = users; const { fetchPage: fetchUsers } = users;
const debouncedFetch = React.useMemo( const debouncedFetch = React.useMemo(

View File

@@ -24,11 +24,8 @@ function Groups() {
const { groups } = useStores(); const { groups } = useStores();
const team = useCurrentTeam(); const team = useCurrentTeam();
const can = usePolicy(team); const can = usePolicy(team);
const [ const [newGroupModalOpen, handleNewGroupModalOpen, handleNewGroupModalClose] =
newGroupModalOpen, useBoolean();
handleNewGroupModalOpen,
handleNewGroupModalClose,
] = useBoolean();
return ( return (
<Scene <Scene

View File

@@ -28,11 +28,8 @@ import UserStatusFilter from "./components/UserStatusFilter";
function Members() { function Members() {
const location = useLocation(); const location = useLocation();
const history = useHistory(); const history = useHistory();
const [ const [inviteModalOpen, handleInviteModalOpen, handleInviteModalClose] =
inviteModalOpen, useBoolean();
handleInviteModalOpen,
handleInviteModalClose,
] = useBoolean();
const team = useCurrentTeam(); const team = useCurrentTeam();
const { users } = useStores(); const { users } = useStores();
const { t } = useTranslation(); const { t } = useTranslation();

View File

@@ -38,9 +38,11 @@ function Security() {
inviteRequired: team.inviteRequired, inviteRequired: team.inviteRequired,
}); });
const { data: providers, loading, request } = useRequest(() => const {
authenticationProviders.fetchPage({}) data: providers,
); loading,
request,
} = useRequest(() => authenticationProviders.fetchPage({}));
React.useEffect(() => { React.useEffect(() => {
if (!providers && !loading) { if (!providers && !loading) {

View File

@@ -31,9 +31,8 @@ function DomainManagement({ onSuccess }: Props) {
allowedDomains.length allowedDomains.length
); );
const [existingDomainsTouched, setExistingDomainsTouched] = React.useState( const [existingDomainsTouched, setExistingDomainsTouched] =
false React.useState(false);
);
const handleSaveDomains = React.useCallback(async () => { const handleSaveDomains = React.useCallback(async () => {
try { try {
@@ -67,19 +66,18 @@ function DomainManagement({ onSuccess }: Props) {
setAllowedDomains(newDomains); setAllowedDomains(newDomains);
}; };
const createOnDomainChangedHandler = (index: number) => ( const createOnDomainChangedHandler =
ev: React.ChangeEvent<HTMLInputElement> (index: number) => (ev: React.ChangeEvent<HTMLInputElement>) => {
) => { const newDomains = allowedDomains.slice();
const newDomains = allowedDomains.slice();
newDomains[index] = ev.currentTarget.value; newDomains[index] = ev.currentTarget.value;
setAllowedDomains(newDomains); setAllowedDomains(newDomains);
const touchedExistingDomain = index < lastKnownDomainCount; const touchedExistingDomain = index < lastKnownDomainCount;
if (touchedExistingDomain) { if (touchedExistingDomain) {
setExistingDomainsTouched(true); setExistingDomainsTouched(true);
} }
}; };
const showSaveChanges = const showSaveChanges =
existingDomainsTouched || existingDomainsTouched ||

View File

@@ -19,9 +19,11 @@ function UserDelete() {
const { auth } = useStores(); const { auth } = useStores();
const { showToast } = useToasts(); const { showToast } = useToasts();
const { t } = useTranslation(); const { t } = useTranslation();
const { register, handleSubmit: formHandleSubmit, formState } = useForm< const {
FormData register,
>(); handleSubmit: formHandleSubmit,
formState,
} = useForm<FormData>();
const handleRequestDelete = React.useCallback( const handleRequestDelete = React.useCallback(
async (ev: React.SyntheticEvent) => { async (ev: React.SyntheticEvent) => {

View File

@@ -2,9 +2,7 @@ import AuthenticationProvider from "~/models/AuthenticationProvider";
import BaseStore, { RPCAction } from "./BaseStore"; import BaseStore, { RPCAction } from "./BaseStore";
import RootStore from "./RootStore"; import RootStore from "./RootStore";
export default class AuthenticationProvidersStore extends BaseStore< export default class AuthenticationProvidersStore extends BaseStore<AuthenticationProvider> {
AuthenticationProvider
> {
actions = [RPCAction.List, RPCAction.Update]; actions = [RPCAction.List, RPCAction.Update];
constructor(rootStore: RootStore) { constructor(rootStore: RootStore) {

View File

@@ -7,9 +7,7 @@ import { client } from "~/utils/ApiClient";
import BaseStore, { PAGINATION_SYMBOL, RPCAction } from "./BaseStore"; import BaseStore, { PAGINATION_SYMBOL, RPCAction } from "./BaseStore";
import RootStore from "./RootStore"; import RootStore from "./RootStore";
export default class CollectionGroupMembershipsStore extends BaseStore< export default class CollectionGroupMembershipsStore extends BaseStore<CollectionGroupMembership> {
CollectionGroupMembership
> {
actions = [RPCAction.Create, RPCAction.Delete]; actions = [RPCAction.Create, RPCAction.Delete];
constructor(rootStore: RootStore) { constructor(rootStore: RootStore) {

View File

@@ -3,9 +3,7 @@ import WebhookSubscription from "~/models/WebhookSubscription";
import BaseStore, { RPCAction } from "./BaseStore"; import BaseStore, { RPCAction } from "./BaseStore";
import RootStore from "./RootStore"; import RootStore from "./RootStore";
export default class WebhookSubscriptionsStore extends BaseStore< export default class WebhookSubscriptionsStore extends BaseStore<WebhookSubscription> {
WebhookSubscription
> {
actions = [ actions = [
RPCAction.List, RPCAction.List,
RPCAction.Create, RPCAction.Create,

View File

@@ -328,7 +328,7 @@
"lint-staged": "^13.1.0", "lint-staged": "^13.1.0",
"nodemon": "^2.0.22", "nodemon": "^2.0.22",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.0.5", "prettier": "^2.8.8",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"rimraf": "^2.5.4", "rimraf": "^2.5.4",
"rollup-plugin-webpack-stats": "^0.2.0", "rollup-plugin-webpack-stats": "^0.2.0",

View File

@@ -19,11 +19,8 @@ type Props = {
const WebhookSubscriptionListItem = ({ webhook }: Props) => { const WebhookSubscriptionListItem = ({ webhook }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { dialogs } = useStores(); const { dialogs } = useStores();
const [ const [editModalOpen, handleEditModalOpen, handleEditModalClose] =
editModalOpen, useBoolean();
handleEditModalOpen,
handleEditModalClose,
] = useBoolean();
const showDeletionConfirmation = React.useCallback(() => { const showDeletionConfirmation = React.useCallback(() => {
dialogs.openModal({ dialogs.openModal({

View File

@@ -7,7 +7,7 @@ const emptyFn = function () {};
const callableHandlers = { const callableHandlers = {
get<T, P extends keyof T>(_target: T, _prop: P, _receiver: any): T[P] { get<T, P extends keyof T>(_target: T, _prop: P, _receiver: any): T[P] {
const newMock = new Proxy(emptyFn, callableHandlers); const newMock = new Proxy(emptyFn, callableHandlers);
return (newMock as any) as T[P]; return newMock as any as T[P];
}, },
apply<T extends (...args: any) => any, A extends Parameters<T>>( apply<T extends (...args: any) => any, A extends Parameters<T>>(
@@ -16,7 +16,7 @@ const callableHandlers = {
_args: A _args: A
): ReturnType<T> { ): ReturnType<T> {
const newMock = new Proxy(emptyFn, callableHandlers); const newMock = new Proxy(emptyFn, callableHandlers);
return (newMock as any) as ReturnType<T>; return newMock as any as ReturnType<T>;
}, },
}; };

View File

@@ -88,9 +88,8 @@ export default class PersistenceExtension implements Extension {
// Find the collaborators that have modified the document since it was last // Find the collaborators that have modified the document since it was last
// persisted and clear the map, if there's no collaborators then we don't // persisted and clear the map, if there's no collaborators then we don't
// need to persist the document. // need to persist the document.
const documentCollaboratorIds = this.documentCollaboratorIds.get( const documentCollaboratorIds =
documentName this.documentCollaboratorIds.get(documentName);
);
if (!documentCollaboratorIds) { if (!documentCollaboratorIds) {
Logger.debug("multiplayer", `No changes for ${documentName}`); Logger.debug("multiplayer", `No changes for ${documentName}`);
return; return;

View File

@@ -30,8 +30,7 @@ const importMapping: ImportableFile[] = [
getMarkdown: docxToMarkdown, getMarkdown: docxToMarkdown,
}, },
{ {
type: type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
getMarkdown: docxToMarkdown, getMarkdown: docxToMarkdown,
}, },
{ {

View File

@@ -171,12 +171,12 @@ class Logger {
if (isString(input)) { if (isString(input)) {
if (sensitiveFields.some((field) => input.includes(field))) { if (sensitiveFields.some((field) => input.includes(field))) {
return ("[Filtered]" as any) as T; return "[Filtered]" as any as T;
} }
} }
if (isArray(input)) { if (isArray(input)) {
return (input.map(this.sanitize) as any) as T; return input.map(this.sanitize) as any as T;
} }
if (isObject(input)) { if (isObject(input)) {

View File

@@ -26,7 +26,7 @@ import env from "@server/env";
import tracer from "./tracer"; import tracer from "./tracer";
import * as Tracing from "./tracer"; import * as Tracing from "./tracer";
type DDTag = typeof DDTags[keyof typeof DDTags]; type DDTag = (typeof DDTags)[keyof typeof DDTags];
type Tags = { type Tags = {
[tag in DDTag]?: any; [tag in DDTag]?: any;
@@ -55,72 +55,74 @@ interface TraceConfig {
* *
* @param config Optional configuration for the span that will be created for this trace. * @param config Optional configuration for the span that will be created for this trace.
*/ */
export const traceFunction = (config: TraceConfig) => < export const traceFunction =
F extends (...args: any[]) => any, (config: TraceConfig) =>
P extends Parameters<F>, <
R extends ReturnType<F> F extends (...args: any[]) => any,
>( P extends Parameters<F>,
target: F R extends ReturnType<F>
): F => >(
env.ENVIRONMENT === "test" target: F
? target ): F =>
: (function wrapperFn(this: any, ...args: P): R { env.ENVIRONMENT === "test"
const { className, methodName = target.name, tags } = config; ? target
const childOf = config.isRoot : (function wrapperFn(this: any, ...args: P): R {
? undefined const { className, methodName = target.name, tags } = config;
: tracer.scope().active() || undefined; const childOf = config.isRoot
? undefined
: tracer.scope().active() || undefined;
const spanName = config.spanName || className || "DEFAULT_SPAN_NAME"; const spanName = config.spanName || className || "DEFAULT_SPAN_NAME";
const resourceName = config.resourceName const resourceName = config.resourceName
? config.resourceName ? config.resourceName
: methodName; : methodName;
const spanOptions: SpanOptions = { const spanOptions: SpanOptions = {
childOf, childOf,
tags: { tags: {
[DDTags.RESOURCE_NAME]: resourceName, [DDTags.RESOURCE_NAME]: resourceName,
...tags, ...tags,
}, },
}; };
const span = tracer.startSpan(spanName, spanOptions); const span = tracer.startSpan(spanName, spanOptions);
if (!span) { if (!span) {
return target.call(this, ...args); return target.call(this, ...args);
}
if (config.serviceName) {
span.setTag(
DDTags.SERVICE_NAME,
`${env.DD_SERVICE}-${config.serviceName}`
);
}
if (config.makeSearchable) {
span.setTag(DDTags.ANALYTICS, true);
}
// The callback fn needs to be wrapped in an arrow fn as the activate fn clobbers `this`
return tracer.scope().activate(span, () => {
const output = target.call(this, ...args);
if (output && typeof output.then === "function") {
output
.catch((error: Error | undefined) => {
if (error instanceof Error) {
Tracing.setError(error, span);
}
})
.finally(() => {
span.finish();
});
} else {
span.finish();
} }
return output; if (config.serviceName) {
}); span.setTag(
} as F); DDTags.SERVICE_NAME,
`${env.DD_SERVICE}-${config.serviceName}`
);
}
if (config.makeSearchable) {
span.setTag(DDTags.ANALYTICS, true);
}
// The callback fn needs to be wrapped in an arrow fn as the activate fn clobbers `this`
return tracer.scope().activate(span, () => {
const output = target.call(this, ...args);
if (output && typeof output.then === "function") {
output
.catch((error: Error | undefined) => {
if (error instanceof Error) {
Tracing.setError(error, span);
}
})
.finally(() => {
span.finish();
});
} else {
span.finish();
}
return output;
});
} as F);
const traceMethod = (config?: TraceConfig) => const traceMethod = (config?: TraceConfig) =>
function <R, A extends any[], F extends (...args: A) => R>( function <R, A extends any[], F extends (...args: A) => R>(

View File

@@ -98,8 +98,9 @@ class AuthenticationProvider extends Model {
} }
disable = async (options?: SaveOptions<AuthenticationProvider>) => { disable = async (options?: SaveOptions<AuthenticationProvider>) => {
const res = await (this const res = await (
.constructor as typeof AuthenticationProvider).findAndCountAll({ this.constructor as typeof AuthenticationProvider
).findAndCountAll({
...options, ...options,
where: { where: {
teamId: this.teamId, teamId: this.teamId,

View File

@@ -527,8 +527,9 @@ class Document extends ParanoidModel {
const getChildDocumentIds = async ( const getChildDocumentIds = async (
...parentDocumentId: string[] ...parentDocumentId: string[]
): Promise<string[]> => { ): Promise<string[]> => {
const childDocuments = await (this const childDocuments = await (
.constructor as typeof Document).findAll({ this.constructor as typeof Document
).findAll({
attributes: ["id"], attributes: ["id"],
where: { where: {
parentDocumentId, parentDocumentId,
@@ -560,8 +561,9 @@ class Document extends ParanoidModel {
// Helper to archive all child documents for a document // Helper to archive all child documents for a document
const archiveChildren = async (parentDocumentId: string) => { const archiveChildren = async (parentDocumentId: string) => {
const childDocuments = await (this const childDocuments = await (
.constructor as typeof Document).findAll({ this.constructor as typeof Document
).findAll({
where: { where: {
parentDocumentId, parentDocumentId,
}, },

View File

@@ -1,15 +1,13 @@
/* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/ban-types */
const Deprecated = (message?: string) => ( const Deprecated =
target: Object, (message?: string) => (target: Object, propertyKey: string) => {
propertyKey: string if (process.env[propertyKey]) {
) => { // eslint-disable-next-line no-console
if (process.env[propertyKey]) { console.warn(
// eslint-disable-next-line no-console `The environment variable ${propertyKey} is deprecated and will be removed in a future release. ${message}`
console.warn( );
`The environment variable ${propertyKey} is deprecated and will be removed in a future release. ${message}` }
); };
}
};
export default Deprecated; export default Deprecated;

View File

@@ -4,9 +4,7 @@ import NotificationHelper from "@server/models/helpers/NotificationHelper";
import { CollectionEvent } from "@server/types"; import { CollectionEvent } from "@server/types";
import BaseTask, { TaskPriority } from "./BaseTask"; import BaseTask, { TaskPriority } from "./BaseTask";
export default class CollectionCreatedNotificationsTask extends BaseTask< export default class CollectionCreatedNotificationsTask extends BaseTask<CollectionEvent> {
CollectionEvent
> {
public async perform(event: CollectionEvent) { public async perform(event: CollectionEvent) {
const collection = await Collection.findByPk(event.collectionId); const collection = await Collection.findByPk(event.collectionId);
@@ -15,10 +13,11 @@ export default class CollectionCreatedNotificationsTask extends BaseTask<
return; return;
} }
const recipients = await NotificationHelper.getCollectionNotificationRecipients( const recipients =
collection, await NotificationHelper.getCollectionNotificationRecipients(
NotificationEventType.CreateCollection collection,
); NotificationEventType.CreateCollection
);
for (const recipient of recipients) { for (const recipient of recipients) {
// Suppress notifications for suspended users // Suppress notifications for suspended users

View File

@@ -7,9 +7,7 @@ import ProsemirrorHelper from "@server/models/helpers/ProsemirrorHelper";
import { CommentEvent } from "@server/types"; import { CommentEvent } from "@server/types";
import BaseTask, { TaskPriority } from "./BaseTask"; import BaseTask, { TaskPriority } from "./BaseTask";
export default class CommentCreatedNotificationsTask extends BaseTask< export default class CommentCreatedNotificationsTask extends BaseTask<CommentEvent> {
CommentEvent
> {
public async perform(event: CommentEvent) { public async perform(event: CommentEvent) {
const [document, comment] = await Promise.all([ const [document, comment] = await Promise.all([
Document.scope("withCollection").findOne({ Document.scope("withCollection").findOne({

View File

@@ -4,9 +4,7 @@ import ProsemirrorHelper from "@server/models/helpers/ProsemirrorHelper";
import { CommentEvent, CommentUpdateEvent } from "@server/types"; import { CommentEvent, CommentUpdateEvent } from "@server/types";
import BaseTask, { TaskPriority } from "./BaseTask"; import BaseTask, { TaskPriority } from "./BaseTask";
export default class CommentUpdatedNotificationsTask extends BaseTask< export default class CommentUpdatedNotificationsTask extends BaseTask<CommentEvent> {
CommentEvent
> {
public async perform(event: CommentUpdateEvent) { public async perform(event: CommentUpdateEvent) {
const [document, comment] = await Promise.all([ const [document, comment] = await Promise.all([
Document.scope("withCollection").findOne({ Document.scope("withCollection").findOne({

View File

@@ -6,9 +6,7 @@ import NotificationHelper from "@server/models/helpers/NotificationHelper";
import { DocumentEvent } from "@server/types"; import { DocumentEvent } from "@server/types";
import BaseTask, { TaskPriority } from "./BaseTask"; import BaseTask, { TaskPriority } from "./BaseTask";
export default class DocumentPublishedNotificationsTask extends BaseTask< export default class DocumentPublishedNotificationsTask extends BaseTask<DocumentEvent> {
DocumentEvent
> {
public async perform(event: DocumentEvent) { public async perform(event: DocumentEvent) {
const document = await Document.findByPk(event.documentId, { const document = await Document.findByPk(event.documentId, {
includeState: true, includeState: true,

View File

@@ -287,7 +287,8 @@ export default class ImportNotionTask extends ImportTask {
/** /**
* Regex to find markdown images of all types * Regex to find markdown images of all types
*/ */
private ImageRegex = /!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<title>[^\][”]+)?”?\)/g; private ImageRegex =
/!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<title>[^\][”]+)?”?\)/g;
/** /**
* Regex to find markdown links containing ID's that look like UUID's with the * Regex to find markdown links containing ID's that look like UUID's with the
@@ -298,5 +299,6 @@ export default class ImportNotionTask extends ImportTask {
/** /**
* Regex to find Notion document UUID's in the title of a document. * Regex to find Notion document UUID's in the title of a document.
*/ */
private NotionUUIDRegex = /\s([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}|[0-9a-fA-F]{32})$/; private NotionUUIDRegex =
/\s([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}|[0-9a-fA-F]{32})$/;
} }

View File

@@ -11,9 +11,7 @@ import NotificationHelper from "@server/models/helpers/NotificationHelper";
import { RevisionEvent } from "@server/types"; import { RevisionEvent } from "@server/types";
import BaseTask, { TaskPriority } from "./BaseTask"; import BaseTask, { TaskPriority } from "./BaseTask";
export default class RevisionCreatedNotificationsTask extends BaseTask< export default class RevisionCreatedNotificationsTask extends BaseTask<RevisionEvent> {
RevisionEvent
> {
public async perform(event: RevisionEvent) { public async perform(event: RevisionEvent) {
const [document, revision] = await Promise.all([ const [document, revision] = await Promise.all([
Document.findByPk(event.documentId, { includeState: true }), Document.findByPk(event.documentId, { includeState: true }),

View File

@@ -133,14 +133,12 @@ router.post("auth.info", auth(), async (ctx: APIContext) => {
includeDetails: true, includeDetails: true,
}), }),
team: presentTeam(team), team: presentTeam(team),
availableTeams: uniqBy( availableTeams: uniqBy([...signedInTeams, ...availableTeams], "id").map(
[...signedInTeams, ...availableTeams], (team) =>
"id" presentAvailableTeam(
).map((team) => team,
presentAvailableTeam( signedInTeamIds.includes(team.id) || team.id === user.teamId
team, )
signedInTeamIds.includes(team.id) || team.id === user.teamId
)
), ),
}, },
policies: presentPolicies(user, [team]), policies: presentPolicies(user, [team]),

View File

@@ -168,10 +168,8 @@ router.post(
rateLimiter(RateLimiterStrategy.TenPerHour), rateLimiter(RateLimiterStrategy.TenPerHour),
auth(), auth(),
async (ctx: APIContext) => { async (ctx: APIContext) => {
const { const { attachmentId, format = FileOperationFormat.MarkdownZip } =
attachmentId, ctx.request.body;
format = FileOperationFormat.MarkdownZip,
} = ctx.request.body;
assertUuid(attachmentId, "attachmentId is required"); assertUuid(attachmentId, "attachmentId is required");
const { user } = ctx.state.auth; const { user } = ctx.state.auth;
@@ -630,16 +628,8 @@ router.post(
); );
router.post("collections.update", auth(), async (ctx: APIContext) => { router.post("collections.update", auth(), async (ctx: APIContext) => {
const { const { id, name, description, icon, permission, color, sort, sharing } =
id, ctx.request.body;
name,
description,
icon,
permission,
color,
sort,
sharing,
} = ctx.request.body;
if (color) { if (color) {
assertHexColor(color, "Invalid hex value (please use format #FFFFFF)"); assertHexColor(color, "Invalid hex value (please use format #FFFFFF)");

View File

@@ -995,21 +995,18 @@ router.post(
} }
} }
const { const { documents, collections, collectionChanged } =
documents, await sequelize.transaction(async (transaction) =>
collections, documentMover({
collectionChanged, user,
} = await sequelize.transaction(async (transaction) => document,
documentMover({ collectionId,
user, parentDocumentId,
document, index,
collectionId, ip: ctx.request.ip,
parentDocumentId, transaction,
index, })
ip: ctx.request.ip, );
transaction,
})
);
ctx.body = { ctx.body = {
data: { data: {

View File

@@ -39,50 +39,49 @@ export default function init(
], ],
}); });
server.on("upgrade", function ( server.on(
req: IncomingMessage, "upgrade",
socket: Duplex, function (req: IncomingMessage, socket: Duplex, head: Buffer) {
head: Buffer if (req.url?.startsWith(path)) {
) { // parse document id and close connection if not present in request
if (req.url?.startsWith(path)) { const documentId = url
// parse document id and close connection if not present in request .parse(req.url)
const documentId = url .pathname?.replace(path, "")
.parse(req.url) .split("/")
.pathname?.replace(path, "") .pop();
.split("/")
.pop();
if (documentId) { if (documentId) {
wss.handleUpgrade(req, socket, head, (client) => { wss.handleUpgrade(req, socket, head, (client) => {
// Handle websocket connection errors as soon as the client is upgraded // Handle websocket connection errors as soon as the client is upgraded
client.on("error", (error) => { client.on("error", (error) => {
Logger.error( Logger.error(
`Websocket error`, `Websocket error`,
error, error,
{ {
documentId, documentId,
}, },
req req
); );
});
hocuspocus.handleConnection(client, req, documentId);
}); });
return;
}
}
hocuspocus.handleConnection(client, req, documentId); if (
}); req.url?.startsWith("/realtime") &&
serviceNames.includes("websockets")
) {
// Nothing to do, the websockets service will handle this request
return; return;
} }
}
if ( // If the collaboration service is running it will close the connection
req.url?.startsWith("/realtime") && socket.end(`HTTP/1.1 400 Bad Request\r\n`);
serviceNames.includes("websockets")
) {
// Nothing to do, the websockets service will handle this request
return;
} }
);
// If the collaboration service is running it will close the connection
socket.end(`HTTP/1.1 400 Bad Request\r\n`);
});
ShutdownHelper.add("collaboration", ShutdownOrder.normal, () => ShutdownHelper.add("collaboration", ShutdownOrder.normal, () =>
hocuspocus.destroy() hocuspocus.destroy()

View File

@@ -53,25 +53,24 @@ export default function init(
); );
} }
server.on("upgrade", function ( server.on(
req: IncomingMessage, "upgrade",
socket: Duplex, function (req: IncomingMessage, socket: Duplex, head: Buffer) {
head: Buffer if (req.url?.startsWith(path)) {
) { invariant(ioHandleUpgrade, "Existing upgrade handler must exist");
if (req.url?.startsWith(path)) { ioHandleUpgrade(req, socket, head);
invariant(ioHandleUpgrade, "Existing upgrade handler must exist"); return;
ioHandleUpgrade(req, socket, head); }
return;
}
if (serviceNames.includes("collaboration")) { if (serviceNames.includes("collaboration")) {
// Nothing to do, the collaboration service will handle this request // Nothing to do, the collaboration service will handle this request
return; return;
} }
// If the collaboration service isn't running then we need to close the connection // If the collaboration service isn't running then we need to close the connection
socket.end(`HTTP/1.1 400 Bad Request\r\n`); socket.end(`HTTP/1.1 400 Bad Request\r\n`);
}); }
);
ShutdownHelper.add("websockets", ShutdownOrder.normal, async () => { ShutdownHelper.add("websockets", ShutdownOrder.normal, async () => {
Metrics.gaugePerInstance("websockets.count", 0); Metrics.gaugePerInstance("websockets.count", 0);

View File

@@ -55,7 +55,8 @@ it("should parse attachment ID from markdown with title", () => {
it("should parse multiple attachment IDs from markdown", () => { it("should parse multiple attachment IDs from markdown", () => {
const uuid = uuidv4(); const uuid = uuidv4();
const uuid2 = uuidv4(); const uuid2 = uuidv4();
const results = parseAttachmentIds(`![caption text](/api/attachments.redirect?id=${uuid}) const results =
parseAttachmentIds(`![caption text](/api/attachments.redirect?id=${uuid})
some text some text

View File

@@ -1,7 +1,9 @@
import { uniq, compact } from "lodash"; import { uniq, compact } from "lodash";
const attachmentRedirectRegex = /\/api\/attachments\.redirect\?id=(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi; const attachmentRedirectRegex =
const attachmentPublicRegex = /public\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi; /\/api\/attachments\.redirect\?id=(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;
const attachmentPublicRegex =
/public\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/(?<id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi;
export default function parseAttachmentIds( export default function parseAttachmentIds(
text: string, text: string,

View File

@@ -14,17 +14,13 @@ const UPDATES_KEY = "UPDATES_KEY";
export async function checkUpdates() { export async function checkUpdates() {
const secret = env.SECRET_KEY.slice(0, 6) + env.URL; const secret = env.SECRET_KEY.slice(0, 6) + env.URL;
const id = crypto.createHash("sha256").update(secret).digest("hex"); const id = crypto.createHash("sha256").update(secret).digest("hex");
const [ const [userCount, teamCount, collectionCount, documentCount] =
userCount, await Promise.all([
teamCount, User.count(),
collectionCount, Team.count(),
documentCount, Collection.count(),
] = await Promise.all([ Document.count(),
User.count(), ]);
Team.count(),
Collection.count(),
Document.count(),
]);
const body = JSON.stringify({ const body = JSON.stringify({
id, id,
version: 1, version: 1,

View File

@@ -18,7 +18,7 @@ export function CannotUseWithout(
options: validationOptions, options: validationOptions,
validator: { validator: {
validate<T>(value: T, args: ValidationArguments) { validate<T>(value: T, args: ValidationArguments) {
const object = (args.object as unknown) as T; const object = args.object as unknown as T;
const required = args.constraints[0] as string; const required = args.constraints[0] as string;
return object[required] !== undefined; return object[required] !== undefined;
}, },

View File

@@ -96,9 +96,8 @@ export const newlineInCode = (state: EditorState, dispatch: Dispatch) => {
if (text) { if (text) {
const splitByNewLine = text.split("\n"); const splitByNewLine = text.split("\n");
const numOfSpaces = splitByNewLine[splitByNewLine.length - 1].search( const numOfSpaces =
/\S|$/ splitByNewLine[splitByNewLine.length - 1].search(/\S|$/);
);
newText += " ".repeat(numOfSpaces); newText += " ".repeat(numOfSpaces);
} }

View File

@@ -100,18 +100,18 @@ const Image = (
document.removeEventListener("mousemove", handlePointerMove); document.removeEventListener("mousemove", handlePointerMove);
}; };
const handlePointerDown = (dragging: "left" | "right") => ( const handlePointerDown =
event: React.PointerEvent<HTMLDivElement> (dragging: "left" | "right") =>
) => { (event: React.PointerEvent<HTMLDivElement>) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setSizeAtDragStart({ setSizeAtDragStart({
width: constrainWidth(size.width), width: constrainWidth(size.width),
height: size.height, height: size.height,
}); });
setOffset(event.pageX); setOffset(event.pageX);
setDragging(dragging); setDragging(dragging);
}; };
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") { if (event.key === "Escape") {
@@ -170,11 +170,14 @@ const Image = (
onClick={dragging ? undefined : props.onClick} onClick={dragging ? undefined : props.onClick}
style={widthStyle} style={widthStyle}
> >
{!dragging && size.width > 60 && size.height > 60 && props.onDownload && ( {!dragging &&
<Button onClick={props.onDownload}> size.width > 60 &&
<DownloadIcon /> size.height > 60 &&
</Button> props.onDownload && (
)} <Button onClick={props.onDownload}>
<DownloadIcon />
</Button>
)}
<ImageZoom zoomMargin={24}> <ImageZoom zoomMargin={24}>
<img <img
style={{ style={{

View File

@@ -1,8 +1,9 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
export default function useComponentSize( export default function useComponentSize(ref: React.RefObject<HTMLElement>): {
ref: React.RefObject<HTMLElement> width: number;
): { width: number; height: number } { height: number;
} {
const [size, setSize] = useState({ const [size, setSize] = useState({
width: ref.current?.clientWidth || 0, width: ref.current?.clientWidth || 0,
height: ref.current?.clientHeight || 0, height: ref.current?.clientHeight || 0,

View File

@@ -2,7 +2,8 @@ import * as React from "react";
import Frame from "../components/Frame"; import Frame from "../components/Frame";
import { EmbedProps as Props } from "."; import { EmbedProps as Props } from ".";
const URL_REGEX = /(?:https?:\/\/)?(www\.bilibili\.com)\/video\/([\w\d]+)?(\?\S+)?/i; const URL_REGEX =
/(?:https?:\/\/)?(www\.bilibili\.com)\/video\/([\w\d]+)?(\?\S+)?/i;
export default function Bilibili(props: Props) { export default function Bilibili(props: Props) {
const { matches } = props.attrs; const { matches } = props.attrs;

View File

@@ -3,8 +3,10 @@ import Frame from "../components/Frame";
import ImageZoom from "../components/ImageZoom"; import ImageZoom from "../components/ImageZoom";
import { EmbedProps as Props } from "."; import { EmbedProps as Props } from ".";
const IFRAME_REGEX = /^https:\/\/(invis\.io\/.*)|(projects\.invisionapp\.com\/share\/.*)$/; const IFRAME_REGEX =
const IMAGE_REGEX = /^https:\/\/(opal\.invisionapp\.com\/static-signed\/live-embed\/.*)$/; /^https:\/\/(invis\.io\/.*)|(projects\.invisionapp\.com\/share\/.*)$/;
const IMAGE_REGEX =
/^https:\/\/(opal\.invisionapp\.com\/static-signed\/live-embed\/.*)$/;
function InVision(props: Props) { function InVision(props: Props) {
if (IMAGE_REGEX.test(props.attrs.href)) { if (IMAGE_REGEX.test(props.attrs.href)) {

View File

@@ -164,9 +164,8 @@ export default function Mermaid({
} }
if (diagramToggled) { if (diagramToggled) {
pluginState.diagramVisibility[ pluginState.diagramVisibility[mermaidMeta.toggleDiagram] =
mermaidMeta.toggleDiagram !pluginState.diagramVisibility[mermaidMeta.toggleDiagram];
] = !pluginState.diagramVisibility[mermaidMeta.toggleDiagram];
} }
if ( if (

View File

@@ -114,15 +114,13 @@ export default class Embed extends Node {
} }
commands({ type }: { type: NodeType }) { commands({ type }: { type: NodeType }) {
return (attrs: Record<string, any>) => ( return (attrs: Record<string, any>) =>
state: EditorState, (state: EditorState, dispatch: Dispatch) => {
dispatch: Dispatch dispatch(
) => { state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
dispatch( );
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView() return true;
); };
return true;
};
} }
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) { toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {

View File

@@ -22,7 +22,8 @@ export default class Emoji extends Suggestion {
return { return {
type: SuggestionsMenuType.Emoji, type: SuggestionsMenuType.Emoji,
openRegex: /(?:^|\s):([0-9a-zA-Z_+-]+)?$/, openRegex: /(?:^|\s):([0-9a-zA-Z_+-]+)?$/,
closeRegex: /(?:^|\s):(([0-9a-zA-Z_+-]*\s+)|(\s+[0-9a-zA-Z_+-]+)|[^0-9a-zA-Z_+-]+)$/, closeRegex:
/(?:^|\s):(([0-9a-zA-Z_+-]*\s+)|(\s+[0-9a-zA-Z_+-]+)|[^0-9a-zA-Z_+-]+)$/,
enabledInTable: true, enabledInTable: true,
}; };
} }
@@ -77,24 +78,22 @@ export default class Emoji extends Suggestion {
} }
commands({ type }: { type: NodeType; schema: Schema }) { commands({ type }: { type: NodeType; schema: Schema }) {
return (attrs: Record<string, string>) => ( return (attrs: Record<string, string>) =>
state: EditorState, (state: EditorState, dispatch: Dispatch) => {
dispatch: Dispatch const { selection } = state;
) => { const position =
const { selection } = state; selection instanceof TextSelection
const position = ? selection.$cursor?.pos
selection instanceof TextSelection : selection.$to.pos;
? selection.$cursor?.pos if (position === undefined) {
: selection.$to.pos; return false;
if (position === undefined) { }
return false;
}
const node = type.create(attrs); const node = type.create(attrs);
const transaction = state.tr.insert(position, node); const transaction = state.tr.insert(position, node);
dispatch(transaction); dispatch(transaction);
return true; return true;
}; };
} }
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) { toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {

View File

@@ -28,15 +28,13 @@ export default class HorizontalRule extends Node {
} }
commands({ type }: { type: NodeType }) { commands({ type }: { type: NodeType }) {
return (attrs: Record<string, any>) => ( return (attrs: Record<string, any>) =>
state: EditorState, (state: EditorState, dispatch: Dispatch) => {
dispatch: Dispatch dispatch(
) => { state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView()
dispatch( );
state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView() return true;
); };
return true;
};
} }
keys({ type }: { type: NodeType }) { keys({ type }: { type: NodeType }) {

View File

@@ -189,35 +189,31 @@ export default class Image extends SimpleImage {
]; ];
} }
handleChangeSize = ({ handleChangeSize =
node, ({ node, getPos }: { node: ProsemirrorNode; getPos: () => number }) =>
getPos, ({ width, height }: { width: number; height?: number }) => {
}: { const { view } = this.editor;
node: ProsemirrorNode; const { tr } = view.state;
getPos: () => number;
}) => ({ width, height }: { width: number; height?: number }) => {
const { view } = this.editor;
const { tr } = view.state;
const pos = getPos(); const pos = getPos();
const transaction = tr const transaction = tr
.setNodeMarkup(pos, undefined, { .setNodeMarkup(pos, undefined, {
...node.attrs, ...node.attrs,
width, width,
height, height,
}) })
.setMeta("addToHistory", true); .setMeta("addToHistory", true);
const $pos = transaction.doc.resolve(getPos()); const $pos = transaction.doc.resolve(getPos());
view.dispatch(transaction.setSelection(new NodeSelection($pos))); view.dispatch(transaction.setSelection(new NodeSelection($pos)));
}; };
handleDownload = ({ node }: { node: ProsemirrorNode }) => ( handleDownload =
event: React.MouseEvent ({ node }: { node: ProsemirrorNode }) =>
) => { (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
downloadImageNode(node); downloadImageNode(node);
}; };
component = (props: ComponentProps) => ( component = (props: ComponentProps) => (
<ImageComponent <ImageComponent
@@ -360,7 +356,8 @@ export default class Image extends SimpleImage {
* ![](image.jpg "class") -> [, "", "image.jpg", "small"] * ![](image.jpg "class") -> [, "", "image.jpg", "small"]
* ![Lorem](image.jpg "class") -> [, "Lorem", "image.jpg", "small"] * ![Lorem](image.jpg "class") -> [, "Lorem", "image.jpg", "small"]
*/ */
const IMAGE_INPUT_REGEX = /!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/; const IMAGE_INPUT_REGEX =
/!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/;
return [ return [
new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => { new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => {

View File

@@ -80,24 +80,22 @@ export default class Mention extends Suggestion {
} }
commands({ type }: { type: NodeType; schema: Schema }) { commands({ type }: { type: NodeType; schema: Schema }) {
return (attrs: Record<string, string>) => ( return (attrs: Record<string, string>) =>
state: EditorState, (state: EditorState, dispatch: Dispatch) => {
dispatch: Dispatch const { selection } = state;
) => { const position =
const { selection } = state; selection instanceof TextSelection
const position = ? selection.$cursor?.pos
selection instanceof TextSelection : selection.$to.pos;
? selection.$cursor?.pos if (position === undefined) {
: selection.$to.pos; return false;
if (position === undefined) { }
return false;
}
const node = type.create(attrs); const node = type.create(attrs);
const transaction = state.tr.insert(position, node); const transaction = state.tr.insert(position, node);
dispatch(transaction); dispatch(transaction);
return true; return true;
}; };
} }
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) { toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {

View File

@@ -75,73 +75,65 @@ export default class SimpleImage extends Node {
}; };
} }
handleKeyDown = ({ handleKeyDown =
node, ({ node, getPos }: { node: ProsemirrorNode; getPos: () => number }) =>
getPos, (event: React.KeyboardEvent<HTMLSpanElement>) => {
}: { // Pressing Enter in the caption field should move the cursor/selection
node: ProsemirrorNode; // below the image
getPos: () => number; if (event.key === "Enter") {
}) => (event: React.KeyboardEvent<HTMLSpanElement>) => { event.preventDefault();
// Pressing Enter in the caption field should move the cursor/selection
// below the image const { view } = this.editor;
if (event.key === "Enter") { const $pos = view.state.doc.resolve(getPos() + node.nodeSize);
view.dispatch(
view.state.tr.setSelection(new TextSelection($pos)).split($pos.pos)
);
view.focus();
return;
}
// Pressing Backspace in an an empty caption field should remove the entire
// image, leaving an empty paragraph
if (event.key === "Backspace" && event.currentTarget.innerText === "") {
const { view } = this.editor;
const $pos = view.state.doc.resolve(getPos());
const tr = view.state.tr.setSelection(new NodeSelection($pos));
view.dispatch(tr.deleteSelection());
view.focus();
return;
}
};
handleBlur =
({ node, getPos }: { node: ProsemirrorNode; getPos: () => number }) =>
(event: React.FocusEvent<HTMLSpanElement>) => {
const caption = event.currentTarget.innerText;
if (caption === node.attrs.alt) {
return;
}
const { view } = this.editor;
const { tr } = view.state;
// update meta on object
const pos = getPos();
const transaction = tr.setNodeMarkup(pos, undefined, {
...node.attrs,
alt: caption,
});
view.dispatch(transaction);
};
handleSelect =
({ getPos }: { getPos: () => number }) =>
(event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
const { view } = this.editor;
const $pos = view.state.doc.resolve(getPos() + node.nodeSize);
view.dispatch(
view.state.tr.setSelection(new TextSelection($pos)).split($pos.pos)
);
view.focus();
return;
}
// Pressing Backspace in an an empty caption field should remove the entire
// image, leaving an empty paragraph
if (event.key === "Backspace" && event.currentTarget.innerText === "") {
const { view } = this.editor; const { view } = this.editor;
const $pos = view.state.doc.resolve(getPos()); const $pos = view.state.doc.resolve(getPos());
const tr = view.state.tr.setSelection(new NodeSelection($pos)); const transaction = view.state.tr.setSelection(new NodeSelection($pos));
view.dispatch(tr.deleteSelection()); view.dispatch(transaction);
view.focus(); };
return;
}
};
handleBlur = ({
node,
getPos,
}: {
node: ProsemirrorNode;
getPos: () => number;
}) => (event: React.FocusEvent<HTMLSpanElement>) => {
const caption = event.currentTarget.innerText;
if (caption === node.attrs.alt) {
return;
}
const { view } = this.editor;
const { tr } = view.state;
// update meta on object
const pos = getPos();
const transaction = tr.setNodeMarkup(pos, undefined, {
...node.attrs,
alt: caption,
});
view.dispatch(transaction);
};
handleSelect = ({ getPos }: { getPos: () => number }) => (
event: React.MouseEvent
) => {
event.preventDefault();
const { view } = this.editor;
const $pos = view.state.doc.resolve(getPos());
const transaction = view.state.tr.setSelection(new NodeSelection($pos));
view.dispatch(transaction);
};
handleMouseDown = (ev: React.MouseEvent<HTMLParagraphElement>) => { handleMouseDown = (ev: React.MouseEvent<HTMLParagraphElement>) => {
if (document.activeElement !== ev.currentTarget) { if (document.activeElement !== ev.currentTarget) {
@@ -189,12 +181,8 @@ export default class SimpleImage extends Node {
} }
const { view } = this.editor; const { view } = this.editor;
const { node } = state.selection; const { node } = state.selection;
const { const { uploadFile, onFileUploadStart, onFileUploadStop, onShowToast } =
uploadFile, this.editor.props;
onFileUploadStart,
onFileUploadStop,
onShowToast,
} = this.editor.props;
if (!uploadFile) { if (!uploadFile) {
throw new Error("uploadFile prop is required to replace images"); throw new Error("uploadFile prop is required to replace images");
@@ -225,24 +213,23 @@ export default class SimpleImage extends Node {
inputElement.click(); inputElement.click();
return true; return true;
}, },
createImage: (attrs: Record<string, any>) => ( createImage:
state: EditorState, (attrs: Record<string, any>) =>
dispatch: Dispatch (state: EditorState, dispatch: Dispatch) => {
) => { const { selection } = state;
const { selection } = state; const position =
const position = selection instanceof TextSelection
selection instanceof TextSelection ? selection.$cursor?.pos
? selection.$cursor?.pos : selection.$to.pos;
: selection.$to.pos; if (position === undefined) {
if (position === undefined) { return false;
return false; }
}
const node = type.create(attrs); const node = type.create(attrs);
const transaction = state.tr.insert(position, node); const transaction = state.tr.insert(position, node);
dispatch(transaction); dispatch(transaction);
return true; return true;
}, },
}; };
} }
@@ -255,7 +242,8 @@ export default class SimpleImage extends Node {
* ![](image.jpg "class") -> [, "", "image.jpg", "small"] * ![](image.jpg "class") -> [, "", "image.jpg", "small"]
* ![Lorem](image.jpg "class") -> [, "Lorem", "image.jpg", "small"] * ![Lorem](image.jpg "class") -> [, "Lorem", "image.jpg", "small"]
*/ */
const IMAGE_INPUT_REGEX = /!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/; const IMAGE_INPUT_REGEX =
/!\[(?<alt>[^\][]*?)]\((?<filename>[^\][]*?)(?=“|\))“?(?<layoutclass>[^\][”]+)?”?\)$/;
return [ return [
new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => { new InputRule(IMAGE_INPUT_REGEX, (state, match, start, end) => {

View File

@@ -60,56 +60,47 @@ export default class Table extends Node {
commands({ schema }: { schema: Schema }) { commands({ schema }: { schema: Schema }) {
return { return {
createTable: ({ createTable:
rowsCount, ({ rowsCount, colsCount }: { rowsCount: number; colsCount: number }) =>
colsCount, (state: EditorState, dispatch: Dispatch) => {
}: { const offset = state.tr.selection.anchor + 1;
rowsCount: number; const nodes = createTable(schema, rowsCount, colsCount);
colsCount: number; const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
}) => (state: EditorState, dispatch: Dispatch) => { const resolvedPos = tr.doc.resolve(offset);
const offset = state.tr.selection.anchor + 1;
const nodes = createTable(schema, rowsCount, colsCount);
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView();
const resolvedPos = tr.doc.resolve(offset);
tr.setSelection(TextSelection.near(resolvedPos)); tr.setSelection(TextSelection.near(resolvedPos));
dispatch(tr); dispatch(tr);
return true; return true;
}, },
setColumnAttr: ({ setColumnAttr:
index, ({ index, alignment }: { index: number; alignment: string }) =>
alignment, (state: EditorState, dispatch: Dispatch) => {
}: { const cells = getCellsInColumn(index)(state.selection) || [];
index: number; let transaction = state.tr;
alignment: string; cells.forEach(({ pos }) => {
}) => (state: EditorState, dispatch: Dispatch) => { transaction = transaction.setNodeMarkup(pos, undefined, {
const cells = getCellsInColumn(index)(state.selection) || []; alignment,
let transaction = state.tr; });
cells.forEach(({ pos }) => {
transaction = transaction.setNodeMarkup(pos, undefined, {
alignment,
}); });
}); dispatch(transaction);
dispatch(transaction); return true;
return true; },
},
addColumnBefore: () => addColumnBefore, addColumnBefore: () => addColumnBefore,
addColumnAfter: () => addColumnAfter, addColumnAfter: () => addColumnAfter,
deleteColumn: () => deleteColumn, deleteColumn: () => deleteColumn,
addRowAfter: ({ index }: { index: number }) => ( addRowAfter:
state: EditorState, ({ index }: { index: number }) =>
dispatch: Dispatch (state: EditorState, dispatch: Dispatch) => {
) => { if (index === 0) {
if (index === 0) { // A little hack to avoid cloning the heading row by cloning the row
// A little hack to avoid cloning the heading row by cloning the row // beneath and then moving it to the right index.
// beneath and then moving it to the right index. const tr = addRowAt(index + 2, true)(state.tr);
const tr = addRowAt(index + 2, true)(state.tr); dispatch(moveRow(index + 2, index + 1)(tr));
dispatch(moveRow(index + 2, index + 1)(tr)); } else {
} else { dispatch(addRowAt(index + 1, true)(state.tr));
dispatch(addRowAt(index + 1, true)(state.tr)); }
} return true;
return true; },
},
deleteRow: () => deleteRow, deleteRow: () => deleteRow,
deleteTable: () => deleteTable, deleteTable: () => deleteTable,
toggleHeaderColumn: () => toggleHeaderColumn, toggleHeaderColumn: () => toggleHeaderColumn,
@@ -127,7 +118,7 @@ export default class Table extends Node {
return false; return false;
} }
const index = getRowIndexFromText( const index = getRowIndexFromText(
(state.selection as unknown) as CellSelection state.selection as unknown as CellSelection
); );
if (index === 0) { if (index === 0) {

View File

@@ -1,16 +1,18 @@
import { MarkType } from "prosemirror-model"; import { MarkType } from "prosemirror-model";
import { EditorState } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
const isMarkActive = (type: MarkType) => (state: EditorState): boolean => { const isMarkActive =
if (!type) { (type: MarkType) =>
return false; (state: EditorState): boolean => {
} if (!type) {
return false;
}
const { from, $from, to, empty } = state.selection; const { from, $from, to, empty } = state.selection;
return !!(empty return !!(empty
? type.isInSet(state.storedMarks || $from.marks()) ? type.isInSet(state.storedMarks || $from.marks())
: state.doc.rangeHasMark(from, to, type)); : state.doc.rangeHasMark(from, to, type));
}; };
export default isMarkActive; export default isMarkActive;

View File

@@ -2,22 +2,22 @@ import { NodeType } from "prosemirror-model";
import { EditorState } from "prosemirror-state"; import { EditorState } from "prosemirror-state";
import { findParentNode, findSelectedNodeOfType } from "prosemirror-utils"; import { findParentNode, findSelectedNodeOfType } from "prosemirror-utils";
const isNodeActive = (type: NodeType, attrs: Record<string, any> = {}) => ( const isNodeActive =
state: EditorState (type: NodeType, attrs: Record<string, any> = {}) =>
) => { (state: EditorState) => {
if (!type) { if (!type) {
return false; return false;
} }
const node = const node =
findSelectedNodeOfType(type)(state.selection) || findSelectedNodeOfType(type)(state.selection) ||
findParentNode((node) => node.type === type)(state.selection); findParentNode((node) => node.type === type)(state.selection);
if (!Object.keys(attrs).length || !node) { if (!Object.keys(attrs).length || !node) {
return !!node; return !!node;
} }
return node.node.hasMarkup(type, { ...node.node.attrs, ...attrs }); return node.node.hasMarkup(type, { ...node.node.attrs, ...attrs });
}; };
export default isNodeActive; export default isNodeActive;

View File

@@ -20,9 +20,9 @@ export const ellipsis = () => `
* *
* @returns a theme value * @returns a theme value
*/ */
export const s = (key: keyof DefaultTheme) => (props: { export const s =
theme: DefaultTheme; (key: keyof DefaultTheme) => (props: { theme: DefaultTheme }) =>
}) => String(props.theme[key]); String(props.theme[key]);
/** /**
* Mixin to hide scrollbars. * Mixin to hide scrollbars.

View File

@@ -10568,10 +10568,10 @@ prettier-linter-helpers@^1.0.0:
dependencies: dependencies:
fast-diff "^1.1.2" fast-diff "^1.1.2"
prettier@^2.0.5: prettier@^2.8.8:
version "2.1.2" version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
pretty-bytes@^5.3.0: pretty-bytes@^5.3.0:
version "5.6.0" version "5.6.0"