mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 036b584184 | |||
| 575f1da115 | |||
| eecc7e3443 | |||
| 5fbc57f39a | |||
| 69029b305d | |||
| 35269d7d92 | |||
| 13f45e1a1c | |||
| 5c46bd13ed | |||
| e51f93f9a0 | |||
| d4fe240808 | |||
| 61c76e62ef | |||
| 499392c114 | |||
| af0651f243 | |||
| 7a54d5bb84 | |||
| cb4a978a87 | |||
| 66a5055e73 | |||
| a9ddbde02c | |||
| 6b25000adb | |||
| 0fb8971628 | |||
| 9f277a8f7b | |||
| 97e91eb06b | |||
| a87bda82b1 | |||
| 36a92d5393 | |||
| 50f9f414bf | |||
| 0d6026c21f | |||
| eea0e28630 | |||
| 4ad58b4ccd | |||
| 17f4dc58f1 | |||
| fe839acaeb | |||
| 210aefaf8f | |||
| 90f7a4272e | |||
| 978773ee27 | |||
| 59abc3355c | |||
| 72aa4cde3f |
@@ -59,8 +59,30 @@ jobs:
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn tsc
|
||||
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
server: ${{ steps.filter.outputs.server }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
server:
|
||||
- 'server/**'
|
||||
- 'shared/**'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
app:
|
||||
- 'app/**'
|
||||
- 'shared/**'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
test:
|
||||
needs: build
|
||||
if: ${{ needs.changes.outputs.app == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -75,7 +97,8 @@ jobs:
|
||||
- run: yarn test:${{ matrix.test-group }}
|
||||
|
||||
test-server:
|
||||
needs: build
|
||||
needs: [build, changes]
|
||||
if: ${{ needs.changes.outputs.server == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
@@ -121,6 +144,7 @@ jobs:
|
||||
|
||||
bundle-size:
|
||||
needs: [build, types]
|
||||
if: ${{ needs.changes.outputs.app == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -133,4 +157,7 @@ jobs:
|
||||
run: echo "NODE_ENV=production" >> $GITHUB_ENV
|
||||
- run: yarn vite:build
|
||||
- name: Send bundle stats to RelativeCI
|
||||
run: npx relative-ci-agent
|
||||
uses: relative-ci/agent-action@v2
|
||||
with:
|
||||
key: ${{ secrets.RELATIVE_CI_KEY }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
import stores from "~/stores";
|
||||
import Collection from "~/models/Collection";
|
||||
import { CollectionEdit } from "~/components/Collection/CollectionEdit";
|
||||
import { CollectionNew } from "~/components/Collection/CollectionNew";
|
||||
@@ -62,7 +61,7 @@ export const createCollection = createAction({
|
||||
keywords: "create",
|
||||
visible: ({ stores }) =>
|
||||
stores.policies.abilities(stores.auth.team?.id || "").createCollection,
|
||||
perform: ({ t, event }) => {
|
||||
perform: ({ t, event, stores }) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
stores.dialogs.openModal({
|
||||
@@ -78,10 +77,10 @@ export const editCollection = createAction({
|
||||
analyticsName: "Edit collection",
|
||||
section: ActiveCollectionSection,
|
||||
icon: <EditIcon />,
|
||||
visible: ({ activeCollectionId }) =>
|
||||
visible: ({ activeCollectionId, stores }) =>
|
||||
!!activeCollectionId &&
|
||||
stores.policies.abilities(activeCollectionId).update,
|
||||
perform: ({ t, activeCollectionId }) => {
|
||||
perform: ({ t, activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return;
|
||||
}
|
||||
@@ -104,10 +103,10 @@ export const editCollectionPermissions = createAction({
|
||||
analyticsName: "Collection permissions",
|
||||
section: ActiveCollectionSection,
|
||||
icon: <PadlockIcon />,
|
||||
visible: ({ activeCollectionId }) =>
|
||||
visible: ({ activeCollectionId, stores }) =>
|
||||
!!activeCollectionId &&
|
||||
stores.policies.abilities(activeCollectionId).update,
|
||||
perform: ({ t, activeCollectionId }) => {
|
||||
perform: ({ t, activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +134,7 @@ export const searchInCollection = createAction({
|
||||
analyticsName: "Search collection",
|
||||
section: ActiveCollectionSection,
|
||||
icon: <SearchIcon />,
|
||||
visible: ({ activeCollectionId }) => {
|
||||
visible: ({ activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return false;
|
||||
}
|
||||
@@ -160,7 +159,7 @@ export const starCollection = createAction({
|
||||
section: ActiveCollectionSection,
|
||||
icon: <StarredIcon />,
|
||||
keywords: "favorite bookmark",
|
||||
visible: ({ activeCollectionId }) => {
|
||||
visible: ({ activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return false;
|
||||
}
|
||||
@@ -170,7 +169,7 @@ export const starCollection = createAction({
|
||||
stores.policies.abilities(activeCollectionId).star
|
||||
);
|
||||
},
|
||||
perform: async ({ activeCollectionId }) => {
|
||||
perform: async ({ activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +186,7 @@ export const unstarCollection = createAction({
|
||||
section: ActiveCollectionSection,
|
||||
icon: <UnstarredIcon />,
|
||||
keywords: "unfavorite unbookmark",
|
||||
visible: ({ activeCollectionId }) => {
|
||||
visible: ({ activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return false;
|
||||
}
|
||||
@@ -197,7 +196,7 @@ export const unstarCollection = createAction({
|
||||
stores.policies.abilities(activeCollectionId).unstar
|
||||
);
|
||||
},
|
||||
perform: async ({ activeCollectionId }) => {
|
||||
perform: async ({ activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return;
|
||||
}
|
||||
@@ -339,13 +338,13 @@ export const deleteCollection = createAction({
|
||||
section: ActiveCollectionSection,
|
||||
dangerous: true,
|
||||
icon: <TrashIcon />,
|
||||
visible: ({ activeCollectionId }) => {
|
||||
visible: ({ activeCollectionId, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return false;
|
||||
}
|
||||
return stores.policies.abilities(activeCollectionId).delete;
|
||||
},
|
||||
perform: ({ activeCollectionId, t }) => {
|
||||
perform: ({ activeCollectionId, t, stores }) => {
|
||||
if (!activeCollectionId) {
|
||||
return;
|
||||
}
|
||||
@@ -373,7 +372,7 @@ export const createTemplate = createAction({
|
||||
section: ActiveCollectionSection,
|
||||
icon: <ShapesIcon />,
|
||||
keywords: "new create template",
|
||||
visible: ({ activeCollectionId }) =>
|
||||
visible: ({ activeCollectionId, stores }) =>
|
||||
!!(
|
||||
!!activeCollectionId &&
|
||||
stores.policies.abilities(activeCollectionId).createDocument
|
||||
|
||||
@@ -13,6 +13,7 @@ import MenuIconWrapper from "./MenuIconWrapper";
|
||||
type Props = {
|
||||
id?: string;
|
||||
onClick?: (event: React.MouseEvent) => void | Promise<void>;
|
||||
onPointerMove?: (event: React.MouseEvent) => void | Promise<void>;
|
||||
active?: boolean;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
@@ -31,6 +32,7 @@ type Props = {
|
||||
const MenuItem = (
|
||||
{
|
||||
onClick,
|
||||
onPointerMove,
|
||||
children,
|
||||
active,
|
||||
selected,
|
||||
@@ -90,6 +92,7 @@ const MenuItem = (
|
||||
return (
|
||||
<BaseMenuItem
|
||||
onClick={disabled ? undefined : onClick}
|
||||
onPointerMove={disabled ? undefined : onPointerMove}
|
||||
disabled={disabled}
|
||||
hide={hide}
|
||||
{...rest}
|
||||
|
||||
@@ -15,7 +15,7 @@ import scrollIntoView from "scroll-into-view-if-needed";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Icon from "@shared/components/Icon";
|
||||
import { NavigationNode } from "@shared/types";
|
||||
import { NavigationNode, NavigationNodeType } from "@shared/types";
|
||||
import { isModKey } from "@shared/utils/keyboard";
|
||||
import DocumentExplorerNode from "~/components/DocumentExplorerNode";
|
||||
import DocumentExplorerSearchResult from "~/components/DocumentExplorerSearchResult";
|
||||
@@ -78,6 +78,10 @@ function DocumentExplorer({ onSubmit, onSelect, items, defaultValue }: Props) {
|
||||
const VERTICAL_PADDING = 6;
|
||||
const HORIZONTAL_PADDING = 24;
|
||||
|
||||
const recentlyViewedItemIds = documents.recentlyViewed
|
||||
.slice(0, 5)
|
||||
.map((item) => item.id);
|
||||
|
||||
const searchIndex = React.useMemo(
|
||||
() =>
|
||||
new FuzzySearch(items, ["title"], {
|
||||
@@ -126,11 +130,18 @@ function DocumentExplorer({ onSubmit, onSelect, items, defaultValue }: Props) {
|
||||
return searchTerm
|
||||
? searchIndex.search(searchTerm)
|
||||
: items
|
||||
.filter((item) => item.type === "collection")
|
||||
.filter((item) => recentlyViewedItemIds.includes(item.id))
|
||||
.concat(
|
||||
items.filter((item) => item.type === NavigationNodeType.Collection)
|
||||
)
|
||||
.flatMap(includeDescendants);
|
||||
}
|
||||
|
||||
const nodes = getNodes();
|
||||
const baseDepth = nodes.reduce(
|
||||
(min, node) => (node.depth ? Math.min(min, node.depth) : min),
|
||||
Infinity
|
||||
);
|
||||
|
||||
const scrollNodeIntoView = React.useCallback(
|
||||
(node: number) => {
|
||||
@@ -304,7 +315,7 @@ function DocumentExplorer({ onSubmit, onSelect, items, defaultValue }: Props) {
|
||||
expanded={isExpanded(index)}
|
||||
icon={renderedIcon}
|
||||
title={title}
|
||||
depth={node.depth as number}
|
||||
depth={(node.depth ?? 0) - baseDepth}
|
||||
hasChildren={hasChildren(index)}
|
||||
ref={itemRefs[index]}
|
||||
/>
|
||||
|
||||
@@ -41,9 +41,9 @@ function DocumentExplorerNode(
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const OFFSET = 12;
|
||||
const ICON_SIZE = 24;
|
||||
const DISCLOSURE = 20;
|
||||
|
||||
const width = depth ? depth * ICON_SIZE + OFFSET : ICON_SIZE;
|
||||
const width = depth ? depth * DISCLOSURE + OFFSET : DISCLOSURE;
|
||||
|
||||
return (
|
||||
<Node
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
import { fadeIn } from "~/styles/animations";
|
||||
|
||||
const Fade = styled.span<{ timing?: number | string }>`
|
||||
animation: ${fadeIn} ${(props) => props.timing || "250ms"} ease-in-out;
|
||||
`;
|
||||
|
||||
export default Fade;
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { fadeIn } from "~/styles/animations";
|
||||
|
||||
const Fade = styled.span<{ timing?: number | string }>`
|
||||
animation: ${fadeIn} ${(props) => props.timing || "250ms"} ease-in-out;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
children?: JSX.Element | null;
|
||||
/** If true, children will be animated. */
|
||||
animate: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps children in a <Fade> if loading is true on mount.
|
||||
*/
|
||||
export const ConditionalFade = ({ animate, children }: Props) => {
|
||||
const [isAnimated] = React.useState(animate);
|
||||
|
||||
return isAnimated ? <Fade>{children}</Fade> : <>{children}</>;
|
||||
};
|
||||
|
||||
export default Fade;
|
||||
@@ -176,6 +176,7 @@ function Input(
|
||||
if (ev.key === "Enter" && ev.metaKey) {
|
||||
if (props.onRequestSubmit) {
|
||||
props.onRequestSubmit(ev);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,10 +231,11 @@ function Input(
|
||||
])}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={handleKeyDown}
|
||||
hasIcon={!!icon}
|
||||
hasPrefix={!!prefix}
|
||||
{...rest}
|
||||
// set it after "rest" to override "onKeyDown" from prop.
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
) : (
|
||||
<NativeInput
|
||||
@@ -243,11 +245,12 @@ function Input(
|
||||
])}
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={handleKeyDown}
|
||||
hasIcon={!!icon}
|
||||
hasPrefix={!!prefix}
|
||||
type={type}
|
||||
{...rest}
|
||||
// set it after "rest" to override "onKeyDown" from prop.
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
|
||||
@@ -48,6 +48,15 @@ function Notifications(
|
||||
notifications.approximateUnreadCount
|
||||
);
|
||||
}
|
||||
|
||||
// PWA badging
|
||||
if ("setAppBadge" in navigator) {
|
||||
if (notifications.approximateUnreadCount) {
|
||||
void navigator.setAppBadge(notifications.approximateUnreadCount);
|
||||
} else {
|
||||
void navigator.clearAppBadge();
|
||||
}
|
||||
}
|
||||
}, [notifications.approximateUnreadCount]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import invariant from "invariant";
|
||||
import find from "lodash/find";
|
||||
import isObject from "lodash/isObject";
|
||||
import { action, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { withTranslation, WithTranslation } from "react-i18next";
|
||||
import semver from "semver";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
import { toast } from "sonner";
|
||||
import EDITOR_VERSION from "@shared/editor/version";
|
||||
import { FileOperationState, FileOperationType } from "@shared/types";
|
||||
import RootStore from "~/stores/RootStore";
|
||||
import Collection from "~/models/Collection";
|
||||
@@ -114,10 +117,23 @@ class WebsocketProvider extends React.Component<Props> {
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on("authenticated", () => {
|
||||
this.socket.on("authenticated", (data) => {
|
||||
if (this.socket) {
|
||||
this.socket.authenticated = true;
|
||||
}
|
||||
if (isObject(data) && "editorVersion" in data) {
|
||||
const parsedClientVersion = semver.parse(EDITOR_VERSION);
|
||||
const parsedCurrentVersion = semver.parse(String(data.editorVersion));
|
||||
|
||||
if (
|
||||
parsedClientVersion &&
|
||||
parsedCurrentVersion &&
|
||||
(parsedClientVersion.major < parsedCurrentVersion.major ||
|
||||
parsedClientVersion.minor < parsedCurrentVersion.minor)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on("unauthorized", (err: Error) => {
|
||||
|
||||
@@ -24,6 +24,54 @@ import useOnClickOutside from "~/hooks/useOnClickOutside";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import { useEditor } from "./EditorContext";
|
||||
|
||||
type KeyboardShortcutsProps = {
|
||||
popover: ReturnType<typeof usePopoverState>;
|
||||
handleOpen: ({ withReplace }: { withReplace: boolean }) => void;
|
||||
handleCaseSensitive: () => void;
|
||||
handleRegex: () => void;
|
||||
};
|
||||
|
||||
function useKeyboardShortcuts({
|
||||
popover,
|
||||
handleOpen,
|
||||
handleCaseSensitive,
|
||||
handleRegex,
|
||||
}: KeyboardShortcutsProps) {
|
||||
// Open popover
|
||||
useKeyDown(
|
||||
(ev) =>
|
||||
isModKey(ev) &&
|
||||
ev.code === "KeyF" &&
|
||||
// Keyboard handler is through the AppMenu on Desktop v1.2.0+
|
||||
!(Desktop.bridge && "onFindInPage" in Desktop.bridge),
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
handleOpen({ withReplace: ev.altKey });
|
||||
},
|
||||
{ allowInInput: true }
|
||||
);
|
||||
|
||||
// Enable/disable case sensitive search
|
||||
useKeyDown(
|
||||
(ev) => isModKey(ev) && ev.altKey && ev.code === "KeyC" && popover.visible,
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
handleCaseSensitive();
|
||||
},
|
||||
{ allowInInput: true }
|
||||
);
|
||||
|
||||
// Enable/disable regex search
|
||||
useKeyDown(
|
||||
(ev) => isModKey(ev) && ev.altKey && ev.code === "KeyR" && popover.visible,
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
handleRegex();
|
||||
},
|
||||
{ allowInInput: true }
|
||||
);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
/** Whether the find and replace popover is open */
|
||||
open: boolean;
|
||||
@@ -89,42 +137,48 @@ export default function FindAndReplace({
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
useOnClickOutside(popover.unstable_referenceRef, popover.hide);
|
||||
// Callbacks
|
||||
const selectInputText = React.useCallback(() => {
|
||||
inputRef.current?.focus();
|
||||
inputRef.current?.setSelectionRange(0, inputRef.current?.value.length);
|
||||
}, []);
|
||||
|
||||
const selectInputReplaceText = React.useCallback(() => {
|
||||
setTimeout(() => {
|
||||
inputReplaceRef.current?.focus();
|
||||
inputReplaceRef.current?.setSelectionRange(
|
||||
0,
|
||||
inputReplaceRef.current?.value.length
|
||||
);
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
const handleOpen = React.useCallback(
|
||||
({ withReplace }: { withReplace: boolean }) => {
|
||||
const shouldShowReplace = !readOnly && withReplace;
|
||||
|
||||
// If already open, switch focus to corresponding input text.
|
||||
if (popover.visible) {
|
||||
if (shouldShowReplace) {
|
||||
setShowReplace(true);
|
||||
selectInputReplaceText();
|
||||
} else {
|
||||
selectInputText();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
useKeyDown(
|
||||
(ev) =>
|
||||
isModKey(ev) &&
|
||||
!popover.visible &&
|
||||
ev.code === "KeyF" &&
|
||||
// Keyboard handler is through the AppMenu on Desktop v1.2.0+
|
||||
!(Desktop.bridge && "onFindInPage" in Desktop.bridge),
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
selectionRef.current = window.getSelection()?.toString();
|
||||
popover.show();
|
||||
}
|
||||
);
|
||||
|
||||
useKeyDown(
|
||||
(ev) => isModKey(ev) && ev.altKey && ev.code === "KeyR" && popover.visible,
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
setRegex((state) => !state);
|
||||
if (shouldShowReplace) {
|
||||
setShowReplace(true);
|
||||
}
|
||||
},
|
||||
{ allowInInput: true }
|
||||
[popover, readOnly, selectInputText, selectInputReplaceText]
|
||||
);
|
||||
|
||||
useKeyDown(
|
||||
(ev) => isModKey(ev) && ev.altKey && ev.code === "KeyC" && popover.visible,
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
setCaseSensitive((state) => !state);
|
||||
},
|
||||
{ allowInInput: true }
|
||||
);
|
||||
|
||||
// Callbacks
|
||||
const handleMore = React.useCallback(() => {
|
||||
setShowReplace((state) => !state);
|
||||
setTimeout(() => inputReplaceRef.current?.focus(), 100);
|
||||
@@ -132,68 +186,65 @@ export default function FindAndReplace({
|
||||
|
||||
const handleCaseSensitive = React.useCallback(() => {
|
||||
setCaseSensitive((state) => {
|
||||
const caseSensitive = !state;
|
||||
const isCaseSensitive = !state;
|
||||
|
||||
editor.commands.find({
|
||||
text: searchTerm,
|
||||
caseSensitive,
|
||||
caseSensitive: isCaseSensitive,
|
||||
regexEnabled,
|
||||
});
|
||||
|
||||
return caseSensitive;
|
||||
return isCaseSensitive;
|
||||
});
|
||||
}, [regexEnabled, editor.commands, searchTerm]);
|
||||
|
||||
const handleRegex = React.useCallback(() => {
|
||||
setRegex((state) => {
|
||||
const regexEnabled = !state;
|
||||
const isRegexEnabled = !state;
|
||||
|
||||
editor.commands.find({
|
||||
text: searchTerm,
|
||||
caseSensitive,
|
||||
regexEnabled,
|
||||
regexEnabled: isRegexEnabled,
|
||||
});
|
||||
|
||||
return regexEnabled;
|
||||
return isRegexEnabled;
|
||||
});
|
||||
}, [caseSensitive, editor.commands, searchTerm]);
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
(ev: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
function nextPrevious(ev: React.KeyboardEvent<HTMLInputElement>) {
|
||||
function nextPrevious() {
|
||||
if (ev.shiftKey) {
|
||||
editor.commands.prevSearchMatch();
|
||||
} else {
|
||||
editor.commands.nextSearchMatch();
|
||||
}
|
||||
}
|
||||
function selectInputText() {
|
||||
inputRef.current?.setSelectionRange(0, inputRef.current?.value.length);
|
||||
}
|
||||
|
||||
switch (ev.key) {
|
||||
case "Enter": {
|
||||
ev.preventDefault();
|
||||
nextPrevious(ev);
|
||||
nextPrevious();
|
||||
return;
|
||||
}
|
||||
case "g": {
|
||||
if (ev.metaKey) {
|
||||
ev.preventDefault();
|
||||
nextPrevious(ev);
|
||||
nextPrevious();
|
||||
selectInputText();
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "F3": {
|
||||
ev.preventDefault();
|
||||
nextPrevious(ev);
|
||||
nextPrevious();
|
||||
selectInputText();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
[editor.commands]
|
||||
[editor.commands, selectInputText]
|
||||
);
|
||||
|
||||
const handleReplace = React.useCallback(
|
||||
@@ -243,6 +294,15 @@ export default function FindAndReplace({
|
||||
[handleReplace]
|
||||
);
|
||||
|
||||
useOnClickOutside(popover.unstable_referenceRef, popover.hide);
|
||||
|
||||
useKeyboardShortcuts({
|
||||
popover,
|
||||
handleOpen,
|
||||
handleCaseSensitive,
|
||||
handleRegex,
|
||||
});
|
||||
|
||||
const style: React.CSSProperties = React.useMemo(
|
||||
() => ({
|
||||
position: "fixed",
|
||||
@@ -285,7 +345,7 @@ export default function FindAndReplace({
|
||||
<>
|
||||
<Tooltip
|
||||
content={t("Previous match")}
|
||||
shortcut="shift+enter"
|
||||
shortcut="Shift+Enter"
|
||||
placement="bottom"
|
||||
>
|
||||
<ButtonLarge
|
||||
@@ -295,7 +355,7 @@ export default function FindAndReplace({
|
||||
<CaretUpIcon />
|
||||
</ButtonLarge>
|
||||
</Tooltip>
|
||||
<Tooltip content={t("Next match")} shortcut="enter" placement="bottom">
|
||||
<Tooltip content={t("Next match")} shortcut="Enter" placement="bottom">
|
||||
<ButtonLarge
|
||||
disabled={disabled}
|
||||
onClick={() => editor.commands.nextSearchMatch()}
|
||||
@@ -354,7 +414,11 @@ export default function FindAndReplace({
|
||||
</StyledInput>
|
||||
{navigation}
|
||||
{!readOnly && (
|
||||
<Tooltip content={t("Replace options")} placement="bottom">
|
||||
<Tooltip
|
||||
content={t("Replace options")}
|
||||
shortcut={`${altDisplay}+${metaDisplay}+f`}
|
||||
placement="bottom"
|
||||
>
|
||||
<ButtonLarge onClick={handleMore}>
|
||||
<ReplaceIcon color={theme.textSecondary} />
|
||||
</ButtonLarge>
|
||||
@@ -376,12 +440,28 @@ export default function FindAndReplace({
|
||||
onRequestSubmit={handleReplaceAll}
|
||||
onChange={(ev) => setReplaceTerm(ev.currentTarget.value)}
|
||||
/>
|
||||
<Button onClick={handleReplace} disabled={disabled} neutral>
|
||||
{t("Replace")}
|
||||
</Button>
|
||||
<Button onClick={handleReplaceAll} disabled={disabled} neutral>
|
||||
{t("Replace all")}
|
||||
</Button>
|
||||
<Tooltip
|
||||
content={t("Replace")}
|
||||
shortcut="Enter"
|
||||
placement="bottom"
|
||||
>
|
||||
<Button onClick={handleReplace} disabled={disabled} neutral>
|
||||
{t("Replace")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={t("Replace all")}
|
||||
shortcut={`${metaDisplay}+Enter`}
|
||||
placement="bottom"
|
||||
>
|
||||
<Button
|
||||
onClick={handleReplaceAll}
|
||||
disabled={disabled}
|
||||
neutral
|
||||
>
|
||||
{t("Replace all")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
)}
|
||||
</ResizingHeightContainer>
|
||||
|
||||
@@ -6,7 +6,6 @@ import styled, { css } from "styled-components";
|
||||
import { isCode } from "@shared/editor/lib/isCode";
|
||||
import { findParentNode } from "@shared/editor/queries/findParentNode";
|
||||
import { EditorStyleHelper } from "@shared/editor/styles/EditorStyleHelper";
|
||||
import { useComponentSize } from "@shared/hooks/useComponentSize";
|
||||
import { depths, s } from "@shared/styles";
|
||||
import { HEADER_HEIGHT } from "~/components/Header";
|
||||
import { Portal } from "~/components/Portal";
|
||||
@@ -41,7 +40,8 @@ function usePosition({
|
||||
}) {
|
||||
const { view } = useEditor();
|
||||
const { selection } = view.state;
|
||||
const { width: menuWidth, height: menuHeight } = useComponentSize(menuRef);
|
||||
const menuWidth = menuRef.current?.offsetWidth;
|
||||
const menuHeight = menuRef.current?.offsetHeight;
|
||||
|
||||
if (!active || !menuWidth || !menuHeight || !menuRef.current) {
|
||||
return defaultPosition;
|
||||
@@ -78,13 +78,24 @@ function usePosition({
|
||||
|
||||
// position at the top right of code blocks
|
||||
const codeBlock = findParentNode(isCode)(view.state.selection);
|
||||
const noticeBlock = findParentNode(
|
||||
(node) => node.type.name === "container_notice"
|
||||
)(view.state.selection);
|
||||
|
||||
if (codeBlock && view.state.selection.empty) {
|
||||
const element = view.nodeDOM(codeBlock.pos);
|
||||
const bounds = (element as HTMLElement).getBoundingClientRect();
|
||||
selectionBounds.top = bounds.top;
|
||||
selectionBounds.left = bounds.right - menuWidth;
|
||||
selectionBounds.right = bounds.right;
|
||||
if ((codeBlock || noticeBlock) && view.state.selection.empty) {
|
||||
const position = codeBlock
|
||||
? codeBlock.pos
|
||||
: noticeBlock
|
||||
? noticeBlock.pos
|
||||
: null;
|
||||
|
||||
if (position !== null) {
|
||||
const element = view.nodeDOM(position);
|
||||
const bounds = (element as HTMLElement).getBoundingClientRect();
|
||||
selectionBounds.top = bounds.top;
|
||||
selectionBounds.left = bounds.right - menuWidth;
|
||||
selectionBounds.right = bounds.right;
|
||||
}
|
||||
}
|
||||
|
||||
// tables are an oddity, and need their own positioning logic
|
||||
@@ -188,7 +199,8 @@ function usePosition({
|
||||
top: Math.round(top - offsetParent.top),
|
||||
offset: Math.round(offset),
|
||||
maxWidth: Math.min(window.innerWidth, offsetParent.width) - margin * 2,
|
||||
blockSelection: codeBlock || isColSelection || isRowSelection,
|
||||
blockSelection:
|
||||
codeBlock || isColSelection || isRowSelection || noticeBlock,
|
||||
visible: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { transparentize } from "polished";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
|
||||
@@ -13,6 +14,10 @@ const Input = styled.input`
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
|
||||
&::placeholder {
|
||||
color: ${(props) => transparentize(0.5, props.theme.text)};
|
||||
}
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { ArrowIcon, CloseIcon, OpenIcon } from "outline-icons";
|
||||
import { observer } from "mobx-react";
|
||||
import { ArrowIcon, CloseIcon, DocumentIcon, OpenIcon } from "outline-icons";
|
||||
import { Mark } from "prosemirror-model";
|
||||
import { Selection } from "prosemirror-state";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import * as React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import Icon from "@shared/components/Icon";
|
||||
import { hideScrollbars, s } from "@shared/styles";
|
||||
import { isInternalUrl, sanitizeUrl } from "@shared/utils/urls";
|
||||
import Flex from "~/components/Flex";
|
||||
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
import Logger from "~/utils/Logger";
|
||||
import useRequest from "~/hooks/useRequest";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import Input from "./Input";
|
||||
import SuggestionsMenuItem from "./SuggestionsMenuItem";
|
||||
import ToolbarButton from "./ToolbarButton";
|
||||
import Tooltip from "./Tooltip";
|
||||
|
||||
@@ -32,152 +41,87 @@ type Props = {
|
||||
view: EditorView;
|
||||
};
|
||||
|
||||
type State = {
|
||||
value: string;
|
||||
previousValue: string;
|
||||
};
|
||||
const LinkEditor: React.FC<Props> = ({
|
||||
mark,
|
||||
from,
|
||||
to,
|
||||
dictionary,
|
||||
onRemoveLink,
|
||||
onSelectLink,
|
||||
onClickLink,
|
||||
view,
|
||||
}) => {
|
||||
const getHref = () => sanitizeUrl(mark?.attrs.href) ?? "";
|
||||
const initialValue = getHref();
|
||||
const initialSelectionLength = to - from;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const discardRef = useRef(false);
|
||||
const [query, setQuery] = useState(initialValue);
|
||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||
const { documents } = useStores();
|
||||
|
||||
class LinkEditor extends React.Component<Props, State> {
|
||||
discardInputValue = false;
|
||||
initialValue = this.href;
|
||||
initialSelectionLength = this.props.to - this.props.from;
|
||||
inputRef = React.createRef<HTMLInputElement>();
|
||||
const trimmedQuery = query.trim();
|
||||
const results = trimmedQuery
|
||||
? documents.findByQuery(trimmedQuery, { maxResults: 25 })
|
||||
: [];
|
||||
|
||||
state: State = {
|
||||
value: this.href,
|
||||
previousValue: "",
|
||||
};
|
||||
const { request } = useRequest(
|
||||
React.useCallback(async () => {
|
||||
const res = await client.post("/suggestions.mention", { query });
|
||||
res.data.documents.map(documents.add);
|
||||
}, [query])
|
||||
);
|
||||
|
||||
get href(): string {
|
||||
return sanitizeUrl(this.props.mark?.attrs.href) ?? "";
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
window.addEventListener("keydown", this.handleGlobalKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount = () => {
|
||||
window.removeEventListener("keydown", this.handleGlobalKeyDown);
|
||||
|
||||
// If we discarded the changes then nothing to do
|
||||
if (this.discardInputValue) {
|
||||
return;
|
||||
useEffect(() => {
|
||||
if (trimmedQuery) {
|
||||
void request();
|
||||
}
|
||||
}, [trimmedQuery, request]);
|
||||
|
||||
// If the link is the same as it was when the editor opened, nothing to do
|
||||
if (this.state.value === this.initialValue) {
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "k" && event.metaKey) {
|
||||
inputRef.current?.select();
|
||||
}
|
||||
};
|
||||
|
||||
// If the link is totally empty or only spaces then remove the mark
|
||||
const href = (this.state.value || "").trim();
|
||||
if (!href) {
|
||||
return this.handleRemoveLink();
|
||||
}
|
||||
window.addEventListener("keydown", handleGlobalKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleGlobalKeyDown);
|
||||
|
||||
this.save(href, href);
|
||||
};
|
||||
// If we discarded the changes then nothing to do
|
||||
if (discardRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleGlobalKeyDown = (event: KeyboardEvent): void => {
|
||||
if (event.key === "k" && event.metaKey) {
|
||||
this.inputRef.current?.select();
|
||||
}
|
||||
};
|
||||
// If the link is the same as it was when the editor opened, nothing to do
|
||||
if (trimmedQuery === initialValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
save = (href: string, title?: string): void => {
|
||||
// If the link is totally empty or only spaces then remove the mark
|
||||
if (!trimmedQuery) {
|
||||
return handleRemoveLink();
|
||||
}
|
||||
|
||||
save(trimmedQuery, trimmedQuery);
|
||||
};
|
||||
}, [trimmedQuery, initialValue]);
|
||||
|
||||
const save = (href: string, title?: string) => {
|
||||
href = href.trim();
|
||||
|
||||
if (href.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.discardInputValue = true;
|
||||
const { from, to } = this.props;
|
||||
discardRef.current = true;
|
||||
href = sanitizeUrl(href) ?? "";
|
||||
|
||||
this.props.onSelectLink({ href, title, from, to });
|
||||
onSelectLink({ href, title, from, to });
|
||||
};
|
||||
|
||||
handleKeyDown = (event: React.KeyboardEvent): void => {
|
||||
switch (event.key) {
|
||||
case "Enter": {
|
||||
event.preventDefault();
|
||||
const { value } = this.state;
|
||||
|
||||
this.save(value, value);
|
||||
|
||||
if (this.initialSelectionLength) {
|
||||
this.moveSelectionToEnd();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
case "Escape": {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.initialValue) {
|
||||
this.setState({ value: this.initialValue }, this.moveSelectionToEnd);
|
||||
} else {
|
||||
this.handleRemoveLink();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleSearch = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
): Promise<void> => {
|
||||
const value = event.target.value;
|
||||
|
||||
this.setState({
|
||||
value,
|
||||
});
|
||||
|
||||
const trimmedValue = value.trim();
|
||||
|
||||
if (trimmedValue) {
|
||||
try {
|
||||
this.setState({
|
||||
previousValue: trimmedValue,
|
||||
});
|
||||
} catch (err) {
|
||||
Logger.error("Error searching for link", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handlePaste = (): void => {
|
||||
setTimeout(() => this.save(this.state.value, this.state.value), 0);
|
||||
};
|
||||
|
||||
handleOpenLink = (event: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
this.props.onClickLink(this.href, event);
|
||||
} catch (err) {
|
||||
toast.error(this.props.dictionary.openLinkError);
|
||||
}
|
||||
};
|
||||
|
||||
handleRemoveLink = (): void => {
|
||||
this.discardInputValue = true;
|
||||
|
||||
const { from, to, mark, view, onRemoveLink } = this.props;
|
||||
const { state, dispatch } = this.props.view;
|
||||
|
||||
if (mark) {
|
||||
dispatch(state.tr.removeMark(from, to, mark));
|
||||
}
|
||||
|
||||
onRemoveLink?.();
|
||||
view.focus();
|
||||
};
|
||||
|
||||
moveSelectionToEnd = () => {
|
||||
const { to, view } = this.props;
|
||||
const moveSelectionToEnd = () => {
|
||||
const { state, dispatch } = view;
|
||||
const nextSelection = Selection.findFrom(state.tr.doc.resolve(to), 1, true);
|
||||
if (nextSelection) {
|
||||
@@ -186,47 +130,178 @@ class LinkEditor extends React.Component<Props, State> {
|
||||
view.focus();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { view, dictionary } = this.props;
|
||||
const { value } = this.state;
|
||||
const isInternal = isInternalUrl(value);
|
||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||
switch (event.key) {
|
||||
case "ArrowDown": {
|
||||
event.preventDefault();
|
||||
const maxIndex = results.length - 1;
|
||||
setSelectedIndex((current) => (current >= maxIndex ? 0 : current + 1));
|
||||
return;
|
||||
}
|
||||
case "ArrowUp": {
|
||||
event.preventDefault();
|
||||
const maxIndex = results.length - 1;
|
||||
setSelectedIndex((current) => (current <= 0 ? maxIndex : current - 1));
|
||||
return;
|
||||
}
|
||||
case "Enter": {
|
||||
event.preventDefault();
|
||||
|
||||
return (
|
||||
if (selectedIndex >= 0 && results[selectedIndex]) {
|
||||
const selectedDoc = results[selectedIndex];
|
||||
const href = selectedDoc.url;
|
||||
save(href, selectedDoc.title);
|
||||
} else {
|
||||
save(trimmedQuery, trimmedQuery);
|
||||
}
|
||||
|
||||
if (initialSelectionLength) {
|
||||
moveSelectionToEnd();
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "Escape": {
|
||||
event.preventDefault();
|
||||
|
||||
if (initialValue) {
|
||||
setQuery(initialValue);
|
||||
moveSelectionToEnd();
|
||||
} else {
|
||||
handleRemoveLink();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = event.target.value;
|
||||
setQuery(newValue);
|
||||
setSelectedIndex(-1);
|
||||
};
|
||||
|
||||
const handlePaste = () => {
|
||||
setTimeout(() => save(query, query), 0);
|
||||
};
|
||||
|
||||
const handleOpenLink = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
onClickLink(getHref(), event);
|
||||
} catch (err) {
|
||||
toast.error(dictionary.openLinkError);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveLink = () => {
|
||||
discardRef.current = true;
|
||||
|
||||
const { state, dispatch } = view;
|
||||
if (mark) {
|
||||
dispatch(state.tr.removeMark(from, to, mark));
|
||||
}
|
||||
|
||||
onRemoveLink?.();
|
||||
view.focus();
|
||||
};
|
||||
|
||||
const isInternal = isInternalUrl(query);
|
||||
const hasResults = !!results.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
<Input
|
||||
ref={this.inputRef}
|
||||
value={value}
|
||||
placeholder={dictionary.enterLink}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onPaste={this.handlePaste}
|
||||
onChange={this.handleSearch}
|
||||
onFocus={this.handleSearch}
|
||||
autoFocus={this.href === ""}
|
||||
ref={inputRef}
|
||||
value={query}
|
||||
placeholder={dictionary.searchOrPasteLink}
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handlePaste}
|
||||
onChange={handleSearch}
|
||||
onFocus={handleSearch}
|
||||
autoFocus={getHref() === ""}
|
||||
readOnly={!view.editable}
|
||||
/>
|
||||
|
||||
<Tooltip
|
||||
content={isInternal ? dictionary.goToLink : dictionary.openLink}
|
||||
>
|
||||
<ToolbarButton onClick={this.handleOpenLink} disabled={!value}>
|
||||
<ToolbarButton onClick={handleOpenLink} disabled={!query}>
|
||||
{isInternal ? <ArrowIcon /> : <OpenIcon />}
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
{view.editable && (
|
||||
<Tooltip content={dictionary.removeLink}>
|
||||
<ToolbarButton onClick={this.handleRemoveLink}>
|
||||
<ToolbarButton onClick={handleRemoveLink}>
|
||||
<CloseIcon />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
<SearchResults $hasResults={hasResults}>
|
||||
<ResizingHeightContainer>
|
||||
{hasResults && (
|
||||
<>
|
||||
{results.map((doc, index) => (
|
||||
<SuggestionsMenuItem
|
||||
onClick={() => {
|
||||
save(doc.url, doc.title);
|
||||
if (initialSelectionLength) {
|
||||
moveSelectionToEnd();
|
||||
}
|
||||
}}
|
||||
onPointerMove={() => setSelectedIndex(index)}
|
||||
selected={index === selectedIndex}
|
||||
key={doc.id}
|
||||
subtitle={doc.collection?.name}
|
||||
title={doc.title}
|
||||
icon={
|
||||
doc.icon ? (
|
||||
<Icon value={doc.icon} color={doc.color ?? undefined} />
|
||||
) : (
|
||||
<DocumentIcon />
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</ResizingHeightContainer>
|
||||
</SearchResults>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
pointer-events: all;
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
export default LinkEditor;
|
||||
const SearchResults = styled(Scrollable)<{ $hasResults: boolean }>`
|
||||
background: ${s("menuBackground")};
|
||||
box-shadow: ${(props) => (props.$hasResults ? s("menuShadow") : "none")};
|
||||
clip-path: inset(0px -100px -100px -100px);
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
left: 0;
|
||||
margin-top: -6px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
padding: ${(props) => (props.$hasResults ? "6px" : "0")};
|
||||
max-height: 240px;
|
||||
pointer-events: all;
|
||||
|
||||
${hideScrollbars()}
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
position: fixed;
|
||||
top: auto;
|
||||
bottom: 40px;
|
||||
border-radius: 0;
|
||||
max-height: 50vh;
|
||||
padding: 8px 8px 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default observer(LinkEditor);
|
||||
|
||||
@@ -10,8 +10,6 @@ import Icon from "@shared/components/Icon";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { MentionType } from "@shared/types";
|
||||
import parseDocumentSlug from "@shared/utils/parseDocumentSlug";
|
||||
import Document from "~/models/Document";
|
||||
import User from "~/models/User";
|
||||
import { Avatar, AvatarSize } from "~/components/Avatar";
|
||||
import Flex from "~/components/Flex";
|
||||
import { DocumentsSection, UserSection } from "~/actions/sections";
|
||||
@@ -48,17 +46,11 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
|
||||
const documentId = parseDocumentSlug(location.pathname);
|
||||
const maxResultsInSection = search ? 25 : 5;
|
||||
|
||||
const { loading, request } = useRequest<{
|
||||
documents: Document[];
|
||||
users: User[];
|
||||
}>(
|
||||
const { loading, request } = useRequest(
|
||||
React.useCallback(async () => {
|
||||
const res = await client.post("/suggestions.mention", { query: search });
|
||||
|
||||
return {
|
||||
documents: res.data.documents.map(documents.add),
|
||||
users: res.data.users.map(users.add),
|
||||
};
|
||||
res.data.documents.map(documents.add);
|
||||
res.data.users.map(users.add);
|
||||
}, [search, documents, users])
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from "react";
|
||||
import filterExcessSeparators from "@shared/editor/lib/filterExcessSeparators";
|
||||
import { getMarkRange } from "@shared/editor/queries/getMarkRange";
|
||||
import { isInCode } from "@shared/editor/queries/isInCode";
|
||||
import { isInNotice } from "@shared/editor/queries/isInNotice";
|
||||
import { isMarkActive } from "@shared/editor/queries/isMarkActive";
|
||||
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
|
||||
import { getColumnIndex, getRowIndex } from "@shared/editor/queries/table";
|
||||
@@ -18,6 +19,7 @@ import getCodeMenuItems from "../menus/code";
|
||||
import getDividerMenuItems from "../menus/divider";
|
||||
import getFormattingMenuItems from "../menus/formatting";
|
||||
import getImageMenuItems from "../menus/image";
|
||||
import getNoticeMenuItems from "../menus/notice";
|
||||
import getReadOnlyMenuItems from "../menus/readOnly";
|
||||
import getTableMenuItems from "../menus/table";
|
||||
import getTableColMenuItems from "../menus/tableCol";
|
||||
@@ -55,6 +57,10 @@ function useIsActive(state: EditorState) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isInNotice(state) && selection.from > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!selection || selection.empty) {
|
||||
return false;
|
||||
}
|
||||
@@ -184,6 +190,7 @@ export default function SelectionToolbar(props: Props) {
|
||||
selection instanceof NodeSelection &&
|
||||
selection.node.type.name === "attachment";
|
||||
const isCodeSelection = isInCode(state, { onlyBlock: true });
|
||||
const isNoticeSelection = isInNotice(state);
|
||||
|
||||
let items: MenuItem[] = [];
|
||||
|
||||
@@ -203,6 +210,8 @@ export default function SelectionToolbar(props: Props) {
|
||||
items = getDividerMenuItems(state, dictionary);
|
||||
} else if (readOnly) {
|
||||
items = getReadOnlyMenuItems(state, !!canUpdate, dictionary);
|
||||
} else if (isNoticeSelection && selection.empty) {
|
||||
items = getNoticeMenuItems(state, readOnly, dictionary);
|
||||
} else {
|
||||
items = getFormattingMenuItems(state, isTemplate, isMobile, dictionary);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ export type Props = {
|
||||
disabled?: boolean;
|
||||
/** Callback when the item is clicked */
|
||||
onClick: (event: React.SyntheticEvent) => void;
|
||||
/** Callback when the item is hovered */
|
||||
onPointerMove?: (event: React.SyntheticEvent) => void;
|
||||
/** An optional icon for the item */
|
||||
icon?: React.ReactNode;
|
||||
/** The title of the item */
|
||||
@@ -25,6 +27,7 @@ function SuggestionsMenuItem({
|
||||
selected,
|
||||
disabled,
|
||||
onClick,
|
||||
onPointerMove,
|
||||
title,
|
||||
subtitle,
|
||||
shortcut,
|
||||
@@ -53,6 +56,7 @@ function SuggestionsMenuItem({
|
||||
ref={ref}
|
||||
active={selected}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
onPointerMove={disabled ? undefined : onPointerMove}
|
||||
icon={icon}
|
||||
>
|
||||
{title}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { InputRule } from "@shared/editor/lib/InputRule";
|
||||
|
||||
const rightArrow = new InputRule(/->$/, "→");
|
||||
const emdash = new InputRule(/--$/, "—");
|
||||
const oneHalf = new InputRule(/(?:^|\s)1\/2$/, "½");
|
||||
const threeQuarters = new InputRule(/(?:^|\s)3\/4$/, "¾");
|
||||
const oneHalf = new InputRule(/(?:^|\s)(1\/2)$/, "½");
|
||||
const threeQuarters = new InputRule(/(?:^|\s)(3\/4)$/, "¾");
|
||||
const copyright = new InputRule(/\(c\)$/, "©️");
|
||||
const registered = new InputRule(/\(r\)$/, "®️");
|
||||
const trademarked = new InputRule(/\(tm\)$/, "™️");
|
||||
|
||||
@@ -13,13 +13,11 @@ export default function attachmentMenuItems(
|
||||
name: "replaceAttachment",
|
||||
tooltip: dictionary.replaceAttachment,
|
||||
icon: <ReplaceIcon />,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
name: "deleteAttachment",
|
||||
tooltip: dictionary.deleteAttachment,
|
||||
icon: <TrashIcon />,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
name: "separator",
|
||||
|
||||
@@ -33,14 +33,12 @@ export default function imageMenuItems(
|
||||
name: "alignLeft",
|
||||
tooltip: dictionary.alignLeft,
|
||||
icon: <AlignImageLeftIcon />,
|
||||
visible: true,
|
||||
active: isLeftAligned,
|
||||
},
|
||||
{
|
||||
name: "alignCenter",
|
||||
tooltip: dictionary.alignCenter,
|
||||
icon: <AlignImageCenterIcon />,
|
||||
visible: true,
|
||||
active: (state) =>
|
||||
isNodeActive(schema.nodes.image)(state) &&
|
||||
!isLeftAligned(state) &&
|
||||
@@ -51,19 +49,16 @@ export default function imageMenuItems(
|
||||
name: "alignRight",
|
||||
tooltip: dictionary.alignRight,
|
||||
icon: <AlignImageRightIcon />,
|
||||
visible: true,
|
||||
active: isRightAligned,
|
||||
},
|
||||
{
|
||||
name: "alignFullWidth",
|
||||
tooltip: dictionary.alignFullWidth,
|
||||
icon: <AlignFullWidthIcon />,
|
||||
visible: true,
|
||||
active: isFullWidthAligned,
|
||||
},
|
||||
{
|
||||
name: "separator",
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
name: "downloadImage",
|
||||
@@ -75,13 +70,11 @@ export default function imageMenuItems(
|
||||
name: "replaceImage",
|
||||
tooltip: dictionary.replaceImage,
|
||||
icon: <ReplaceIcon />,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
name: "deleteImage",
|
||||
tooltip: dictionary.deleteImage,
|
||||
icon: <TrashIcon />,
|
||||
visible: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
DoneIcon,
|
||||
ExpandedIcon,
|
||||
InfoIcon,
|
||||
StarredIcon,
|
||||
WarningIcon,
|
||||
} from "outline-icons";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import { NoticeTypes } from "@shared/editor/nodes/Notice";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
|
||||
export default function noticeMenuItems(
|
||||
state: EditorState,
|
||||
readOnly: boolean | undefined,
|
||||
dictionary: Dictionary
|
||||
): MenuItem[] {
|
||||
const node = state.selection.$from.node(-1);
|
||||
const currentStyle = node?.attrs.style as NoticeTypes;
|
||||
|
||||
const mapping = {
|
||||
[NoticeTypes.Info]: dictionary.infoNotice,
|
||||
[NoticeTypes.Warning]: dictionary.warningNotice,
|
||||
[NoticeTypes.Success]: dictionary.successNotice,
|
||||
[NoticeTypes.Tip]: dictionary.tipNotice,
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: "container_notice",
|
||||
visible: !readOnly,
|
||||
label: mapping[currentStyle],
|
||||
icon: <ExpandedIcon />,
|
||||
children: [
|
||||
{
|
||||
name: NoticeTypes.Info,
|
||||
icon: <InfoIcon />,
|
||||
label: dictionary.infoNotice,
|
||||
active: () => currentStyle === NoticeTypes.Info,
|
||||
},
|
||||
{
|
||||
name: NoticeTypes.Success,
|
||||
icon: <DoneIcon />,
|
||||
label: dictionary.successNotice,
|
||||
active: () => currentStyle === NoticeTypes.Success,
|
||||
},
|
||||
{
|
||||
name: NoticeTypes.Warning,
|
||||
icon: <WarningIcon />,
|
||||
label: dictionary.warningNotice,
|
||||
active: () => currentStyle === NoticeTypes.Warning,
|
||||
},
|
||||
{
|
||||
name: NoticeTypes.Tip,
|
||||
icon: <StarredIcon />,
|
||||
label: dictionary.tipNotice,
|
||||
active: () => currentStyle === NoticeTypes.Tip,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -24,7 +24,6 @@ const NotificationMenu: React.FC = () => {
|
||||
{
|
||||
type: "button",
|
||||
title: t("Notification settings"),
|
||||
visible: true,
|
||||
onClick: () => performAction(navigateToNotificationSettings, context),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -28,7 +28,6 @@ function TableOfContentsMenu() {
|
||||
const i = [
|
||||
{
|
||||
type: "heading",
|
||||
visible: true,
|
||||
title: t("Contents"),
|
||||
},
|
||||
...headings.map((heading) => ({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import isEqual from "fast-deep-equal";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
@@ -136,14 +137,19 @@ function History() {
|
||||
"desc"
|
||||
);
|
||||
|
||||
const latestEvent = merged[0];
|
||||
const latestRevisionEvent = merged.find(
|
||||
(event) => event.name === "revisions.create"
|
||||
);
|
||||
|
||||
if (latestEvent && document) {
|
||||
const latestRevisionEvent = merged.find(
|
||||
(event) => event.name === "revisions.create"
|
||||
);
|
||||
if (latestRevisionEvent && document) {
|
||||
const latestRevision = revisions.get(latestRevisionEvent.id);
|
||||
|
||||
if (latestEvent.createdAt !== document.updatedAt) {
|
||||
const isDocUpdated =
|
||||
latestRevision?.title !== document.title ||
|
||||
!isEqual(latestRevision.data, document.data);
|
||||
|
||||
if (isDocUpdated) {
|
||||
revisions.remove(RevisionHelper.latestId(document.id));
|
||||
merged.unshift({
|
||||
id: RevisionHelper.latestId(document.id),
|
||||
name: "revisions.create",
|
||||
@@ -157,7 +163,7 @@ function History() {
|
||||
}
|
||||
|
||||
return merged;
|
||||
}, [document, revisionEvents, nonRevisionEvents]);
|
||||
}, [revisions, document, revisionEvents, nonRevisionEvents]);
|
||||
|
||||
const onCloseHistory = React.useCallback(() => {
|
||||
if (document) {
|
||||
|
||||
@@ -105,7 +105,7 @@ function Message({ notice }: { notice: string }) {
|
||||
case "authentication-provider-disabled":
|
||||
return (
|
||||
<Trans>
|
||||
Authentication failed – this login method was disabled by a team
|
||||
Authentication failed – this login method was disabled by a workspace
|
||||
admin.
|
||||
</Trans>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import ArrowKeyNavigation from "~/components/ArrowKeyNavigation";
|
||||
import Fade from "~/components/Fade";
|
||||
import { ConditionalFade } from "~/components/Fade";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import RecentSearchListItem from "./RecentSearchListItem";
|
||||
|
||||
@@ -19,7 +19,6 @@ function RecentSearches(
|
||||
) {
|
||||
const { searches } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const [isPreloaded] = React.useState(searches.recent.length > 0);
|
||||
|
||||
React.useEffect(() => {
|
||||
void searches.fetchPage({
|
||||
@@ -48,7 +47,11 @@ function RecentSearches(
|
||||
</>
|
||||
) : null;
|
||||
|
||||
return isPreloaded ? content : <Fade>{content}</Fade>;
|
||||
return (
|
||||
<ConditionalFade animate={!searches.recent.length}>
|
||||
{content}
|
||||
</ConditionalFade>
|
||||
);
|
||||
}
|
||||
|
||||
const Heading = styled.h2`
|
||||
|
||||
@@ -10,6 +10,7 @@ import Group from "~/models/Group";
|
||||
import { Action } from "~/components/Actions";
|
||||
import Button from "~/components/Button";
|
||||
import Empty from "~/components/Empty";
|
||||
import { ConditionalFade } from "~/components/Fade";
|
||||
import Heading from "~/components/Heading";
|
||||
import InputSearch from "~/components/InputSearch";
|
||||
import Scene from "~/components/Scene";
|
||||
@@ -149,15 +150,17 @@ function Groups() {
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</StickyFilters>
|
||||
<GroupsTable
|
||||
data={data ?? []}
|
||||
sort={sort}
|
||||
loading={loading}
|
||||
page={{
|
||||
hasNext: !!next,
|
||||
fetchNext: next,
|
||||
}}
|
||||
/>
|
||||
<ConditionalFade animate={!data}>
|
||||
<GroupsTable
|
||||
data={data ?? []}
|
||||
sort={sort}
|
||||
loading={loading}
|
||||
page={{
|
||||
hasNext: !!next,
|
||||
fetchNext: next,
|
||||
}}
|
||||
/>
|
||||
</ConditionalFade>
|
||||
</>
|
||||
)}
|
||||
</Scene>
|
||||
|
||||
@@ -9,7 +9,7 @@ import styled from "styled-components";
|
||||
import UsersStore, { queriedUsers } from "~/stores/UsersStore";
|
||||
import { Action } from "~/components/Actions";
|
||||
import Button from "~/components/Button";
|
||||
import Fade from "~/components/Fade";
|
||||
import { ConditionalFade } from "~/components/Fade";
|
||||
import Heading from "~/components/Heading";
|
||||
import InputSearch from "~/components/InputSearch";
|
||||
import Scene from "~/components/Scene";
|
||||
@@ -22,7 +22,7 @@ import usePolicy from "~/hooks/usePolicy";
|
||||
import useQuery from "~/hooks/useQuery";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { useTableRequest } from "~/hooks/useTableRequest";
|
||||
import { PeopleTable } from "./components/PeopleTable";
|
||||
import { MembersTable } from "./components/MembersTable";
|
||||
import { StickyFilters } from "./components/StickyFilters";
|
||||
import UserRoleFilter from "./components/UserRoleFilter";
|
||||
import UserStatusFilter from "./components/UserStatusFilter";
|
||||
@@ -163,8 +163,8 @@ function Members() {
|
||||
onSelect={handleRoleFilter}
|
||||
/>
|
||||
</StickyFilters>
|
||||
<Fade>
|
||||
<PeopleTable
|
||||
<ConditionalFade animate={!data}>
|
||||
<MembersTable
|
||||
data={data ?? []}
|
||||
sort={sort}
|
||||
canManage={can.update}
|
||||
@@ -174,7 +174,7 @@ function Members() {
|
||||
fetchNext: next,
|
||||
}}
|
||||
/>
|
||||
</Fade>
|
||||
</ConditionalFade>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import Fade from "~/components/Fade";
|
||||
import { ConditionalFade } from "~/components/Fade";
|
||||
import Heading from "~/components/Heading";
|
||||
import Notice from "~/components/Notice";
|
||||
import Scene from "~/components/Scene";
|
||||
@@ -83,20 +83,18 @@ function Shares() {
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
{data?.length ? (
|
||||
<Fade>
|
||||
<SharesTable
|
||||
data={data ?? []}
|
||||
sort={sort}
|
||||
canManage={can.update}
|
||||
loading={loading}
|
||||
page={{
|
||||
hasNext: !!next,
|
||||
fetchNext: next,
|
||||
}}
|
||||
/>
|
||||
</Fade>
|
||||
) : null}
|
||||
<ConditionalFade animate={!data}>
|
||||
<SharesTable
|
||||
data={data ?? []}
|
||||
sort={sort}
|
||||
canManage={can.update}
|
||||
loading={loading}
|
||||
page={{
|
||||
hasNext: !!next,
|
||||
fetchNext: next,
|
||||
}}
|
||||
/>
|
||||
</ConditionalFade>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { transparentize } from "polished";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { s } from "@shared/styles";
|
||||
|
||||
/**
|
||||
@@ -8,8 +9,9 @@ import { s } from "@shared/styles";
|
||||
export const ActionRow = styled.div`
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 16px 50vw;
|
||||
margin: 0 -50vw;
|
||||
width: 100vw;
|
||||
padding: 16px 12px;
|
||||
margin-left: -12px;
|
||||
|
||||
background: ${s("background")};
|
||||
|
||||
@@ -17,4 +19,8 @@ export const ActionRow = styled.div`
|
||||
backdrop-filter: blur(20px);
|
||||
background: ${(props) => transparentize(0.2, props.theme.background)};
|
||||
}
|
||||
|
||||
${breakpoint("tablet")`
|
||||
width: auto;
|
||||
`}
|
||||
`;
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ type Props = Omit<TableProps<User>, "columns" | "rowHeight"> & {
|
||||
canManage: boolean;
|
||||
};
|
||||
|
||||
export function PeopleTable({ canManage, ...rest }: Props) {
|
||||
export function MembersTable({ canManage, ...rest }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
+8
-8
@@ -48,11 +48,11 @@
|
||||
"> 0.25%, not dead"
|
||||
],
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.749.0",
|
||||
"@aws-sdk/lib-storage": "3.749.0",
|
||||
"@aws-sdk/s3-presigned-post": "3.749.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.749.0",
|
||||
"@aws-sdk/signature-v4-crt": "^3.749.0",
|
||||
"@aws-sdk/client-s3": "3.750.0",
|
||||
"@aws-sdk/lib-storage": "3.750.0",
|
||||
"@aws-sdk/s3-presigned-post": "3.750.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.750.0",
|
||||
"@aws-sdk/signature-v4-crt": "^3.750.0",
|
||||
"@babel/core": "^7.26.9",
|
||||
"@babel/plugin-proposal-decorators": "^7.25.9",
|
||||
"@babel/plugin-transform-class-properties": "^7.25.9",
|
||||
@@ -188,7 +188,7 @@
|
||||
"prosemirror-transform": "1.10.0",
|
||||
"prosemirror-view": "^1.37.1",
|
||||
"query-string": "^7.1.3",
|
||||
"randomstring": "1.3.0",
|
||||
"randomstring": "1.3.1",
|
||||
"rate-limiter-flexible": "^2.4.2",
|
||||
"react": "^17.0.2",
|
||||
"react-avatar-editor": "^13.0.2",
|
||||
@@ -217,7 +217,7 @@
|
||||
"rfc6902": "^5.1.1",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"scroll-into-view-if-needed": "^3.1.0",
|
||||
"semver": "^7.6.2",
|
||||
"semver": "^7.7.1",
|
||||
"sequelize": "^6.37.3",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
"sequelize-encrypted": "^1.0.0",
|
||||
@@ -356,7 +356,7 @@
|
||||
"rimraf": "^2.5.4",
|
||||
"rollup-plugin-webpack-stats": "^2.0.1",
|
||||
"terser": "^5.37.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "^5.7.3",
|
||||
"vite-plugin-static-copy": "^0.17.0",
|
||||
"yarn-deduplicate": "^6.0.2"
|
||||
},
|
||||
|
||||
@@ -190,6 +190,56 @@ describe("accountProvisioner", () => {
|
||||
expect(error).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should prioritize enabled authentication provider", async () => {
|
||||
const existingTeam = await buildTeam();
|
||||
const existingProviders = await existingTeam.$get(
|
||||
"authenticationProviders"
|
||||
);
|
||||
|
||||
const team2 = await buildTeam();
|
||||
|
||||
const providers = await team2.$get("authenticationProviders");
|
||||
const authenticationProvider = providers[0];
|
||||
await authenticationProvider.update({
|
||||
enabled: false,
|
||||
providerId: existingProviders[0].providerId,
|
||||
});
|
||||
|
||||
const existing = await buildUser({
|
||||
teamId: existingTeam.id,
|
||||
});
|
||||
const authentications = await existing.$get("authentications");
|
||||
const authentication = authentications[0];
|
||||
const { isNewUser, isNewTeam } = await accountProvisioner({
|
||||
ip,
|
||||
user: {
|
||||
name: existing.name,
|
||||
email: existing.email!,
|
||||
avatarUrl: existing.avatarUrl,
|
||||
},
|
||||
team: {
|
||||
name: existingTeam.name,
|
||||
avatarUrl: existingTeam.avatarUrl,
|
||||
subdomain: faker.internet.domainWord(),
|
||||
},
|
||||
authenticationProvider: {
|
||||
name: authenticationProvider.name,
|
||||
providerId: authenticationProvider.providerId,
|
||||
},
|
||||
authentication: {
|
||||
providerId: authentication.providerId,
|
||||
accessToken: "123",
|
||||
scopes: ["read"],
|
||||
},
|
||||
});
|
||||
const auth = await UserAuthentication.findByPk(authentication.id);
|
||||
expect(auth?.accessToken).toEqual("123");
|
||||
expect(auth?.scopes.length).toEqual(1);
|
||||
expect(auth?.scopes[0]).toEqual("read");
|
||||
expect(isNewTeam).toEqual(false);
|
||||
expect(isNewUser).toEqual(false);
|
||||
});
|
||||
|
||||
it("should throw an error when the domain is not allowed", async () => {
|
||||
const existingTeam = await buildTeam();
|
||||
const admin = await buildAdmin({ teamId: existingTeam.id });
|
||||
|
||||
@@ -102,7 +102,7 @@ async function accountProvisioner({
|
||||
if (err.id === "invalid_authentication") {
|
||||
const authenticationProvider = await AuthenticationProvider.findOne({
|
||||
where: {
|
||||
name: authenticationProviderParams.name, // example: "google"
|
||||
name: authenticationProviderParams.name,
|
||||
teamId: teamParams.teamId,
|
||||
},
|
||||
include: [
|
||||
@@ -112,6 +112,7 @@ async function accountProvisioner({
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
order: [["enabled", "DESC"]],
|
||||
});
|
||||
|
||||
if (authenticationProvider) {
|
||||
|
||||
@@ -61,6 +61,7 @@ async function teamProvisioner({
|
||||
paranoid: false,
|
||||
},
|
||||
],
|
||||
order: [["enabled", "DESC"]],
|
||||
});
|
||||
|
||||
// This authentication provider already exists which means we have a team and
|
||||
|
||||
@@ -63,7 +63,7 @@ export default async function userProvisioner({
|
||||
const auth = authentication
|
||||
? await UserAuthentication.findOne({
|
||||
where: {
|
||||
providerId: "" + authentication.providerId,
|
||||
providerId: String(authentication.providerId),
|
||||
},
|
||||
include: [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
const tableName = "team_domains";
|
||||
// because of this issue in Sequelize the foreign key constraint may be named differently depending
|
||||
// on when the previous migrations were ran https://github.com/sequelize/sequelize/pull/9890
|
||||
|
||||
const constraintNames = [
|
||||
"team_domains_createdById_fkey",
|
||||
"createdById_foreign_idx"
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
let error;
|
||||
|
||||
for (const constraintName of constraintNames) {
|
||||
try {
|
||||
await queryInterface.sequelize.query(
|
||||
`alter table "${tableName}" drop constraint "${constraintName}"`
|
||||
);
|
||||
await queryInterface.sequelize.query(`alter table "${tableName}"
|
||||
add constraint "${constraintName}" foreign key("createdById") references "users" ("id")
|
||||
on delete set null`);
|
||||
return;
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
let error;
|
||||
|
||||
for (const constraintName of constraintNames) {
|
||||
try {
|
||||
await queryInterface.sequelize.query(
|
||||
`alter table "${tableName}" drop constraint "${constraintName}"`
|
||||
);
|
||||
await queryInterface.sequelize.query(`alter table "${tableName}"\
|
||||
add constraint "${constraintName}" foreign key("createdById") references "users" ("id")
|
||||
on delete no action`);
|
||||
return;
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
},
|
||||
};
|
||||
@@ -79,6 +79,7 @@ describe("user model", () => {
|
||||
expect(response.length).toEqual(1);
|
||||
expect(response[0]).toEqual(collection.id);
|
||||
});
|
||||
|
||||
it("should return read collections", async () => {
|
||||
const team = await buildTeam();
|
||||
const user = await buildUser({
|
||||
@@ -92,6 +93,7 @@ describe("user model", () => {
|
||||
expect(response.length).toEqual(1);
|
||||
expect(response[0]).toEqual(collection.id);
|
||||
});
|
||||
|
||||
it("should not return private collections", async () => {
|
||||
const team = await buildTeam();
|
||||
const user = await buildUser({
|
||||
@@ -104,6 +106,7 @@ describe("user model", () => {
|
||||
const response = await user.collectionIds();
|
||||
expect(response.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("should not return private collection with membership", async () => {
|
||||
const team = await buildTeam();
|
||||
const user = await buildUser({
|
||||
|
||||
@@ -614,6 +614,7 @@ class User extends ParanoidModel<
|
||||
where: { email: this.email },
|
||||
},
|
||||
],
|
||||
order: [["createdAt", "ASC"]],
|
||||
});
|
||||
|
||||
// hooks
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import {
|
||||
buildComment,
|
||||
buildDocument,
|
||||
buildSubscription,
|
||||
buildUser,
|
||||
@@ -7,6 +8,112 @@ import {
|
||||
import NotificationHelper from "./NotificationHelper";
|
||||
|
||||
describe("NotificationHelper", () => {
|
||||
describe("getCommentNotificationRecipients", () => {
|
||||
it("should return users who have notification enabled for comment creation and are subscribed to the document in case of parent comment", async () => {
|
||||
const documentAuthor = await buildUser();
|
||||
const document = await buildDocument({
|
||||
userId: documentAuthor.id,
|
||||
teamId: documentAuthor.teamId,
|
||||
});
|
||||
const notificationEnabledUser = await buildUser({
|
||||
teamId: document.teamId,
|
||||
notificationSettings: { [NotificationEventType.CreateComment]: true },
|
||||
});
|
||||
const notificationDisabledUser = await buildUser({
|
||||
teamId: document.teamId,
|
||||
notificationSettings: { [NotificationEventType.CreateComment]: false },
|
||||
});
|
||||
await Promise.all([
|
||||
buildSubscription({
|
||||
userId: documentAuthor.id,
|
||||
documentId: document.id,
|
||||
}),
|
||||
buildSubscription({
|
||||
userId: notificationEnabledUser.id,
|
||||
documentId: document.id,
|
||||
}),
|
||||
buildSubscription({
|
||||
userId: notificationDisabledUser.id,
|
||||
documentId: document.id,
|
||||
}),
|
||||
]);
|
||||
|
||||
const comment = await buildComment({
|
||||
documentId: document.id,
|
||||
userId: documentAuthor.id,
|
||||
});
|
||||
|
||||
const recipients =
|
||||
await NotificationHelper.getCommentNotificationRecipients(
|
||||
document,
|
||||
comment,
|
||||
comment.createdById
|
||||
);
|
||||
|
||||
expect(recipients.length).toEqual(1);
|
||||
expect(recipients[0].id).toEqual(notificationEnabledUser.id);
|
||||
});
|
||||
|
||||
it("should return users who have notification enabled for comment creation and are in the thread in case of child comment", async () => {
|
||||
const documentAuthor = await buildUser();
|
||||
const document = await buildDocument({
|
||||
userId: documentAuthor.id,
|
||||
teamId: documentAuthor.teamId,
|
||||
});
|
||||
const notificationEnabledUserInThread = await buildUser({
|
||||
teamId: document.teamId,
|
||||
notificationSettings: { [NotificationEventType.CreateComment]: true },
|
||||
});
|
||||
const notificationEnabledUserNotInThread = await buildUser({
|
||||
teamId: document.teamId,
|
||||
notificationSettings: { [NotificationEventType.CreateComment]: true },
|
||||
});
|
||||
const notificationDisabledUser = await buildUser({
|
||||
teamId: document.teamId,
|
||||
notificationSettings: {
|
||||
[NotificationEventType.CreateComment]: false,
|
||||
},
|
||||
});
|
||||
await Promise.all([
|
||||
buildSubscription({
|
||||
userId: documentAuthor.id,
|
||||
documentId: document.id,
|
||||
}),
|
||||
buildSubscription({
|
||||
userId: notificationEnabledUserInThread.id,
|
||||
documentId: document.id,
|
||||
}),
|
||||
buildSubscription({
|
||||
userId: notificationEnabledUserNotInThread.id,
|
||||
documentId: document.id,
|
||||
}),
|
||||
buildSubscription({
|
||||
userId: notificationDisabledUser.id,
|
||||
documentId: document.id,
|
||||
}),
|
||||
]);
|
||||
const parentComment = await buildComment({
|
||||
documentId: document.id,
|
||||
userId: notificationEnabledUserInThread.id,
|
||||
});
|
||||
const childComment = await buildComment({
|
||||
documentId: document.id,
|
||||
userId: documentAuthor.id,
|
||||
parentCommentId: parentComment.id,
|
||||
});
|
||||
|
||||
const recipients =
|
||||
await NotificationHelper.getCommentNotificationRecipients(
|
||||
document,
|
||||
childComment,
|
||||
childComment.createdById
|
||||
);
|
||||
|
||||
expect(recipients.length).toEqual(1);
|
||||
expect(recipients[0].id).toEqual(notificationEnabledUserInThread.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDocumentNotificationRecipients", () => {
|
||||
it("should return all users who have notification enabled for the event", async () => {
|
||||
const documentAuthor = await buildUser();
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class NotificationHelper {
|
||||
): Promise<User[]> => {
|
||||
let recipients = await this.getDocumentNotificationRecipients({
|
||||
document,
|
||||
notificationType: NotificationEventType.UpdateDocument,
|
||||
notificationType: NotificationEventType.CreateComment,
|
||||
onlySubscribers: !comment.parentCommentId,
|
||||
actorId,
|
||||
});
|
||||
|
||||
@@ -601,6 +601,8 @@ export default class SearchHelper {
|
||||
}
|
||||
|
||||
private static removeStopWords(query: string): string {
|
||||
// Based on:
|
||||
// https://github.com/postgres/postgres/blob/fc0d0ce978752493868496be6558fa17b7c4c3cf/src/backend/snowball/stopwords/english.stop
|
||||
const stopwords = [
|
||||
"i",
|
||||
"me",
|
||||
@@ -665,7 +667,6 @@ export default class SearchHelper {
|
||||
"because",
|
||||
"as",
|
||||
"until",
|
||||
"while",
|
||||
"of",
|
||||
"at",
|
||||
"by",
|
||||
@@ -673,7 +674,6 @@ export default class SearchHelper {
|
||||
"with",
|
||||
"about",
|
||||
"against",
|
||||
"between",
|
||||
"into",
|
||||
"through",
|
||||
"during",
|
||||
@@ -681,18 +681,12 @@ export default class SearchHelper {
|
||||
"after",
|
||||
"above",
|
||||
"below",
|
||||
"to",
|
||||
"from",
|
||||
"up",
|
||||
"down",
|
||||
"in",
|
||||
"out",
|
||||
"on",
|
||||
"off",
|
||||
"over",
|
||||
"under",
|
||||
"again",
|
||||
"further",
|
||||
"then",
|
||||
"once",
|
||||
"here",
|
||||
@@ -700,22 +694,15 @@ export default class SearchHelper {
|
||||
"when",
|
||||
"where",
|
||||
"why",
|
||||
"how",
|
||||
"all",
|
||||
"any",
|
||||
"both",
|
||||
"each",
|
||||
"few",
|
||||
"more",
|
||||
"most",
|
||||
"other",
|
||||
"some",
|
||||
"such",
|
||||
"no",
|
||||
"nor",
|
||||
"not",
|
||||
"only",
|
||||
"own",
|
||||
"same",
|
||||
"so",
|
||||
"than",
|
||||
@@ -723,12 +710,8 @@ export default class SearchHelper {
|
||||
"very",
|
||||
"s",
|
||||
"t",
|
||||
"can",
|
||||
"will",
|
||||
"just",
|
||||
"don",
|
||||
"should",
|
||||
"now",
|
||||
];
|
||||
return query
|
||||
.split(" ")
|
||||
|
||||
+1
-36
@@ -6,18 +6,8 @@ import Koa from "koa";
|
||||
import escape from "lodash/escape";
|
||||
import isNil from "lodash/isNil";
|
||||
import snakeCase from "lodash/snakeCase";
|
||||
import {
|
||||
ValidationError as SequelizeValidationError,
|
||||
EmptyResultError as SequelizeEmptyResultError,
|
||||
} from "sequelize";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
AuthorizationError,
|
||||
ClientClosedRequestError,
|
||||
InternalError,
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
} from "@server/errors";
|
||||
import { ClientClosedRequestError, InternalError } from "@server/errors";
|
||||
import { requestErrorHandler } from "@server/logging/sentry";
|
||||
|
||||
let errorHtmlCache: Buffer | undefined;
|
||||
@@ -32,16 +22,6 @@ export default function onerror(app: Koa) {
|
||||
|
||||
err = wrapInNativeError(err);
|
||||
|
||||
if (err instanceof SequelizeValidationError) {
|
||||
if (err.errors && err.errors[0]) {
|
||||
err = ValidationError(
|
||||
`${err.errors[0].message} (${err.errors[0].path})`
|
||||
);
|
||||
} else {
|
||||
err = ValidationError();
|
||||
}
|
||||
}
|
||||
|
||||
// Client aborted errors are a 500 by default, but 499 is more appropriate
|
||||
if (err instanceof formidable.errors.FormidableError) {
|
||||
if (err.internalCode === 1002) {
|
||||
@@ -49,21 +29,6 @@ export default function onerror(app: Koa) {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
err.code === "ENOENT" ||
|
||||
err instanceof SequelizeEmptyResultError ||
|
||||
/Not found/i.test(err.message)
|
||||
) {
|
||||
err = NotFoundError();
|
||||
}
|
||||
|
||||
if (
|
||||
!(err instanceof AuthorizationError) &&
|
||||
/Authorization error/i.test(err.message)
|
||||
) {
|
||||
err = AuthorizationError();
|
||||
}
|
||||
|
||||
// Push only unknown and 500 status errors to sentry
|
||||
if (
|
||||
typeof err.status !== "number" ||
|
||||
|
||||
@@ -179,9 +179,15 @@ describe("revisions.create", () => {
|
||||
expect(events[2].documentId).toEqual(document.id);
|
||||
|
||||
// Events should mention correct `userId`.
|
||||
expect(events[0].userId).toEqual(collaborator0.id);
|
||||
expect(events[1].userId).toEqual(collaborator1.id);
|
||||
expect(events[2].userId).toEqual(collaborator2.id);
|
||||
const userIds = events.map((event) => event.userId);
|
||||
expect(userIds).toEqual(
|
||||
expect.arrayContaining([
|
||||
collaborator0.id,
|
||||
collaborator1.id,
|
||||
collaborator2.id,
|
||||
])
|
||||
);
|
||||
expect(userIds.length).toBe(3);
|
||||
});
|
||||
|
||||
test("should not send multiple emails", async () => {
|
||||
|
||||
@@ -9,6 +9,59 @@ exports[`#comments.add_reaction should require authentication 1`] = `
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`#comments.create should create a comment from markdown text 1`] = `
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"attrs": {
|
||||
"level": 2,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"text": "heading",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "heading",
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "list item 1",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "list_item",
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "list item 2",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "list_item",
|
||||
},
|
||||
],
|
||||
"type": "bullet_list",
|
||||
},
|
||||
],
|
||||
"type": "doc",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`#comments.create should require authentication 1`] = `
|
||||
{
|
||||
"error": "authentication_required",
|
||||
|
||||
@@ -481,6 +481,30 @@ describe("#comments.create", () => {
|
||||
expect(body.policies[0].abilities.delete).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should create a comment from markdown text", async () => {
|
||||
const team = await buildTeam();
|
||||
const user = await buildUser({ teamId: team.id });
|
||||
const document = await buildDocument({
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
});
|
||||
|
||||
const text = "## heading\n\n- list item 1\n- list item 2";
|
||||
|
||||
const res = await server.post("/api/comments.create", {
|
||||
body: {
|
||||
token: user.getJwtToken(),
|
||||
documentId: document.id,
|
||||
text,
|
||||
},
|
||||
});
|
||||
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.data).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not allow empty comment data", async () => {
|
||||
const team = await buildTeam();
|
||||
const user = await buildUser({ teamId: team.id });
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TeamPreference,
|
||||
MentionType,
|
||||
} from "@shared/types";
|
||||
import { parser } from "@server/editor";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import { feature } from "@server/middlewares/feature";
|
||||
import { rateLimiter } from "@server/middlewares/rateLimiter";
|
||||
@@ -13,6 +14,7 @@ import { transaction } from "@server/middlewares/transaction";
|
||||
import validate from "@server/middlewares/validate";
|
||||
import { Document, Comment, Collection, Reaction } from "@server/models";
|
||||
import { ProsemirrorHelper } from "@server/models/helpers/ProsemirrorHelper";
|
||||
import { TextHelper } from "@server/models/helpers/TextHelper";
|
||||
import { authorize } from "@server/policies";
|
||||
import { presentComment, presentPolicies } from "@server/presenters";
|
||||
import { APIContext } from "@server/types";
|
||||
@@ -30,7 +32,7 @@ router.post(
|
||||
validate(T.CommentsCreateSchema),
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.CommentsCreateReq>) => {
|
||||
const { id, documentId, parentCommentId, data } = ctx.input.body;
|
||||
const { id, documentId, parentCommentId } = ctx.input.body;
|
||||
const { user } = ctx.state.auth;
|
||||
const { transaction } = ctx.state;
|
||||
|
||||
@@ -40,6 +42,15 @@ router.post(
|
||||
});
|
||||
authorize(user, "comment", document);
|
||||
|
||||
const text = ctx.input.body.text
|
||||
? await TextHelper.replaceImagesWithAttachments(
|
||||
ctx,
|
||||
ctx.input.body.text,
|
||||
user
|
||||
)
|
||||
: undefined;
|
||||
const data = text ? parser.parse(text).toJSON() : ctx.input.body.data;
|
||||
|
||||
const comment = await Comment.createWithCtx(ctx, {
|
||||
id,
|
||||
data,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import emojiRegex from "emoji-regex";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import { z } from "zod";
|
||||
import { CommentStatusFilter } from "@shared/types";
|
||||
import { BaseSchema, ProsemirrorSchema } from "@server/routes/api/schema";
|
||||
@@ -23,19 +24,26 @@ const CommentsSortParamsSchema = z.object({
|
||||
});
|
||||
|
||||
export const CommentsCreateSchema = BaseSchema.extend({
|
||||
body: z.object({
|
||||
/** Allow creation with a specific ID */
|
||||
id: z.string().uuid().optional(),
|
||||
body: z
|
||||
.object({
|
||||
/** Allow creation with a specific ID */
|
||||
id: z.string().uuid().optional(),
|
||||
|
||||
/** Create comment for this document */
|
||||
documentId: z.string().uuid(),
|
||||
/** Create comment for this document */
|
||||
documentId: z.string().uuid(),
|
||||
|
||||
/** Create comment under this parent */
|
||||
parentCommentId: z.string().uuid().optional(),
|
||||
/** Create comment under this parent */
|
||||
parentCommentId: z.string().uuid().optional(),
|
||||
|
||||
/** Create comment with this data */
|
||||
data: ProsemirrorSchema(),
|
||||
}),
|
||||
/** Create comment with this data */
|
||||
data: ProsemirrorSchema().optional(),
|
||||
|
||||
/** Create comment with this text */
|
||||
text: z.string().optional(),
|
||||
})
|
||||
.refine((obj) => !(isEmpty(obj.data) && isEmpty(obj.text)), {
|
||||
message: "One of data or text is required",
|
||||
}),
|
||||
});
|
||||
|
||||
export type CommentsCreateReq = z.infer<typeof CommentsCreateSchema>;
|
||||
|
||||
@@ -22,6 +22,7 @@ import groupMemberships from "./groupMemberships";
|
||||
import groups from "./groups";
|
||||
import installation from "./installation";
|
||||
import integrations from "./integrations";
|
||||
import apiErrorHandler from "./middlewares/apiErrorHandler";
|
||||
import apiResponse from "./middlewares/apiResponse";
|
||||
import apiTracer from "./middlewares/apiTracer";
|
||||
import editor from "./middlewares/editor";
|
||||
@@ -60,6 +61,7 @@ api.use(coalesceBody());
|
||||
api.use<BaseContext, UserAgentContext>(userAgent);
|
||||
api.use(apiTracer());
|
||||
api.use(apiResponse());
|
||||
api.use(apiErrorHandler());
|
||||
api.use(editor());
|
||||
|
||||
// Register plugin API routes before others to allow for overrides
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Context, Next } from "koa";
|
||||
import {
|
||||
ValidationError as SequelizeValidationError,
|
||||
EmptyResultError as SequelizeEmptyResultError,
|
||||
} from "sequelize";
|
||||
import {
|
||||
AuthorizationError,
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
} from "@server/errors";
|
||||
|
||||
export default function apiErrorHandler() {
|
||||
return async function apiErrorHandlerMiddleware(ctx: Context, next: Next) {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
let transformedErr = err;
|
||||
|
||||
if (
|
||||
!(err instanceof AuthorizationError) &&
|
||||
/Authorization error/i.test(err.message)
|
||||
) {
|
||||
transformedErr = AuthorizationError();
|
||||
}
|
||||
|
||||
if (err instanceof SequelizeValidationError) {
|
||||
if (err.errors && err.errors[0]) {
|
||||
transformedErr = ValidationError(
|
||||
`${err.errors[0].message} (${err.errors[0].path})`
|
||||
);
|
||||
} else {
|
||||
transformedErr = ValidationError();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
err.code === "ENOENT" ||
|
||||
err instanceof SequelizeEmptyResultError ||
|
||||
/Not found/i.test(err.message)
|
||||
) {
|
||||
transformedErr = NotFoundError();
|
||||
}
|
||||
|
||||
throw transformedErr;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -112,6 +112,10 @@ export const renderApp = async (
|
||||
<script type="module" nonce="${ctx.state.cspNonce}" src="${viteHost}/static/${entry}"></script>
|
||||
`;
|
||||
|
||||
// Ensure no caching is performed
|
||||
ctx.response.set("Cache-Control", "no-cache, must-revalidate");
|
||||
ctx.response.set("Expires", "-1");
|
||||
|
||||
ctx.body = page
|
||||
.toString()
|
||||
.replace(/\{env\}/g, environment)
|
||||
|
||||
+8
-11
@@ -139,29 +139,26 @@ router.get("*", shareDomains(), async (ctx, next) => {
|
||||
}
|
||||
|
||||
const team = await getTeamFromContext(ctx);
|
||||
let redirectUrl;
|
||||
|
||||
if (env.isCloudHosted) {
|
||||
// Redirect all requests to custom domain if one is set
|
||||
if (team?.domain && team.domain !== ctx.hostname) {
|
||||
redirectUrl = ctx.href.replace(ctx.hostname, team.domain);
|
||||
if (team?.domain) {
|
||||
if (team.domain !== ctx.hostname) {
|
||||
ctx.redirect(ctx.href.replace(ctx.hostname, team.domain));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect if subdomain is not the current team's subdomain
|
||||
else if (team?.subdomain) {
|
||||
const { teamSubdomain } = parseDomain(ctx.href);
|
||||
if (team?.subdomain !== teamSubdomain) {
|
||||
redirectUrl = ctx.href.replace(
|
||||
`//${teamSubdomain}.`,
|
||||
`//${team.subdomain}.`
|
||||
ctx.redirect(
|
||||
ctx.href.replace(`//${teamSubdomain}.`, `//${team.subdomain}.`)
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (redirectUrl) {
|
||||
ctx.redirect(redirectUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const analytics = team
|
||||
|
||||
@@ -4,6 +4,7 @@ import cookie from "cookie";
|
||||
import Koa from "koa";
|
||||
import IO from "socket.io";
|
||||
import { createAdapter } from "socket.io-redis";
|
||||
import EDITOR_VERSION from "@shared/editor/version";
|
||||
import { AuthenticationError } from "@server/errors";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import Metrics from "@server/logging/Metrics";
|
||||
@@ -116,7 +117,7 @@ export default function init(
|
||||
await authenticate(socket);
|
||||
Logger.debug("websockets", `Authenticated socket ${socket.id}`);
|
||||
|
||||
socket.emit("authenticated", true);
|
||||
socket.emit("authenticated", { editorVersion: EDITOR_VERSION });
|
||||
void authenticated(io, socket);
|
||||
} catch (err) {
|
||||
Logger.debug("websockets", `Authentication error socket ${socket.id}`, {
|
||||
|
||||
@@ -48,16 +48,15 @@ export function createDatabaseInstance(
|
||||
schema,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof URIError) {
|
||||
Logger.fatal(
|
||||
"Could not connect to database",
|
||||
new Error(
|
||||
`Failed to parse: ${databaseUrl}, ensure special characters in database URL are properly encoded`
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
throw error;
|
||||
Logger.fatal(
|
||||
"Could not connect to database",
|
||||
databaseUrl
|
||||
? new Error(
|
||||
`Failed to parse: "${databaseUrl}". Ensure special characters in database URL are encoded`
|
||||
)
|
||||
: new Error(`DATABASE_URL is not set.`)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,13 +61,14 @@ function createTableInner(
|
||||
: cellType.createAndFill(attrs);
|
||||
|
||||
for (let index = 0; index < colsCount; index += 1) {
|
||||
const attrs = colWidth
|
||||
? {
|
||||
colwidth: [colWidth],
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
}
|
||||
: null;
|
||||
const attrs =
|
||||
colWidth && index < colsCount - 1
|
||||
? {
|
||||
colwidth: [colWidth],
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
}
|
||||
: null;
|
||||
const cell = createCell(types.cell, attrs);
|
||||
|
||||
if (cell) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { s } from "../../styles";
|
||||
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
|
||||
|
||||
type Props = {
|
||||
/** Callback triggered when the caption is blurred */
|
||||
@@ -40,7 +40,7 @@ function Caption({ placeholder, children, isSelected, width, ...rest }: Props) {
|
||||
$isSelected={isSelected}
|
||||
onMouseDown={handleMouseDown}
|
||||
onPaste={handlePaste}
|
||||
className="caption"
|
||||
className={EditorStyleHelper.imageCaption}
|
||||
tabIndex={-1}
|
||||
role="textbox"
|
||||
contentEditable
|
||||
@@ -54,34 +54,16 @@ function Caption({ placeholder, children, isSelected, width, ...rest }: Props) {
|
||||
}
|
||||
|
||||
const Content = styled.p<{ $width: number; $isSelected: boolean }>`
|
||||
border: 0;
|
||||
display: block;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
color: ${s("textSecondary")};
|
||||
padding: 8px 0 4px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
min-height: 1em;
|
||||
outline: none;
|
||||
background: none;
|
||||
resize: none;
|
||||
user-select: text;
|
||||
margin: 0 auto !important;
|
||||
cursor: text;
|
||||
width: ${(props) => props.$width}px;
|
||||
min-width: 200px;
|
||||
max-width: 100%;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
font-size: 13px;
|
||||
`};
|
||||
|
||||
&:empty:not(:focus) {
|
||||
display: ${(props) => (props.$isSelected ? "block" : "none")}};
|
||||
}
|
||||
|
||||
&:empty:before {
|
||||
&:empty::before {
|
||||
color: ${s("placeholder")};
|
||||
content: attr(data-caption);
|
||||
pointer-events: none;
|
||||
|
||||
@@ -692,11 +692,29 @@ img.ProseMirror-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.${EditorStyleHelper.imageCaption} {
|
||||
border: 0;
|
||||
display: block;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
font-size: 13px;
|
||||
color: ${props.theme.textSecondary};
|
||||
padding: 8px 0 4px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
min-height: 1em;
|
||||
outline: none;
|
||||
background: none;
|
||||
resize: none;
|
||||
user-select: text;
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
|
||||
.ProseMirror[contenteditable="false"] {
|
||||
.caption {
|
||||
.${EditorStyleHelper.imageCaption} {
|
||||
pointer-events: none;
|
||||
}
|
||||
.caption:empty {
|
||||
.${EditorStyleHelper.imageCaption}:empty {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,6 @@ const embeds: EmbedDescriptor[] = [
|
||||
regexMatch: [new RegExp("^https?://www\\.google\\.com/maps/embed\\?(.*)$")],
|
||||
transformMatch: (matches: RegExpMatchArray) => matches[0],
|
||||
icon: <Img src="/images/google-maps.png" alt="Google Maps" />,
|
||||
visible: true,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Drawings",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { sanitizeUrl } from "../../utils/urls";
|
||||
import Caption from "../components/Caption";
|
||||
import ImageComponent from "../components/Image";
|
||||
import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import { EditorStyleHelper } from "../styles/EditorStyleHelper";
|
||||
import { ComponentProps } from "../types";
|
||||
import SimpleImage from "./SimpleImage";
|
||||
|
||||
@@ -152,11 +153,8 @@ export default class Image extends SimpleImage {
|
||||
const className = node.attrs.layoutClass
|
||||
? `image image-${node.attrs.layoutClass}`
|
||||
: "image";
|
||||
return [
|
||||
"div",
|
||||
{
|
||||
class: className,
|
||||
},
|
||||
|
||||
const children = [
|
||||
[
|
||||
"img",
|
||||
{
|
||||
@@ -167,7 +165,22 @@ export default class Image extends SimpleImage {
|
||||
contentEditable: "false",
|
||||
},
|
||||
],
|
||||
["p", { class: "caption" }, 0],
|
||||
];
|
||||
|
||||
if (node.attrs.alt) {
|
||||
children.push([
|
||||
"p",
|
||||
{ class: EditorStyleHelper.imageCaption },
|
||||
node.attrs.alt,
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
"div",
|
||||
{
|
||||
class: className,
|
||||
},
|
||||
...children,
|
||||
];
|
||||
},
|
||||
toPlainText: (node) =>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Token } from "markdown-it";
|
||||
import { WarningIcon, InfoIcon, StarredIcon, DoneIcon } from "outline-icons";
|
||||
import { wrappingInputRule } from "prosemirror-inputrules";
|
||||
import { NodeSpec, Node as ProsemirrorNode, NodeType } from "prosemirror-model";
|
||||
import { Command, EditorState, Transaction } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Primitive } from "utility-types";
|
||||
@@ -10,6 +11,13 @@ import { MarkdownSerializerState } from "../lib/markdown/serializer";
|
||||
import noticesRule from "../rules/notices";
|
||||
import Node from "./Node";
|
||||
|
||||
export enum NoticeTypes {
|
||||
Info = "info",
|
||||
Success = "success",
|
||||
Tip = "tip",
|
||||
Warning = "warning",
|
||||
}
|
||||
|
||||
export default class Notice extends Node {
|
||||
get name() {
|
||||
return "container_notice";
|
||||
@@ -23,7 +31,7 @@ export default class Notice extends Node {
|
||||
return {
|
||||
attrs: {
|
||||
style: {
|
||||
default: "info",
|
||||
default: NoticeTypes.Info,
|
||||
},
|
||||
},
|
||||
content:
|
||||
@@ -38,12 +46,12 @@ export default class Notice extends Node {
|
||||
contentElement: (node: HTMLDivElement) =>
|
||||
node.querySelector("div.content") || node,
|
||||
getAttrs: (dom: HTMLDivElement) => ({
|
||||
style: dom.className.includes("tip")
|
||||
? "tip"
|
||||
: dom.className.includes("warning")
|
||||
? "warning"
|
||||
: dom.className.includes("success")
|
||||
? "success"
|
||||
style: dom.className.includes(NoticeTypes.Tip)
|
||||
? NoticeTypes.Tip
|
||||
: dom.className.includes(NoticeTypes.Warning)
|
||||
? NoticeTypes.Warning
|
||||
: dom.className.includes(NoticeTypes.Success)
|
||||
? NoticeTypes.Success
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
@@ -60,10 +68,10 @@ export default class Notice extends Node {
|
||||
tag: "div.alert.theme-admonition",
|
||||
preserveWhitespace: "full",
|
||||
getAttrs: (dom: HTMLDivElement) => ({
|
||||
style: dom.className.includes("warning")
|
||||
? "warning"
|
||||
: dom.className.includes("success")
|
||||
? "success"
|
||||
style: dom.className.includes(NoticeTypes.Warning)
|
||||
? NoticeTypes.Warning
|
||||
: dom.className.includes(NoticeTypes.Success)
|
||||
? NoticeTypes.Success
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
@@ -73,11 +81,11 @@ export default class Notice extends Node {
|
||||
preserveWhitespace: "full",
|
||||
getAttrs: (dom: HTMLDivElement) => ({
|
||||
style: dom.className.includes("confluence-information-macro-tip")
|
||||
? "success"
|
||||
? NoticeTypes.Success
|
||||
: dom.className.includes("confluence-information-macro-note")
|
||||
? "tip"
|
||||
? NoticeTypes.Tip
|
||||
: dom.className.includes("confluence-information-macro-warning")
|
||||
? "warning"
|
||||
? NoticeTypes.Warning
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
@@ -87,11 +95,11 @@ export default class Notice extends Node {
|
||||
if (typeof document !== "undefined") {
|
||||
let component;
|
||||
|
||||
if (node.attrs.style === "tip") {
|
||||
if (node.attrs.style === NoticeTypes.Tip) {
|
||||
component = <StarredIcon />;
|
||||
} else if (node.attrs.style === "warning") {
|
||||
} else if (node.attrs.style === NoticeTypes.Warning) {
|
||||
component = <WarningIcon />;
|
||||
} else if (node.attrs.style === "success") {
|
||||
} else if (node.attrs.style === NoticeTypes.Success) {
|
||||
component = <DoneIcon />;
|
||||
} else {
|
||||
component = <InfoIcon />;
|
||||
@@ -113,26 +121,40 @@ export default class Notice extends Node {
|
||||
}
|
||||
|
||||
commands({ type }: { type: NodeType }) {
|
||||
return (attrs: Record<string, Primitive>) => toggleWrap(type, attrs);
|
||||
return {
|
||||
container_notice: (attrs: Record<string, Primitive>) =>
|
||||
toggleWrap(type, attrs),
|
||||
info: (): Command => (state, dispatch) =>
|
||||
this.handleStyleChange(state, dispatch, NoticeTypes.Info),
|
||||
warning: (): Command => (state, dispatch) =>
|
||||
this.handleStyleChange(state, dispatch, NoticeTypes.Warning),
|
||||
success: (): Command => (state, dispatch) =>
|
||||
this.handleStyleChange(state, dispatch, NoticeTypes.Success),
|
||||
tip: (): Command => (state, dispatch) =>
|
||||
this.handleStyleChange(state, dispatch, NoticeTypes.Tip),
|
||||
};
|
||||
}
|
||||
|
||||
handleStyleChange = (event: InputEvent) => {
|
||||
const { view } = this.editor;
|
||||
const { tr } = view.state;
|
||||
const element = event.target;
|
||||
if (!(element instanceof HTMLSelectElement)) {
|
||||
return;
|
||||
}
|
||||
handleStyleChange = (
|
||||
state: EditorState,
|
||||
dispatch: ((tr: Transaction) => void) | undefined,
|
||||
style: NoticeTypes
|
||||
): boolean => {
|
||||
const { tr, selection } = state;
|
||||
const { $from } = selection;
|
||||
const node = $from.node(-1);
|
||||
|
||||
const { top, left } = element.getBoundingClientRect();
|
||||
const result = view.posAtCoords({ top, left });
|
||||
|
||||
if (result) {
|
||||
const transaction = tr.setNodeMarkup(result.inside, undefined, {
|
||||
style: element.value,
|
||||
});
|
||||
view.dispatch(transaction);
|
||||
if (node?.type.name === this.name) {
|
||||
if (dispatch) {
|
||||
const transaction = tr.setNodeMarkup($from.before(-1), undefined, {
|
||||
...node.attrs,
|
||||
style,
|
||||
});
|
||||
dispatch(transaction);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
inputRules({ type }: { type: NodeType }) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { isNodeActive } from "./isNodeActive";
|
||||
|
||||
/**
|
||||
* Returns true if the selection is inside a notice block.
|
||||
*
|
||||
* @param state The editor state.
|
||||
* @returns True if the selection is inside a notice block.
|
||||
*/
|
||||
export function isInNotice(state: EditorState): boolean {
|
||||
const { nodes } = state.schema;
|
||||
return nodes.container_notice && isNodeActive(nodes.container_notice)(state);
|
||||
}
|
||||
@@ -6,6 +6,8 @@ export class EditorStyleHelper {
|
||||
|
||||
static readonly imageHandle = "image-handle";
|
||||
|
||||
static readonly imageCaption = "caption";
|
||||
|
||||
// Comments
|
||||
|
||||
static readonly comment = "comment-marker";
|
||||
|
||||
@@ -794,7 +794,7 @@
|
||||
"Sorry, it looks like that sign-in link is no longer valid, please try requesting another.": "Sorry, it looks like that sign-in link is no longer valid, please try requesting another.",
|
||||
"Your account has been suspended. To re-activate your account, please contact a workspace admin.": "Your account has been suspended. To re-activate your account, please contact a workspace admin.",
|
||||
"This workspace has been suspended. Please contact support to restore access.": "This workspace has been suspended. Please contact support to restore access.",
|
||||
"Authentication failed – this login method was disabled by a team admin.": "Authentication failed – this login method was disabled by a team admin.",
|
||||
"Authentication failed – this login method was disabled by a workspace admin.": "Authentication failed – this login method was disabled by a workspace admin.",
|
||||
"The workspace you are trying to join requires an invite before you can create an account.<1></1>Please request an invite from your workspace admin and try again.": "The workspace you are trying to join requires an invite before you can create an account.<1></1>Please request an invite from your workspace admin and try again.",
|
||||
"Sorry, an unknown error occurred.": "Sorry, an unknown error occurred.",
|
||||
"Login": "Login",
|
||||
|
||||
@@ -105,35 +105,35 @@
|
||||
"@smithy/util-utf8" "^2.0.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/client-s3@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.749.0.tgz#48d486b8baa87f20f2d3aceba82f947b3c95eef9"
|
||||
integrity sha512-Xi+DaBeYRIa2d+1QTAlBdbRIc9j3+H+H5aMbxIrlyIYE2NOz+4fhIgpUYuF4ln63p9Fby5Wh36DhHJYyN4uE0w==
|
||||
"@aws-sdk/client-s3@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.750.0.tgz#54bbbb930bcc275c9c928d2eb4590c3ee2030d52"
|
||||
integrity sha512-S9G9noCeBxchoMVkHYrRi1A1xW/VOTP2W7X34lP+Y7Wpl32yMA7IJo0fAGAuTc0q1Nu6/pXDm+oDG7rhTCA1tg==
|
||||
dependencies:
|
||||
"@aws-crypto/sha1-browser" "5.2.0"
|
||||
"@aws-crypto/sha256-browser" "5.2.0"
|
||||
"@aws-crypto/sha256-js" "5.2.0"
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/credential-provider-node" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/credential-provider-node" "3.750.0"
|
||||
"@aws-sdk/middleware-bucket-endpoint" "3.734.0"
|
||||
"@aws-sdk/middleware-expect-continue" "3.734.0"
|
||||
"@aws-sdk/middleware-flexible-checksums" "3.749.0"
|
||||
"@aws-sdk/middleware-flexible-checksums" "3.750.0"
|
||||
"@aws-sdk/middleware-host-header" "3.734.0"
|
||||
"@aws-sdk/middleware-location-constraint" "3.734.0"
|
||||
"@aws-sdk/middleware-logger" "3.734.0"
|
||||
"@aws-sdk/middleware-recursion-detection" "3.734.0"
|
||||
"@aws-sdk/middleware-sdk-s3" "3.749.0"
|
||||
"@aws-sdk/middleware-sdk-s3" "3.750.0"
|
||||
"@aws-sdk/middleware-ssec" "3.734.0"
|
||||
"@aws-sdk/middleware-user-agent" "3.749.0"
|
||||
"@aws-sdk/middleware-user-agent" "3.750.0"
|
||||
"@aws-sdk/region-config-resolver" "3.734.0"
|
||||
"@aws-sdk/signature-v4-multi-region" "3.749.0"
|
||||
"@aws-sdk/signature-v4-multi-region" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@aws-sdk/util-endpoints" "3.743.0"
|
||||
"@aws-sdk/util-user-agent-browser" "3.734.0"
|
||||
"@aws-sdk/util-user-agent-node" "3.749.0"
|
||||
"@aws-sdk/util-user-agent-node" "3.750.0"
|
||||
"@aws-sdk/xml-builder" "3.734.0"
|
||||
"@smithy/config-resolver" "^4.0.1"
|
||||
"@smithy/core" "^3.1.3"
|
||||
"@smithy/core" "^3.1.4"
|
||||
"@smithy/eventstream-serde-browser" "^4.0.1"
|
||||
"@smithy/eventstream-serde-config-resolver" "^4.0.1"
|
||||
"@smithy/eventstream-serde-node" "^4.0.1"
|
||||
@@ -144,129 +144,129 @@
|
||||
"@smithy/invalid-dependency" "^4.0.1"
|
||||
"@smithy/md5-js" "^4.0.1"
|
||||
"@smithy/middleware-content-length" "^4.0.1"
|
||||
"@smithy/middleware-endpoint" "^4.0.4"
|
||||
"@smithy/middleware-retry" "^4.0.5"
|
||||
"@smithy/middleware-endpoint" "^4.0.5"
|
||||
"@smithy/middleware-retry" "^4.0.6"
|
||||
"@smithy/middleware-serde" "^4.0.2"
|
||||
"@smithy/middleware-stack" "^4.0.1"
|
||||
"@smithy/node-config-provider" "^4.0.1"
|
||||
"@smithy/node-http-handler" "^4.0.2"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/url-parser" "^4.0.1"
|
||||
"@smithy/util-base64" "^4.0.0"
|
||||
"@smithy/util-body-length-browser" "^4.0.0"
|
||||
"@smithy/util-body-length-node" "^4.0.0"
|
||||
"@smithy/util-defaults-mode-browser" "^4.0.5"
|
||||
"@smithy/util-defaults-mode-node" "^4.0.5"
|
||||
"@smithy/util-defaults-mode-browser" "^4.0.6"
|
||||
"@smithy/util-defaults-mode-node" "^4.0.6"
|
||||
"@smithy/util-endpoints" "^3.0.1"
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
"@smithy/util-retry" "^4.0.1"
|
||||
"@smithy/util-stream" "^4.1.0"
|
||||
"@smithy/util-stream" "^4.1.1"
|
||||
"@smithy/util-utf8" "^4.0.0"
|
||||
"@smithy/util-waiter" "^4.0.2"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/client-sso@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.749.0.tgz#533ca94a89409e73c677cbac4f57e6e98a4c92d0"
|
||||
integrity sha512-ecmuDu8EPya1LDpGRtpgN7C9PHayDh8EaW37ZBKhuxA7cg099yvTFqsGngwRXbhNjKJ4oVa9OUe0EDnu60atYA==
|
||||
"@aws-sdk/client-sso@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.750.0.tgz#b45864b78057504f823b2927535ac60b7c5583b2"
|
||||
integrity sha512-y0Rx6pTQXw0E61CaptpZF65qNggjqOgymq/RYZU5vWba5DGQ+iqGt8Yq8s+jfBoBBNXshxq8l8Dl5Uq/JTY1wg==
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser" "5.2.0"
|
||||
"@aws-crypto/sha256-js" "5.2.0"
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/middleware-host-header" "3.734.0"
|
||||
"@aws-sdk/middleware-logger" "3.734.0"
|
||||
"@aws-sdk/middleware-recursion-detection" "3.734.0"
|
||||
"@aws-sdk/middleware-user-agent" "3.749.0"
|
||||
"@aws-sdk/middleware-user-agent" "3.750.0"
|
||||
"@aws-sdk/region-config-resolver" "3.734.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@aws-sdk/util-endpoints" "3.743.0"
|
||||
"@aws-sdk/util-user-agent-browser" "3.734.0"
|
||||
"@aws-sdk/util-user-agent-node" "3.749.0"
|
||||
"@aws-sdk/util-user-agent-node" "3.750.0"
|
||||
"@smithy/config-resolver" "^4.0.1"
|
||||
"@smithy/core" "^3.1.3"
|
||||
"@smithy/core" "^3.1.4"
|
||||
"@smithy/fetch-http-handler" "^5.0.1"
|
||||
"@smithy/hash-node" "^4.0.1"
|
||||
"@smithy/invalid-dependency" "^4.0.1"
|
||||
"@smithy/middleware-content-length" "^4.0.1"
|
||||
"@smithy/middleware-endpoint" "^4.0.4"
|
||||
"@smithy/middleware-retry" "^4.0.5"
|
||||
"@smithy/middleware-endpoint" "^4.0.5"
|
||||
"@smithy/middleware-retry" "^4.0.6"
|
||||
"@smithy/middleware-serde" "^4.0.2"
|
||||
"@smithy/middleware-stack" "^4.0.1"
|
||||
"@smithy/node-config-provider" "^4.0.1"
|
||||
"@smithy/node-http-handler" "^4.0.2"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/url-parser" "^4.0.1"
|
||||
"@smithy/util-base64" "^4.0.0"
|
||||
"@smithy/util-body-length-browser" "^4.0.0"
|
||||
"@smithy/util-body-length-node" "^4.0.0"
|
||||
"@smithy/util-defaults-mode-browser" "^4.0.5"
|
||||
"@smithy/util-defaults-mode-node" "^4.0.5"
|
||||
"@smithy/util-defaults-mode-browser" "^4.0.6"
|
||||
"@smithy/util-defaults-mode-node" "^4.0.6"
|
||||
"@smithy/util-endpoints" "^3.0.1"
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
"@smithy/util-retry" "^4.0.1"
|
||||
"@smithy/util-utf8" "^4.0.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/core@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.749.0.tgz#27bc3403cdbe17913b00b73f4de7a05f85dad408"
|
||||
integrity sha512-w5Jj573+XKwrDNZUjUJDXL5upx+RCw64TLq3Zk8FVg9MsgkzAPorQ9qmzffi6os+PWngd3pFmD8q5y+Y35LpFQ==
|
||||
"@aws-sdk/core@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.750.0.tgz#087ce3dd86e2e94e9a2828506a82223ae9f364ff"
|
||||
integrity sha512-bZ5K7N5L4+Pa2epbVpUQqd1XLG2uU8BGs/Sd+2nbgTf+lNQJyIxAg/Qsrjz9MzmY8zzQIeRQEkNmR6yVAfCmmQ==
|
||||
dependencies:
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/core" "^3.1.3"
|
||||
"@smithy/core" "^3.1.4"
|
||||
"@smithy/node-config-provider" "^4.0.1"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/signature-v4" "^5.0.1"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
fast-xml-parser "4.4.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/credential-provider-env@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.749.0.tgz#cc810d9b1f949f1bb8c37af1165cf515862d7003"
|
||||
integrity sha512-bhB1ds5QzcSfmCTbjVessXy8xHJROota6wOhFtBsL1aZRQyNN2a9h2QS6xkxjmqVE3yHBsPz+OiSOeLn0kxm7Q==
|
||||
"@aws-sdk/credential-provider-env@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.750.0.tgz#adfa47d24bb9ea0d87993c6998b1ddc38fd3444f"
|
||||
integrity sha512-In6bsG0p/P31HcH4DBRKBbcDS/3SHvEPjfXV8ODPWZO/l3/p7IRoYBdQ07C9R+VMZU2D0+/Sc/DWK/TUNDk1+Q==
|
||||
dependencies:
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/credential-provider-http@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.749.0.tgz#d5efad8fce2a10e7d8b861075f2878e78f86c795"
|
||||
integrity sha512-enFGT8uvETbE6+4bDA2aTOrA/83GrIVPpg2g2r7MwJb36jreXA3KDXaP/5jQsxyIZW70cnYNl/Cawdd4ZXs7CQ==
|
||||
"@aws-sdk/credential-provider-http@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.750.0.tgz#2879dde158dfccb21165aab95c90b7286bcdd5cf"
|
||||
integrity sha512-wFB9qqfa20AB0dElsQz5ZlZT5o+a+XzpEpmg0erylmGYqEOvh8NQWfDUVpRmQuGq9VbvW/8cIbxPoNqEbPtuWQ==
|
||||
dependencies:
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/fetch-http-handler" "^5.0.1"
|
||||
"@smithy/node-http-handler" "^4.0.2"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/util-stream" "^4.1.0"
|
||||
"@smithy/util-stream" "^4.1.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/credential-provider-ini@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.749.0.tgz#36db051f7a00236fef64fcb8705ed3c17eeaf480"
|
||||
integrity sha512-OB4AGK61lQdoW2mTmaMBw8L+eBo7wF3YJZXwqFI7M2cQe9WtfuKGIxbYWMBMzoLvEtmsbzeppoZZUezooaIclg==
|
||||
"@aws-sdk/credential-provider-ini@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.750.0.tgz#5079c5732ac886d72f357c0da532749d0c7487fd"
|
||||
integrity sha512-2YIZmyEr5RUd3uxXpxOLD9G67Bibm4I/65M6vKFP17jVMUT+R1nL7mKqmhEVO2p+BoeV+bwMyJ/jpTYG368PCg==
|
||||
dependencies:
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/credential-provider-env" "3.749.0"
|
||||
"@aws-sdk/credential-provider-http" "3.749.0"
|
||||
"@aws-sdk/credential-provider-process" "3.749.0"
|
||||
"@aws-sdk/credential-provider-sso" "3.749.0"
|
||||
"@aws-sdk/credential-provider-web-identity" "3.749.0"
|
||||
"@aws-sdk/nested-clients" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/credential-provider-env" "3.750.0"
|
||||
"@aws-sdk/credential-provider-http" "3.750.0"
|
||||
"@aws-sdk/credential-provider-process" "3.750.0"
|
||||
"@aws-sdk/credential-provider-sso" "3.750.0"
|
||||
"@aws-sdk/credential-provider-web-identity" "3.750.0"
|
||||
"@aws-sdk/nested-clients" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/credential-provider-imds" "^4.0.1"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
@@ -274,17 +274,17 @@
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/credential-provider-node@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.749.0.tgz#b21b241fede2125f99da2e6d652368a3f216a36b"
|
||||
integrity sha512-1QGstZmGmgmY0rLSTAURlBJdR4s2PRYiZh6dS4HkzzQu7xVDWoCMD+2F7dolsNA8ChTNx2OvBW80n3O9QPICCg==
|
||||
"@aws-sdk/credential-provider-node@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.750.0.tgz#0eb117a287dac34040fb8cdf65d7d239b703b2ff"
|
||||
integrity sha512-THWHHAceLwsOiowPEmKyhWVDlEUxH07GHSw5AQFDvNQtGKOQl0HSIFO1mKObT2Q2Vqzji9Bq8H58SO5BFtNPRw==
|
||||
dependencies:
|
||||
"@aws-sdk/credential-provider-env" "3.749.0"
|
||||
"@aws-sdk/credential-provider-http" "3.749.0"
|
||||
"@aws-sdk/credential-provider-ini" "3.749.0"
|
||||
"@aws-sdk/credential-provider-process" "3.749.0"
|
||||
"@aws-sdk/credential-provider-sso" "3.749.0"
|
||||
"@aws-sdk/credential-provider-web-identity" "3.749.0"
|
||||
"@aws-sdk/credential-provider-env" "3.750.0"
|
||||
"@aws-sdk/credential-provider-http" "3.750.0"
|
||||
"@aws-sdk/credential-provider-ini" "3.750.0"
|
||||
"@aws-sdk/credential-provider-process" "3.750.0"
|
||||
"@aws-sdk/credential-provider-sso" "3.750.0"
|
||||
"@aws-sdk/credential-provider-web-identity" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/credential-provider-imds" "^4.0.1"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
@@ -292,61 +292,61 @@
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/credential-provider-process@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.749.0.tgz#ee83034d51779394217863616f75f888fdf8c459"
|
||||
integrity sha512-C/cgg/AhRabybZRY9mJ6KDz8uqfasWKuFIFGzTpeb/MIDIL53ZqP61CspiQJTRvC4zlFGqvm43XefphfrBGGlQ==
|
||||
"@aws-sdk/credential-provider-process@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.750.0.tgz#04ecf72fb30dbe6b360ea9371446f13183701b5e"
|
||||
integrity sha512-Q78SCH1n0m7tpu36sJwfrUSxI8l611OyysjQeMiIOliVfZICEoHcLHLcLkiR+tnIpZ3rk7d2EQ6R1jwlXnalMQ==
|
||||
dependencies:
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
"@smithy/shared-ini-file-loader" "^4.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/credential-provider-sso@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.749.0.tgz#0d0a4a8c0db630efab8aeedaa994e4a939b5ab9d"
|
||||
integrity sha512-bQNgWcYk10fYOvFwcLskYYVNLO3KMgmV1ip9ieapJb9JDg6bSBaXNjIDhdpK4biTOfrV+adtDO5EU93ogpmAWA==
|
||||
"@aws-sdk/credential-provider-sso@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.750.0.tgz#a96afc83cfd63a957c5b9ed7913d60830c5b1f57"
|
||||
integrity sha512-FGYrDjXN/FOQVi/t8fHSv8zCk+NEvtFnuc4cZUj5OIbM4vrfFc5VaPyn41Uza3iv6Qq9rZg0QOwWnqK8lNrqUw==
|
||||
dependencies:
|
||||
"@aws-sdk/client-sso" "3.749.0"
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/token-providers" "3.749.0"
|
||||
"@aws-sdk/client-sso" "3.750.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/token-providers" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
"@smithy/shared-ini-file-loader" "^4.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/credential-provider-web-identity@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.749.0.tgz#1869159d76ae4a7604391fb98efe3073111617fb"
|
||||
integrity sha512-jzHk6i4G4dnXL+L+qeILguDCiIhA1rNvJzB5lTts4R8OdmNkG12bGbYL8bL4O1b5qCimlo7HS0IZIby0pS7rcg==
|
||||
"@aws-sdk/credential-provider-web-identity@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.750.0.tgz#2ab785cced1326f253c324d6ec10f74a02506c00"
|
||||
integrity sha512-Nz8zs3YJ+GOTSrq+LyzbbC1Ffpt7pK38gcOyNZv76pP5MswKTUKNYBJehqwa+i7FcFQHsCk3TdhR8MT1ZR23uA==
|
||||
dependencies:
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/nested-clients" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/nested-clients" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/crt-loader@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/crt-loader/-/crt-loader-3.749.0.tgz#8f6cb04b4dd026f3b7cbd17566b178043efff0e3"
|
||||
integrity sha512-HLakuc/wWQbeG2VfHS6UHflV0HbTDnLQrooOu0RgQaGmmXxcyx26lPGMtxsAylSQ2+Rw1p55ViIal27pclf0gQ==
|
||||
"@aws-sdk/crt-loader@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/crt-loader/-/crt-loader-3.750.0.tgz#360ce8be1349793ac44e83630fa94ba5bd85f49e"
|
||||
integrity sha512-L92XdDaDKQi1ldocQnBjUpu5o47GMdGaZ2TTUDOnOZyIggb15lNsfknJaLz3Q74rx09qkY1OYe8zsTKBB7lMmQ==
|
||||
dependencies:
|
||||
"@aws-sdk/util-user-agent-node" "3.749.0"
|
||||
"@aws-sdk/util-user-agent-node" "3.750.0"
|
||||
aws-crt "^1.24.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/lib-storage@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.749.0.tgz#029a2dc2692aff4522f832fc06148bfa7323fb36"
|
||||
integrity sha512-eYvsRWzukHeZ3k/hFt66or33JJg5zAm/jtstbxZaKRq/H1VfWkKh1QyQHu0UiOXZnjEkTFNJujwGME8JmAWpRg==
|
||||
"@aws-sdk/lib-storage@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.750.0.tgz#3e3eb685ae3b302d141fd82ceb21871fc78380f8"
|
||||
integrity sha512-2IHbhUzlKtiAZVW7S5jkJfVDj5pJC9TldHGJLYRAR9GReG9HhK6mI7kLnYE9jf3GchWfe/Bn3wqSwh3BIf0OZQ==
|
||||
dependencies:
|
||||
"@smithy/abort-controller" "^4.0.1"
|
||||
"@smithy/middleware-endpoint" "^4.0.4"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/middleware-endpoint" "^4.0.5"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
buffer "5.6.0"
|
||||
events "3.3.0"
|
||||
stream-browserify "3.0.0"
|
||||
@@ -375,22 +375,22 @@
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/middleware-flexible-checksums@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.749.0.tgz#264fe1621d23e1c8c4ff4b5bc1c7ad09dff2d979"
|
||||
integrity sha512-LebOuHbO5BPeke0EB7I7aU7EK807XEyHeCQ3El4CztkGr7cY37PiYP5E0bsSs/4dlK534en/oD0dBA82gZ3xcg==
|
||||
"@aws-sdk/middleware-flexible-checksums@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.750.0.tgz#7ba7defd7b90b22b0aacedcc05072cb7fc50532c"
|
||||
integrity sha512-ach0d2buDnX2TUausUbiXXFWFo3IegLnCrA+Rw8I9AYVpLN9lTaRwAYJwYC6zEuW9Golff8MwkYsp/OaC5tKMw==
|
||||
dependencies:
|
||||
"@aws-crypto/crc32" "5.2.0"
|
||||
"@aws-crypto/crc32c" "5.2.0"
|
||||
"@aws-crypto/util" "5.2.0"
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/is-array-buffer" "^4.0.0"
|
||||
"@smithy/node-config-provider" "^4.0.1"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
"@smithy/util-stream" "^4.1.0"
|
||||
"@smithy/util-stream" "^4.1.1"
|
||||
"@smithy/util-utf8" "^4.0.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
@@ -432,23 +432,23 @@
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/middleware-sdk-s3@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.749.0.tgz#46da5e9cbf6dc5318f94e4383e7de7f859780e69"
|
||||
integrity sha512-A7OaNkSFzI2vCRarSKEzoVejAyiLY1M1Z6PR3TE2woAPUUAgRv5UlFOXSymBx1Ya3HUQzWky7mnpnKAFyRGzSg==
|
||||
"@aws-sdk/middleware-sdk-s3@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.750.0.tgz#35f372310b3f2150e3ea8aee292e1b98fb40c1f0"
|
||||
integrity sha512-3H6Z46cmAQCHQ0z8mm7/cftY5ifiLfCjbObrbyyp2fhQs9zk6gCKzIX8Zjhw0RMd93FZi3ebRuKJWmMglf4Itw==
|
||||
dependencies:
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@aws-sdk/util-arn-parser" "3.723.0"
|
||||
"@smithy/core" "^3.1.3"
|
||||
"@smithy/core" "^3.1.4"
|
||||
"@smithy/node-config-provider" "^4.0.1"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/signature-v4" "^5.0.1"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/util-config-provider" "^4.0.0"
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
"@smithy/util-stream" "^4.1.0"
|
||||
"@smithy/util-stream" "^4.1.1"
|
||||
"@smithy/util-utf8" "^4.0.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
@@ -461,57 +461,57 @@
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/middleware-user-agent@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.749.0.tgz#05960c434c4129ccbcb45d2fd369c8d6c135738d"
|
||||
integrity sha512-dNRkZtiM8OoGb/h2Fgrgvty9ltYEubzsD5FH+VN14RrluertLQMmqHrgvq7JoAXFf7MJy+uwhRu4V6pf1sZR/w==
|
||||
"@aws-sdk/middleware-user-agent@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.750.0.tgz#cea1d9ece724acba1369d7b4a1efa16192cbf658"
|
||||
integrity sha512-YYcslDsP5+2NZoN3UwuhZGkhAHPSli7HlJHBafBrvjGV/I9f8FuOO1d1ebxGdEP4HyRXUGyh+7Ur4q+Psk0ryw==
|
||||
dependencies:
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@aws-sdk/util-endpoints" "3.743.0"
|
||||
"@smithy/core" "^3.1.3"
|
||||
"@smithy/core" "^3.1.4"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/nested-clients@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.749.0.tgz#61006646246d916822fc82c6f7d637358bae83e3"
|
||||
integrity sha512-5L8OuVojcVQAZw+iVXTaw88AZTlMw8fD51lB6spzbZdNLl6dd5Iz1JVJAOUl2mTAZXRiN5Q9VECwY1yMgWweAw==
|
||||
"@aws-sdk/nested-clients@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.750.0.tgz#facfef441ad78db2f544be0eb3f1f7adb16846c1"
|
||||
integrity sha512-OH68BRF0rt9nDloq4zsfeHI0G21lj11a66qosaljtEP66PWm7tQ06feKbFkXHT5E1K3QhJW3nVyK8v2fEBY5fg==
|
||||
dependencies:
|
||||
"@aws-crypto/sha256-browser" "5.2.0"
|
||||
"@aws-crypto/sha256-js" "5.2.0"
|
||||
"@aws-sdk/core" "3.749.0"
|
||||
"@aws-sdk/core" "3.750.0"
|
||||
"@aws-sdk/middleware-host-header" "3.734.0"
|
||||
"@aws-sdk/middleware-logger" "3.734.0"
|
||||
"@aws-sdk/middleware-recursion-detection" "3.734.0"
|
||||
"@aws-sdk/middleware-user-agent" "3.749.0"
|
||||
"@aws-sdk/middleware-user-agent" "3.750.0"
|
||||
"@aws-sdk/region-config-resolver" "3.734.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@aws-sdk/util-endpoints" "3.743.0"
|
||||
"@aws-sdk/util-user-agent-browser" "3.734.0"
|
||||
"@aws-sdk/util-user-agent-node" "3.749.0"
|
||||
"@aws-sdk/util-user-agent-node" "3.750.0"
|
||||
"@smithy/config-resolver" "^4.0.1"
|
||||
"@smithy/core" "^3.1.3"
|
||||
"@smithy/core" "^3.1.4"
|
||||
"@smithy/fetch-http-handler" "^5.0.1"
|
||||
"@smithy/hash-node" "^4.0.1"
|
||||
"@smithy/invalid-dependency" "^4.0.1"
|
||||
"@smithy/middleware-content-length" "^4.0.1"
|
||||
"@smithy/middleware-endpoint" "^4.0.4"
|
||||
"@smithy/middleware-retry" "^4.0.5"
|
||||
"@smithy/middleware-endpoint" "^4.0.5"
|
||||
"@smithy/middleware-retry" "^4.0.6"
|
||||
"@smithy/middleware-serde" "^4.0.2"
|
||||
"@smithy/middleware-stack" "^4.0.1"
|
||||
"@smithy/node-config-provider" "^4.0.1"
|
||||
"@smithy/node-http-handler" "^4.0.2"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/url-parser" "^4.0.1"
|
||||
"@smithy/util-base64" "^4.0.0"
|
||||
"@smithy/util-body-length-browser" "^4.0.0"
|
||||
"@smithy/util-body-length-node" "^4.0.0"
|
||||
"@smithy/util-defaults-mode-browser" "^4.0.5"
|
||||
"@smithy/util-defaults-mode-node" "^4.0.5"
|
||||
"@smithy/util-defaults-mode-browser" "^4.0.6"
|
||||
"@smithy/util-defaults-mode-node" "^4.0.6"
|
||||
"@smithy/util-endpoints" "^3.0.1"
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
"@smithy/util-retry" "^4.0.1"
|
||||
@@ -530,42 +530,42 @@
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/s3-presigned-post@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.749.0.tgz#c752effdd6012cea25661b844a12189a65233357"
|
||||
integrity sha512-CpIP8PhXLt/ooEqhaRsxF+gcOB83p+TkAqdqgxp2SJhsIvHqEwjjhrk31IZe1IXDMjpgbt6VvZ2lGJLAUBytNw==
|
||||
"@aws-sdk/s3-presigned-post@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.750.0.tgz#9478be88ed4fe4577090d214784c10345a8c55d3"
|
||||
integrity sha512-pKCc/ZMj4rSnMwRyRiMfmTIPj5ODc0VM11+Lkywl+rEWru9kH05fww6TYximZuiBcixbaMVkQ4ePXj6DNsRB4w==
|
||||
dependencies:
|
||||
"@aws-sdk/client-s3" "3.749.0"
|
||||
"@aws-sdk/client-s3" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@aws-sdk/util-format-url" "3.734.0"
|
||||
"@smithy/middleware-endpoint" "^4.0.4"
|
||||
"@smithy/middleware-endpoint" "^4.0.5"
|
||||
"@smithy/signature-v4" "^5.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
"@smithy/util-hex-encoding" "^4.0.0"
|
||||
"@smithy/util-utf8" "^4.0.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/s3-request-presigner@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.749.0.tgz#f5ff8e2bd2372ee40f2008c5cb5cf705547381f9"
|
||||
integrity sha512-ZUBBJvKGXNuLTsHymBjGsr6WoouRVojURouAc8ueePWxTCt0u/mOOP0KRFVfwmM166YMr+QWr/ym4bJ8aHLAkA==
|
||||
"@aws-sdk/s3-request-presigner@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.750.0.tgz#7c014d6d30a4a8820ad3dcd49a411ad6d637939e"
|
||||
integrity sha512-G4GNngNQlh9EyJZj2WKOOikX0Fev1WSxTV/XJugaHlpnVriebvi3GzolrgxUpRrcGpFGWjmAxLi/gYxTUla1ow==
|
||||
dependencies:
|
||||
"@aws-sdk/signature-v4-multi-region" "3.749.0"
|
||||
"@aws-sdk/signature-v4-multi-region" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@aws-sdk/util-format-url" "3.734.0"
|
||||
"@smithy/middleware-endpoint" "^4.0.4"
|
||||
"@smithy/middleware-endpoint" "^4.0.5"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/smithy-client" "^4.1.4"
|
||||
"@smithy/smithy-client" "^4.1.5"
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/signature-v4-crt@^3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-crt/-/signature-v4-crt-3.749.0.tgz#0f70b8c2e648377eff6cf6fb5e31852e419b5578"
|
||||
integrity sha512-h1cOkh7q8Q26Fx4CrxZEGORtZM0J2J33zTOQ97Rl/CL0Wyp9tGthC7lb7205iJcaZXQ40YL2qzqPdUzlAldCtg==
|
||||
"@aws-sdk/signature-v4-crt@^3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-crt/-/signature-v4-crt-3.750.0.tgz#c3e7213285375d684ba4138f637ecdc03af37cc5"
|
||||
integrity sha512-8GET9edlujvrCHi4C6mnlViiQmYsena6HiSAHIeLh6vu7WgCDI1Gpgo2UI7Z93TRjyhp/c95ifKVjJDeauPPrw==
|
||||
dependencies:
|
||||
"@aws-sdk/crt-loader" "3.749.0"
|
||||
"@aws-sdk/signature-v4-multi-region" "3.749.0"
|
||||
"@aws-sdk/crt-loader" "3.750.0"
|
||||
"@aws-sdk/signature-v4-multi-region" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/querystring-parser" "^4.0.1"
|
||||
"@smithy/signature-v4" "^5.0.1"
|
||||
@@ -573,24 +573,24 @@
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/signature-v4-multi-region@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.749.0.tgz#20d6868de7ce3cb0f16369f19836b6b119a7e1d8"
|
||||
integrity sha512-8ZR3vnJn1Tgg/qpalkyQMUILzZhw6XV1aCDxYMevqHYu6/f0ujvytJjiUd+j92sfxgN781f6bWpt/jVY0jarxg==
|
||||
"@aws-sdk/signature-v4-multi-region@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.750.0.tgz#b948dfc7ab7fbcb97e0df6bdffc03b3f3cecb49a"
|
||||
integrity sha512-RA9hv1Irro/CrdPcOEXKwJ0DJYJwYCsauGEdRXihrRfy8MNSR9E+mD5/Fr5Rxjaq5AHM05DYnN3mg/DU6VwzSw==
|
||||
dependencies:
|
||||
"@aws-sdk/middleware-sdk-s3" "3.749.0"
|
||||
"@aws-sdk/middleware-sdk-s3" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/protocol-http" "^5.0.1"
|
||||
"@smithy/signature-v4" "^5.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/token-providers@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.749.0.tgz#a679c011edca66a3f4fe4ef5a051204c2ddeba9a"
|
||||
integrity sha512-s3ExVWoNZan6U7ljMqjiHq3bOe4EqL+U+cVPwqNxAsMaJpGyCiA8VQFlXNGg0EPAFbz/0DVBcozYht6/vyH3sg==
|
||||
"@aws-sdk/token-providers@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.750.0.tgz#dc72c3d71f224ee5a7df35829547966d2562aba2"
|
||||
integrity sha512-X/KzqZw41iWolwNdc8e3RMcNSMR364viHv78u6AefXOO5eRM40c4/LuST1jDzq35/LpnqRhL7/MuixOetw+sFw==
|
||||
dependencies:
|
||||
"@aws-sdk/nested-clients" "3.749.0"
|
||||
"@aws-sdk/nested-clients" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/property-provider" "^4.0.1"
|
||||
"@smithy/shared-ini-file-loader" "^4.0.1"
|
||||
@@ -649,12 +649,12 @@
|
||||
bowser "^2.11.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@aws-sdk/util-user-agent-node@3.749.0":
|
||||
version "3.749.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.749.0.tgz#4b02afd8ce9170f799bc8fa0a7037c1af9a24d87"
|
||||
integrity sha512-uBzolGGrwvZKhpYlGIy9tw6gRdqVs2zyjjXUiifdgbr3WiQXJe8sE1KhLjzyN/VOPcZB0rY34ybqiKEDOymOeQ==
|
||||
"@aws-sdk/util-user-agent-node@3.750.0":
|
||||
version "3.750.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.750.0.tgz#a12fe898bcab26cf50b31cb70b5fc5e887edce40"
|
||||
integrity sha512-84HJj9G9zbrHX2opLk9eHfDceB+UIHVrmflMzWHpsmo9fDuro/flIBqaVDlE021Osj6qIM0SJJcnL6s23j7JEw==
|
||||
dependencies:
|
||||
"@aws-sdk/middleware-user-agent" "3.749.0"
|
||||
"@aws-sdk/middleware-user-agent" "3.750.0"
|
||||
"@aws-sdk/types" "3.734.0"
|
||||
"@smithy/node-config-provider" "^4.0.1"
|
||||
"@smithy/types" "^4.1.0"
|
||||
@@ -3698,7 +3698,7 @@
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/core@^3.1.3", "@smithy/core@^3.1.4":
|
||||
"@smithy/core@^3.1.4":
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.1.4.tgz#47ce2b1b363ba92be2b47551bdd30969c6435bd2"
|
||||
integrity sha512-wFExFGK+7r2wYriOqe7RRIBNpvxwiS95ih09+GSLRBdoyK/O1uZA7K7pKesj5CBvwJuSBeXwLyR88WwIAY+DGA==
|
||||
@@ -3848,7 +3848,7 @@
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/middleware-endpoint@^4.0.4", "@smithy/middleware-endpoint@^4.0.5":
|
||||
"@smithy/middleware-endpoint@^4.0.5":
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.5.tgz#b3d58c0a44b5dcccb1da34267b6f651bc1ba7642"
|
||||
integrity sha512-cPzGZV7qStHwboFrm6GfrzQE+YDiCzWcTh4+7wKrP/ZQ4gkw+r7qDjV8GjM4N0UYsuUyLfpzLGg5hxsYTU11WA==
|
||||
@@ -3862,7 +3862,7 @@
|
||||
"@smithy/util-middleware" "^4.0.1"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/middleware-retry@^4.0.5":
|
||||
"@smithy/middleware-retry@^4.0.6":
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.0.6.tgz#07f8259dc05835e317aaf37af7e79bae349eabb4"
|
||||
integrity sha512-s8QzuOQnbdvRymD9Gt9c9zMq10wUQAHQ3z72uirrBHCwZcLTrL5iCOuVTMdka2IXOYhQE890WD5t6G24+F+Qcg==
|
||||
@@ -3976,7 +3976,7 @@
|
||||
"@smithy/util-utf8" "^4.0.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/smithy-client@^4.1.4", "@smithy/smithy-client@^4.1.5":
|
||||
"@smithy/smithy-client@^4.1.5":
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-4.1.5.tgz#f4641ad6771f5e4f6de9e573b9deb1787e43ef71"
|
||||
integrity sha512-DMXYoYeL4QkElr216n1yodTFeATbfb4jwYM9gKn71Rw/FNA1/Sm36tkTSCsZEs7mgpG3OINmkxL9vgVFzyGPaw==
|
||||
@@ -4051,7 +4051,7 @@
|
||||
dependencies:
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/util-defaults-mode-browser@^4.0.5":
|
||||
"@smithy/util-defaults-mode-browser@^4.0.6":
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.6.tgz#4ee93d8e32c8211709437aa29c5fe616895e7c51"
|
||||
integrity sha512-N8+VCt+piupH1A7DgSVDNrVHqRLz8r6DvBkpS7EWHiIxsUk4jqGuQLjqC/gnCzmwGkVBdNruHoYAzzaSQ8e80w==
|
||||
@@ -4062,7 +4062,7 @@
|
||||
bowser "^2.11.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/util-defaults-mode-node@^4.0.5":
|
||||
"@smithy/util-defaults-mode-node@^4.0.6":
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.6.tgz#213e5b32549beb48aeccbcf99cf56c97db47e70b"
|
||||
integrity sha512-9zhx1shd1VwSSVvLZB8CM3qQ3RPD3le7A3h/UPuyh/PC7g4OaWDi2xUNzamsVoSmCGtmUBONl56lM2EU6LcH7A==
|
||||
@@ -4108,7 +4108,7 @@
|
||||
"@smithy/types" "^4.1.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@smithy/util-stream@^4.1.0", "@smithy/util-stream@^4.1.1":
|
||||
"@smithy/util-stream@^4.1.1":
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-4.1.1.tgz#d7edec543d65ed335d2fda9aae6a42ee97da4a4e"
|
||||
integrity sha512-+Xvh8nhy0Wjv1y71rBVyV3eJU3356XsFQNI8dEZVNrQju7Eib8G31GWtO+zMa9kTCGd41Mflu+ZKfmQL/o2XzQ==
|
||||
@@ -13068,24 +13068,19 @@ raf@^3.4.1:
|
||||
dependencies:
|
||||
performance-now "^2.1.0"
|
||||
|
||||
randombytes@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
|
||||
integrity "sha1-Z0yZdgkBw8QRJ3GjHlIdw0nMCew= sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg=="
|
||||
|
||||
randombytes@^2.1.0:
|
||||
randombytes@2.1.0, randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
integrity "sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo= sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="
|
||||
dependencies:
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
randomstring@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.3.0.tgz#1bf9d730066899e70aee3285573f84708278683d"
|
||||
integrity sha512-gY7aQ4i1BgwZ8I1Op4YseITAyiDiajeZOPQUbIq9TPGPhUm5FX59izIaOpmKbME1nmnEiABf28d9K2VSii6BBg==
|
||||
randomstring@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/randomstring/-/randomstring-1.3.1.tgz#11a494a9d51b320dd8694a3e0ee7d77b0f8e477c"
|
||||
integrity sha512-lgXZa80MUkjWdE7g2+PZ1xDLzc7/RokXVEQOv5NN2UOTChW1I8A9gha5a9xYBOqgaSoI6uJikDmCU8PyRdArRQ==
|
||||
dependencies:
|
||||
randombytes "2.0.3"
|
||||
randombytes "2.1.0"
|
||||
|
||||
rate-limiter-flexible@^2.4.2:
|
||||
version "2.4.2"
|
||||
@@ -13906,10 +13901,10 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semve
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.5.0, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2, semver@^7.6.3:
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
||||
semver@^7.5.0, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3, semver@^7.7.1:
|
||||
version "7.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
|
||||
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
|
||||
|
||||
sequelize-cli@^6.6.2:
|
||||
version "6.6.2"
|
||||
@@ -15080,10 +15075,10 @@ typescript@^4.2.4:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
|
||||
integrity "sha1-GohZbRz0fVlQehvN+1ud/k1IgjU= sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ=="
|
||||
|
||||
typescript@^5.7.2:
|
||||
version "5.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
|
||||
integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==
|
||||
typescript@^5.7.3:
|
||||
version "5.7.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
|
||||
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
|
||||
|
||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||
version "1.0.6"
|
||||
|
||||
Reference in New Issue
Block a user