Compare commits

..

22 Commits

Author SHA1 Message Date
Tom Moor ecd4e9371f fix: Overflow on math blocks 2025-04-21 16:18:43 -03:00
Tom Moor 2b07f412e2 fix: Image caption is not correctly centered on full-width image (#9013) 2025-04-18 19:31:36 -04:00
Hemachandar 65bb3b11f3 fix: Parse emoji and url only as workspace icon (#9009)
* fix: Parse emoji and url only as workspace icon

* scope emoji regex to transform function
2025-04-18 10:45:17 -04:00
Tom Moor e1e334dd5f fix: Deleted users appear in mention menu before search query (#9003) 2025-04-17 22:57:43 -04:00
codegen-sh[bot] 6e9092bcaf #8962: Remove "Self hosted" integrations page (#9001)
* #8962: Remove "Self hosted" integrations page

* Remove unused BuildingBlocksIcon import

---------

Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom@getoutline.com>
2025-04-17 08:34:08 -04:00
Tom Moor 09a4b76aae fix: Users subscribed to document and collection may be notified twice (#8997)
fix: Create notifications in transaction
2025-04-17 08:08:09 -04:00
Hemachandar 5789d65bf5 Ensure iframely fallback is not executed for connected unfurl integration (#8995)
* Ensure iframely fallback is not executed for connected unfurl integration

* tsc
2025-04-16 18:22:51 -04:00
Tom Moor 03a0f54236 fix: Cannot drag-select text while editing document title in sidebar (#8991)
* fix: Cannot drag-select text while editing document title in sidebar

* Clarify isEditing parameter description
2025-04-16 18:22:43 -04:00
Tom Moor 1e7244c737 fix: Infinite loop loading page with vbnet code embed (#8987) 2025-04-16 01:51:57 +00:00
Tom Moor 96c41ce823 chore: Disable bundle-size job on forks (#8986) 2025-04-16 01:31:25 +00:00
Tom Moor 0702570b0d fix: Small modal overflow scrolling behavior (#8981)
closes #8966
2025-04-15 06:35:33 -07:00
Tom Moor 4b209a7913 fix: Full-width image control should act as toggle (#8980)
closes #8954
2025-04-15 12:22:14 +00:00
Tom Moor 6393bd02f4 fix: Cannot select divider, closes #8964 (#8979) 2025-04-15 12:13:45 +00:00
dependabot[bot] 1776aad833 chore(deps): bump the aws group with 5 updates (#8968)
Bumps the aws group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.782.0` | `3.787.0` |
| [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) | `3.782.0` | `3.787.0` |
| [@aws-sdk/s3-presigned-post](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-presigned-post) | `3.782.0` | `3.787.0` |
| [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-request-presigner) | `3.782.0` | `3.787.0` |
| [@aws-sdk/signature-v4-crt](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/signature-v4-crt) | `3.782.0` | `3.787.0` |


Updates `@aws-sdk/client-s3` from 3.782.0 to 3.787.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.787.0/clients/client-s3)

Updates `@aws-sdk/lib-storage` from 3.782.0 to 3.787.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.787.0/lib/lib-storage)

Updates `@aws-sdk/s3-presigned-post` from 3.782.0 to 3.787.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-presigned-post/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.787.0/packages/s3-presigned-post)

Updates `@aws-sdk/s3-request-presigner` from 3.782.0 to 3.787.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-request-presigner/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.787.0/packages/s3-request-presigner)

Updates `@aws-sdk/signature-v4-crt` from 3.782.0 to 3.787.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/signature-v4-crt/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.787.0/packages/signature-v4-crt)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.787.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/lib-storage"
  dependency-version: 3.787.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/s3-presigned-post"
  dependency-version: 3.787.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/s3-request-presigner"
  dependency-version: 3.787.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
- dependency-name: "@aws-sdk/signature-v4-crt"
  dependency-version: 3.787.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 22:40:15 -04:00
dependabot[bot] 0c6b37cb60 chore(deps): bump react-virtualized-auto-sizer from 1.0.25 to 1.0.26 (#8969)
Bumps [react-virtualized-auto-sizer](https://github.com/bvaughn/react-virtualized-auto-sizer) from 1.0.25 to 1.0.26.
- [Release notes](https://github.com/bvaughn/react-virtualized-auto-sizer/releases)
- [Changelog](https://github.com/bvaughn/react-virtualized-auto-sizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bvaughn/react-virtualized-auto-sizer/compare/1.0.25...1.0.26)

---
updated-dependencies:
- dependency-name: react-virtualized-auto-sizer
  dependency-version: 1.0.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 22:32:45 -04:00
Tom Moor d664044579 perf: Cache promise when loading code languages (#8975) 2025-04-15 02:31:08 +00:00
dependabot[bot] b3ca434c51 chore(deps): bump prosemirror-schema-list from 1.4.1 to 1.5.1 (#8970)
Bumps [prosemirror-schema-list](https://github.com/prosemirror/prosemirror-schema-list) from 1.4.1 to 1.5.1.
- [Changelog](https://github.com/ProseMirror/prosemirror-schema-list/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prosemirror/prosemirror-schema-list/compare/1.4.1...1.5.1)

---
updated-dependencies:
- dependency-name: prosemirror-schema-list
  dependency-version: 1.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 22:28:31 -04:00
dependabot[bot] 631b75def4 chore(deps-dev): bump typescript from 5.8.2 to 5.8.3 (#8972)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.8.2 to 5.8.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.2...v5.8.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 5.8.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-14 22:28:05 -04:00
Tom Moor d183dab063 fix: Mermaid diagrams hanging (#8961) 2025-04-14 12:47:57 +00:00
Hemachandar f082da6456 fix: Reset avatar zoom, set missing text in upload button (#8949)
* fix: Reset avatar zoom, set missing text in upload button

* tiny
2025-04-13 19:15:15 -07:00
Tom Moor ad72210714 fix: Hardcode dynamic imports to avoid production issues (#8958)
* fix: Hardcode dynamic imports to avoid production issues

* tsc
2025-04-13 19:07:47 -07:00
Tom Moor 9c85b26d43 fix: Editor crashes on shared page with no user (#8956) 2025-04-13 21:48:08 +00:00
53 changed files with 727 additions and 1453 deletions
+1 -1
View File
@@ -145,7 +145,7 @@ jobs:
bundle-size:
needs: [build, types, changes]
if: ${{ needs.changes.outputs.app == 'true' }}
if: ${{ needs.changes.outputs.app == 'true' && github.repository == 'outline/outline' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+3
View File
@@ -114,6 +114,8 @@ const Modal: React.FC<Props> = ({
<Small {...props}>
<Centered
onClick={(ev) => ev.stopPropagation()}
// maxHeight needed for proper overflow behavior in Safari
style={{ maxHeight: "65vh" }}
column
reverse
>
@@ -259,6 +261,7 @@ const Small = styled.div`
width: 75vw;
min-width: 350px;
max-width: 450px;
max-height: 65vh;
z-index: ${depths.modal};
display: flex;
justify-content: center;
@@ -148,7 +148,12 @@ function InnerDocumentLink(
const color = document?.color || node.color;
// Draggable
const [{ isDragging }, drag] = useDragDocument(node, depth, document);
const [{ isDragging }, drag] = useDragDocument(
node,
depth,
document,
isEditing
);
// Drop to re-parent
const parentRef = React.useRef<HTMLDivElement>(null);
@@ -270,6 +275,8 @@ function InnerDocumentLink(
<div ref={dropToReparent}>
<DropToImport documentId={node.id} activeClassName="activeDropZone">
<SidebarLink
// @ts-expect-error react-router type is wrong, string component is fine.
component={isEditing ? "div" : undefined}
expanded={hasChildren ? isExpanded : undefined}
onDisclosureClick={handleDisclosureClick}
onClickIntent={handlePrefetch}
@@ -285,6 +292,7 @@ function InnerDocumentLink(
<EditableTitle
title={title}
onSubmit={handleTitleChange}
isEditing={isEditing}
onEditing={setIsEditing}
canUpdate={canUpdate}
maxLength={DocumentValidation.maxTitleLength}
+12 -6
View File
@@ -39,6 +39,7 @@ export interface Props extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
location?: Location;
strict?: boolean;
to: LocationDescriptor;
component?: React.ComponentType;
onBeforeClick?: () => void;
}
@@ -146,17 +147,22 @@ const NavLink = ({
setPreActive(undefined);
}, [currentLocation]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLAnchorElement>) => {
if (["Enter", " "].includes(event.key)) {
navigateTo();
event.currentTarget?.blur();
}
},
[navigateTo]
);
return (
<Link
key={isActive ? "active" : "inactive"}
ref={linkRef}
onClick={handleClick}
onKeyDown={(event) => {
if (["Enter", " "].includes(event.key)) {
navigateTo();
event.currentTarget?.blur();
}
}}
onKeyDown={handleKeyDown}
aria-current={(isActive && ariaCurrent) || undefined}
className={className}
style={style}
@@ -166,11 +166,13 @@ export function useDropToReorderStar(getIndex?: () => string) {
* @param node The NavigationNode model to drag.
* @param depth The depth of the node in the sidebar.
* @param document The related Document model.
* @param isEditing Whether the sidebar item is currently being edited.
*/
export function useDragDocument(
node: NavigationNode,
depth: number,
document?: Document
document?: Document,
isEditing?: boolean
) {
const icon = document?.icon || node.icon || node.emoji;
const color = document?.color || node.color;
@@ -188,7 +190,7 @@ export function useDragDocument(
icon: icon ? <Icon value={icon} color={color} /> : undefined,
collectionId: document?.collectionId || "",
} as DragObject),
canDrag: () => !!document?.isActive,
canDrag: () => !!document?.isActive && !isEditing,
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
-192
View File
@@ -1,192 +0,0 @@
import { IssueSource } from "@shared/schema";
import { ellipsis } from "@shared/styles";
import { observer } from "mobx-react";
import React from "react";
import { Trans, useTranslation } from "react-i18next";
import { toast } from "sonner";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Modal from "~/components/Modal";
import PluginIcon from "~/components/PluginIcon";
import Text from "~/components/Text";
import useBoolean from "~/hooks/useBoolean";
import useRequest from "~/hooks/useRequest";
import { client } from "~/utils/ApiClient";
type Props = {
issueTitle: string;
open: boolean;
onCreate: (source: IssueSource) => Promise<void>;
onClose: () => void;
};
export const CreateIssueDialog = observer(
({ issueTitle, open, onCreate, onClose }: Props) => {
const { t } = useTranslation();
const [isCreating, setCreating, unsetCreating] = useBoolean();
const [selectedSource, selectSource] = React.useState<IssueSource>();
const {
data: sources,
loading,
request,
} = useRequest<IssueSource[]>(
React.useCallback(async () => {
try {
const res = await client.post("/issues.list_sources");
return res.data;
} catch (err) {
toast.error(t("Couldn't load issue sources, try again?"));
throw err;
}
}, [t])
);
const handleCreateIssue = React.useCallback(async () => {
setCreating();
await onCreate(selectedSource!);
unsetCreating();
}, [selectedSource, onCreate, setCreating, unsetCreating]);
React.useEffect(() => {
if (open) {
void request();
} else {
selectSource(undefined);
}
}, [open, request]);
return (
<Modal
title={t("Create issue")}
isOpen={open}
onRequestClose={onClose}
fullscreen={false}
>
<FlexContainer column>
<ListContainer>
{loading ? (
"Loading..."
) : !sources?.length ? (
"No source available"
) : (
<Flex column gap={6}>
{sources.map((source) => (
<SourceItem
key={source.id}
source={source}
selected={source === selectedSource}
onSelect={selectSource}
/>
))}
</Flex>
)}
</ListContainer>
<Footer justify="space-between" align="center" gap={8}>
<StyledText type="secondary">
{selectedSource ? (
<Trans
defaults="Create issue in <em>{{ location }}</em>"
values={{
location: `${selectedSource.owner.name}/${selectedSource.name} `,
}}
components={{
em: <strong />,
}}
/>
) : (
t("Select a source to create issue")
)}
</StyledText>
<Button
disabled={!selectedSource || isCreating}
onClick={handleCreateIssue}
>
{isCreating ? `${t("Creating")}` : t("Create")}
</Button>
</Footer>
</FlexContainer>
</Modal>
);
}
);
const SourceItem = ({
source,
selected,
onSelect,
}: {
source: IssueSource;
selected: boolean;
onSelect: (source: IssueSource) => void;
}) => (
<SourceItemWrapper
justify="space-between"
onClick={() => onSelect(source)}
$selected={selected}
>
<Text>{source.name}</Text>
<Flex align="center" gap={2}>
<PluginIcon id={source.service} size={20} />
<SourceAccount type="tertiary" size="xsmall">
{source.owner.name}
</SourceAccount>
</Flex>
</SourceItemWrapper>
);
export const FlexContainer = styled(Flex)`
margin-left: -24px;
margin-right: -24px;
margin-bottom: -24px;
outline: none;
`;
const ListContainer = styled.div`
height: 65vh;
padding: 0 24px 12px;
overflow: scroll;
${breakpoint("tablet")`
height: 40vh;
`}
`;
const SourceAccount = styled(Text)``;
const SourceItemWrapper = styled(Flex)<{ $selected: boolean }>`
font-size: 16px;
cursor: var(--pointer);
padding: 12px;
border-radius: 6px;
${(props) =>
props.$selected &&
`
background: ${props.theme.accent};
color: ${props.theme.white};
${SourceAccount} {
color: ${props.theme.white};
}
`}
${breakpoint("tablet")`
padding: 4px 6px;
font-size: 15px;
`}
`;
const Footer = styled(Flex)`
height: 64px;
border-top: 1px solid ${(props) => props.theme.horizontalRule};
padding: 0 24px;
`;
const StyledText = styled(Text)`
${ellipsis()}
margin-bottom: 0;
`;
+2 -2
View File
@@ -26,7 +26,7 @@ type Props = Omit<
export const PasteMenu = observer(({ pastedText, embeds, ...props }: Props) => {
const { t } = useTranslation();
const { integrations } = useStores();
const user = useCurrentUser();
const user = useCurrentUser({ rejectOnEmpty: false });
let mentionType: MentionType | undefined;
const url = pastedText ? new URL(pastedText) : undefined;
@@ -70,7 +70,7 @@ export const PasteMenu = observer(({ pastedText, embeds, ...props }: Props) => {
label: pastedText,
href: pastedText,
modelId: v4(),
actorId: user.id,
actorId: user?.id,
},
appendSpace: true,
},
+2 -10
View File
@@ -9,7 +9,6 @@ import { isMarkActive } from "@shared/editor/queries/isMarkActive";
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
import { getColumnIndex, getRowIndex } from "@shared/editor/queries/table";
import { MenuItem } from "@shared/editor/types";
import { useRecentIssueSources } from "~/editor/hooks/useRecentIssueSources";
import useBoolean from "~/hooks/useBoolean";
import useDictionary from "~/hooks/useDictionary";
import useEventListener from "~/hooks/useEventListener";
@@ -107,7 +106,6 @@ export default function SelectionToolbar(props: Props) {
const isActive = useIsActive(view.state) || isMobile;
const isDragging = useIsDragging();
const previousIsActive = usePrevious(isActive);
const { issueSources: recentIssueSources } = useRecentIssueSources();
React.useEffect(() => {
// Trigger callbacks when the toolbar is opened or closed
@@ -176,12 +174,12 @@ export default function SelectionToolbar(props: Props) {
const { isTemplate, rtl, canComment, canUpdate, ...rest } = props;
const { state } = view;
const { selection } = state;
const isDividerSelection = isNodeActive(state.schema.nodes.hr)(state);
if ((readOnly && !canComment) || isDragging) {
return null;
}
const isDividerSelection = isNodeActive(state.schema.nodes.hr)(state);
const colIndex = getColumnIndex(state);
const rowIndex = getRowIndex(state);
const isTableSelection = colIndex !== undefined && rowIndex !== undefined;
@@ -215,13 +213,7 @@ export default function SelectionToolbar(props: Props) {
} else if (isNoticeSelection && selection.empty) {
items = getNoticeMenuItems(state, readOnly, dictionary);
} else {
items = getFormattingMenuItems(
state,
isTemplate,
isMobile,
recentIssueSources,
dictionary
);
items = getFormattingMenuItems(state, isTemplate, isMobile, dictionary);
}
// Some extensions may be disabled, remove corresponding items
-303
View File
@@ -1,303 +0,0 @@
import Extension from "@shared/editor/lib/Extension";
import { isRemoteTransaction } from "@shared/editor/lib/multiplayer";
import { recreateTransform } from "@shared/editor/lib/prosemirror-recreate-transform";
import { isInCode } from "@shared/editor/queries/isInCode";
import { IssueSource } from "@shared/schema";
import {
MentionPlaceholder,
MentionType,
UnfurlResourceType,
UnfurlResponse,
} from "@shared/types";
import { t } from "i18next";
import { action, observable } from "mobx";
import {
Command,
EditorState,
Plugin,
PluginKey,
TextSelection,
} from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view";
import React from "react";
import { toast } from "sonner";
import { Primitive } from "utility-types";
import { v4 } from "uuid";
import stores from "~/stores";
import { client } from "~/utils/ApiClient";
import { CreateIssueDialog } from "../components/CreateIssueDialog";
import { addRecentIssueSource } from "../hooks/useRecentIssueSources";
export default class CreateIssue extends Extension {
private state: {
open: boolean;
title: string;
} = observable({
open: false,
title: "",
});
private key = new PluginKey(this.name);
get name() {
return "issue";
}
get plugins() {
return [
new Plugin({
key: this.key,
state: {
init: () => DecorationSet.empty,
apply: (tr, set) => {
// See if the transaction adds, replaces, or removes any placeholders.
const meta = tr.getMeta(this.key);
// We only want a single paste placeholder at a time, so if we're adding a new
// placeholder we can just return a new DecorationSet and avoid mapping logic.
if (meta?.add) {
const { from, to, id } = meta.add;
const decorations = [
Decoration.inline(
from,
to,
{},
{
id,
}
),
];
return DecorationSet.create(tr.doc, decorations);
}
let mapping = tr.mapping;
const hasDecorations = set.find().length;
if (hasDecorations && (isRemoteTransaction(tr) || meta)) {
try {
mapping = recreateTransform(tr.before, tr.doc, {
complexSteps: true,
wordDiffs: false,
simplifyDiff: true,
}).mapping;
} catch (err) {
// eslint-disable-next-line no-console
console.warn("Failed to recreate transform: ", err);
}
}
set = set.map(mapping, tr.doc);
if (meta?.replace) {
const { id } = meta.replace;
const decorations = set.find(
undefined,
undefined,
(spec) => spec.id === id
);
return DecorationSet.create(tr.doc, decorations);
}
if (meta?.remove) {
const { id } = meta.remove;
const decorations = set.find(
undefined,
undefined,
(spec) => spec.id === id
);
return set.remove(decorations);
}
return set;
},
},
}),
];
}
keys(): Record<string, Command> {
return {
"Mod-Alt-i": (state, dispatch) => {
const isCode = isInCode(state);
const isEmpty = state.selection.empty;
if (isCode || isEmpty) {
return false;
}
const title = state.doc.cut(
state.selection.from,
state.selection.to
).textContent;
const { from } = state.selection;
const to = from + title.length;
const tr = state.tr
.setSelection(TextSelection.near(state.doc.resolve(from)))
.setMeta(this.key, { add: { from, to, id: title } });
dispatch?.(tr);
this.openDialog(title);
return true;
},
};
}
commands() {
return (attrs: Record<string, Primitive>): Command =>
action((state, dispatch) => {
const title = attrs.title as string;
const source = attrs.source
? (JSON.parse(attrs.source as string) as IssueSource)
: undefined;
this.state.title = title;
const { from } = state.selection;
const to = from + title.length;
const tr = state.tr
.setSelection(TextSelection.near(state.doc.resolve(to)))
.setMeta(this.key, { add: { from, to, id: title } });
dispatch?.(tr);
if (source) {
tr.replaceWith(
from,
to,
state.schema.nodes.mention.create({
id: v4(),
type: MentionPlaceholder,
label: title,
href: title,
modelId: v4(),
actorId: stores.auth.currentUserId,
})
).setMeta(this.key, { replace: { id: title } });
}
dispatch?.(tr);
if (source) {
void this.createIssue(source);
} else {
this.openDialog(title);
}
return true;
});
}
widget = () => (
<CreateIssueDialog
issueTitle={this.state.title}
open={this.state.open}
onCreate={this.createIssue}
onClose={this.closeDialog}
/>
);
private createIssue = async (source: IssueSource) => {
try {
addRecentIssueSource(source);
const res = await client.post("/issues.create", {
title: this.state.title,
source,
});
this.addMentionNode(res.data);
toast.success(t("Issue created"));
} catch (err) {
this.removeDecorations(source);
toast.error(t("Couldnt create the issue, try again?"));
}
};
private addMentionNode = (
issue: UnfurlResponse[UnfurlResourceType.Issue]
) => {
const { view } = this.editor;
const { state } = view;
const result = this.findPlaceholder(state, this.state.title);
if (result) {
const tr = state.tr.deleteRange(result[0], result[1]);
view.dispatch(
tr
.setSelection(TextSelection.near(tr.doc.resolve(result[0])))
.setMeta(this.key, {
remove: { id: this.state.title },
})
);
}
this.editor.commands.mention({
id: v4(),
type: MentionType.Issue,
label: this.state.title,
href: issue.url,
modelId: v4(),
actorId: stores.auth.currentUserId,
});
};
private removeDecorations = action((source: IssueSource) => {
const { view } = this.editor;
const { state } = view;
const tr = state.tr.setMeta(this.key, {
remove: { id: this.state.title },
});
const result = this.findPlaceholder(state, this.state.title);
// Placeholder node would have been inserted in recent issue menu flow only.
// We want to reset it with the selected text.
if (source && result) {
tr.replaceWith(
result[0],
result[1],
state.schema.nodeFromJSON({ type: "text", text: this.state.title })
);
}
view.dispatch(tr);
this.state.title = "";
});
private openDialog = action((title: string) => {
this.state.title = title;
this.state.open = true;
});
private closeDialog = action(() => {
const { view } = this.editor;
const { state } = view;
const result = this.findPlaceholder(state, this.state.title);
if (result) {
const tr = state.tr
.setSelection(TextSelection.near(state.doc.resolve(result[0])))
.setMeta(this.key, {
remove: { id: this.state.title },
});
view.dispatch(tr);
}
this.state.title = "";
this.state.open = false;
});
private findPlaceholder = (
state: EditorState,
id: string
): [number, number] | null => {
const decos = this.key.getState(state) as DecorationSet;
const found = decos?.find(undefined, undefined, (spec) => spec.id === id);
return found?.length ? [found[0].from, found[0].to] : null;
};
}
-2
View File
@@ -3,7 +3,6 @@ import Mark from "@shared/editor/marks/Mark";
import Node from "@shared/editor/nodes/Node";
import BlockMenuExtension from "~/editor/extensions/BlockMenu";
import ClipboardTextSerializer from "~/editor/extensions/ClipboardTextSerializer";
import CreateIssueExtension from "~/editor/extensions/CreateIssue";
import EmojiMenuExtension from "~/editor/extensions/EmojiMenu";
import FindAndReplaceExtension from "~/editor/extensions/FindAndReplace";
import HoverPreviewsExtension from "~/editor/extensions/HoverPreviews";
@@ -25,7 +24,6 @@ export const withUIExtensions = (nodes: Nodes) => [
MentionMenuExtension,
FindAndReplaceExtension,
HoverPreviewsExtension,
CreateIssueExtension,
// Order these default key handlers last
PreventTab,
Keys,
-47
View File
@@ -1,47 +0,0 @@
import { IssueSource } from "@shared/schema";
import Storage from "@shared/utils/Storage";
import React from "react";
import usePersistedState, {
setPersistedState,
} from "~/hooks/usePersistedState";
const StorageKey = "recent-issue-sources";
const MaxCount = 5;
export function useRecentIssueSources() {
const [issueSources, setIssueSources] = usePersistedState<IssueSource[]>(
StorageKey,
[] as IssueSource[]
);
const addIssueSource = React.useCallback(
(source: IssueSource) => {
const newIssueSources = insertAndTrim(issueSources, source);
setIssueSources(newIssueSources);
},
[issueSources, setIssueSources]
);
return { issueSources, addIssueSource };
}
export function addRecentIssueSource(source: IssueSource) {
const issueSources: IssueSource[] = Storage.get(StorageKey) ?? [];
const newIssueSources = insertAndTrim(issueSources, source);
setPersistedState(StorageKey, newIssueSources);
}
function insertAndTrim(issueSources: IssueSource[], source: IssueSource) {
const newIssueSources = issueSources.filter((s) => s.id !== source.id);
newIssueSources.unshift(source);
if (newIssueSources.length > MaxCount) {
newIssueSources.pop();
}
return newIssueSources;
}
export type RecentIssueSourcesResponse = ReturnType<
typeof useRecentIssueSources
>;
+1 -48
View File
@@ -1,4 +1,3 @@
import { t } from "i18next";
import {
BoldIcon,
CodeIcon,
@@ -18,9 +17,8 @@ import {
IndentIcon,
CopyIcon,
Heading3Icon,
PlusIcon,
} from "outline-icons";
import { EditorState, TextSelection } from "prosemirror-state";
import { EditorState } from "prosemirror-state";
import * as React from "react";
import styled from "styled-components";
import Highlight from "@shared/editor/marks/Highlight";
@@ -30,17 +28,14 @@ import { isInList } from "@shared/editor/queries/isInList";
import { isMarkActive } from "@shared/editor/queries/isMarkActive";
import { isNodeActive } from "@shared/editor/queries/isNodeActive";
import { MenuItem } from "@shared/editor/types";
import { IssueSource } from "@shared/schema";
import { metaDisplay } from "@shared/utils/keyboard";
import CircleIcon from "~/components/Icons/CircleIcon";
import PluginIcon from "~/components/PluginIcon";
import { Dictionary } from "~/hooks/useDictionary";
export default function formattingMenuItems(
state: EditorState,
isTemplate: boolean,
isMobile: boolean,
recentIssueSources: IssueSource[],
dictionary: Dictionary
): MenuItem[] {
const { schema } = state;
@@ -48,39 +43,12 @@ export default function formattingMenuItems(
const isCodeBlock = isInCode(state, { onlyBlock: true });
const isEmpty = state.selection.empty;
const selectedText =
!isEmpty && state.selection instanceof TextSelection
? state.doc.cut(state.selection.from, state.selection.to).textContent
: undefined;
const highlight = getMarksBetween(
state.selection.from,
state.selection.to,
state
).find(({ mark }) => mark.type.name === "highlight");
const issueSourcesChildren = recentIssueSources.length
? recentIssueSources.map<MenuItem>((source) => ({
name: "issue",
label: `${source.owner.name}/${source.name}`,
icon: <PluginIcon id={source.service} />,
attrs: {
title: selectedText,
source: JSON.stringify(source),
},
}))
: undefined;
if (issueSourcesChildren) {
issueSourcesChildren.push({
name: "issue",
label: `${t("Other")}`,
attrs: {
title: selectedText,
},
});
}
return [
{
name: "placeholder",
@@ -291,21 +259,6 @@ export default function formattingMenuItems(
shortcut: `${metaDisplay}+C`,
visible: isCode && !isCodeBlock && (!isMobile || !isEmpty),
},
{
name: "separator",
visible: !isCode && !isEmpty,
},
{
name: "issue",
tooltip: dictionary.createIssue,
shortcut: `${metaDisplay}+⌥+I`,
icon: <PlusIcon />,
attrs: {
title: selectedText,
},
visible: !isCode && !!selectedText,
children: issueSourcesChildren,
},
];
}
-1
View File
@@ -27,7 +27,6 @@ export default function useDictionary() {
codeInline: t("Code"),
comment: t("Comment"),
copy: t("Copy"),
createIssue: t("Create issue from selection"),
createLink: t("Create link"),
createLinkError: t("Sorry, an error occurred creating the link"),
createNewDoc: t("Create a new doc"),
-10
View File
@@ -8,7 +8,6 @@ import {
GlobeIcon,
TeamIcon,
BeakerIcon,
BuildingBlocksIcon,
SettingsIcon,
ExportIcon,
ImportIcon,
@@ -40,7 +39,6 @@ const Notifications = lazy(() => import("~/scenes/Settings/Notifications"));
const Preferences = lazy(() => import("~/scenes/Settings/Preferences"));
const Profile = lazy(() => import("~/scenes/Settings/Profile"));
const Security = lazy(() => import("~/scenes/Settings/Security"));
const SelfHosted = lazy(() => import("~/scenes/Settings/SelfHosted"));
const Shares = lazy(() => import("~/scenes/Settings/Shares"));
const Templates = lazy(() => import("~/scenes/Settings/Templates"));
const Zapier = lazy(() => import("~/scenes/Settings/Zapier"));
@@ -177,14 +175,6 @@ const useSettingsConfig = () => {
icon: ExportIcon,
},
// Integrations
{
name: t("Self Hosted"),
path: integrationSettingsPath("self-hosted"),
component: SelfHosted,
enabled: can.update && !isCloudHosted,
group: t("Integrations"),
icon: BuildingBlocksIcon,
},
{
name: "Zapier",
path: integrationSettingsPath("zapier"),
+2
View File
@@ -209,7 +209,9 @@ function Invite({ onSubmit }: Props) {
placeholder={`name@${predictedDomain}`}
value={invite.email}
required={index === 0}
autoComplete="off"
autoFocus
data-1p-ignore
flex
/>
<StyledInput
-140
View File
@@ -1,140 +0,0 @@
import find from "lodash/find";
import { observer } from "mobx-react";
import { BuildingBlocksIcon } from "outline-icons";
import * as React from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { IntegrationService, IntegrationType } from "@shared/types";
import Integration from "~/models/Integration";
import Button from "~/components/Button";
import Heading from "~/components/Heading";
import Input from "~/components/Input";
import Scene from "~/components/Scene";
import useStores from "~/hooks/useStores";
import SettingRow from "./components/SettingRow";
type FormData = {
drawIoUrl: string;
gristUrl: string;
};
function SelfHosted() {
const { integrations } = useStores();
const { t } = useTranslation();
const integrationDiagrams = find(integrations.orderedData, {
type: IntegrationType.Embed,
service: IntegrationService.Diagrams,
}) as Integration<IntegrationType.Embed> | undefined;
const integrationGrist = find(integrations.orderedData, {
type: IntegrationType.Embed,
service: IntegrationService.Grist,
}) as Integration<IntegrationType.Embed> | undefined;
const {
register,
reset,
handleSubmit: formHandleSubmit,
formState,
} = useForm<FormData>({
mode: "all",
defaultValues: {
drawIoUrl: integrationDiagrams?.settings.url,
gristUrl: integrationGrist?.settings.url,
},
});
React.useEffect(() => {
void integrations.fetchPage({
type: IntegrationType.Embed,
});
}, [integrations]);
React.useEffect(() => {
reset({
drawIoUrl: integrationDiagrams?.settings.url,
gristUrl: integrationGrist?.settings.url,
});
}, [integrationDiagrams, integrationGrist, reset]);
const handleSubmit = React.useCallback(
async (data: FormData) => {
try {
if (data.drawIoUrl) {
await integrations.save({
id: integrationDiagrams?.id,
type: IntegrationType.Embed,
service: IntegrationService.Diagrams,
settings: {
url: data.drawIoUrl,
},
});
} else {
await integrationDiagrams?.delete();
}
if (data.gristUrl) {
await integrations.save({
id: integrationGrist?.id,
type: IntegrationType.Embed,
service: IntegrationService.Grist,
settings: {
url: data.gristUrl,
},
});
} else {
await integrationGrist?.delete();
}
toast.success(t("Settings saved"));
} catch (err) {
toast.error(err.message);
}
},
[integrations, integrationDiagrams, integrationGrist, t]
);
return (
<Scene title={t("Self Hosted")} icon={<BuildingBlocksIcon />}>
<Heading>{t("Self Hosted")}</Heading>
<form onSubmit={formHandleSubmit(handleSubmit)}>
<SettingRow
label={t("Draw.io deployment")}
name="drawIoUrl"
description={t(
"Add your self-hosted draw.io installation url here to enable automatic embedding of diagrams within documents."
)}
border={false}
>
<Input
placeholder="https://app.diagrams.net/"
pattern="https?://.*"
{...register("drawIoUrl")}
/>
</SettingRow>
<SettingRow
label={t("Grist deployment")}
name="gristUrl"
description={t("Add your self-hosted grist installation URL here.")}
border={false}
>
<Input
placeholder="https://docs.getgrist.com/"
pattern="https?://.*"
{...register("gristUrl")}
/>
</SettingRow>
<Button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting ? `${t("Saving")}` : t("Save")}
</Button>
</form>
</Scene>
);
}
export default observer(SelfHosted);
+117 -79
View File
@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react";
import { useState, useRef } from "react";
import AvatarEditor from "react-avatar-editor";
import Dropzone from "react-dropzone";
import { useDropzone } from "react-dropzone";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { s } from "@shared/styles";
@@ -12,7 +12,6 @@ import { AttachmentValidation } from "@shared/validations";
import ButtonLarge from "~/components/ButtonLarge";
import Flex from "~/components/Flex";
import LoadingIndicator from "~/components/LoadingIndicator";
import Modal from "~/components/Modal";
import useStores from "~/hooks/useStores";
import { compressImage } from "~/utils/compressImage";
import { uploadFile, dataUrlToBlob } from "~/utils/files";
@@ -28,81 +27,138 @@ const ImageUpload: React.FC<Props> = ({
onSuccess,
onError,
submitText,
borderRadius = 150,
borderRadius,
children,
}) => {
const { ui } = useStores();
const { dialogs } = useStores();
const { t } = useTranslation();
submitText || t("Crop image");
const [isUploading, setIsUploading] = useState(false);
const [isCropping, setIsCropping] = useState(false);
const [zoom, setZoom] = useState(1);
const [file, setFile] = useState<File | null>(null);
const avatarEditorRef = useRef<AvatarEditor>(null);
const uploadImage = React.useCallback(
async (blob: Blob, file: File) => {
try {
const compressed = await compressImage(blob, {
maxHeight: 512,
maxWidth: 512,
});
const attachment = await uploadFile(compressed, {
name: file.name,
preset: AttachmentPreset.Avatar,
});
void onSuccess(attachment.url);
} catch (err) {
onError(err.message);
} finally {
setIsUploading(false);
setIsCropping(false);
dialogs.closeAllModals();
}
},
[dialogs, onSuccess, onError]
);
const onDropAccepted = async (files: File[]) => {
setIsCropping(true);
setFile(files[0]);
};
const handleUpload = React.useCallback(
(blob: Blob, file: File) => {
setIsUploading(true);
// allow the UI to update before converting the canvas to a Blob
// for large images this can cause the page rendering to hang.
setTimeout(() => uploadImage(blob, file), 0);
},
[uploadImage]
);
const handleCrop = () => {
setIsUploading(true);
// allow the UI to update before converting the canvas to a Blob
// for large images this can cause the page rendering to hang.
setTimeout(uploadImage, 0);
};
const uploadImage = async () => {
const canvas = avatarEditorRef.current?.getImage();
invariant(canvas, "canvas is not defined");
const imageBlob = dataUrlToBlob(canvas.toDataURL());
try {
const compressed = await compressImage(imageBlob, {
maxHeight: 512,
maxWidth: 512,
});
const attachment = await uploadFile(compressed, {
name: file!.name,
preset: AttachmentPreset.Avatar,
});
void onSuccess(attachment.url);
} catch (err) {
onError(err.message);
} finally {
setIsUploading(false);
setIsCropping(false);
}
};
const handleClose = () => {
const handleClose = React.useCallback(() => {
setIsUploading(false);
setIsCropping(false);
};
}, []);
const handleZoom = (event: React.ChangeEvent<HTMLInputElement>) => {
const target = event.target;
const onDropAccepted = React.useCallback(
async (files: File[]) => {
setIsCropping(true);
dialogs.openModal({
title: "",
content: (
<AvatarEditorDialog
file={files[0]}
onUpload={handleUpload}
isUploading={isUploading}
borderRadius={borderRadius ?? 150}
submitText={submitText || t("Crop image")}
/>
),
onClose: handleClose,
});
},
[
t,
dialogs,
handleUpload,
handleClose,
isUploading,
borderRadius,
submitText,
]
);
if (target instanceof HTMLInputElement) {
setZoom(parseFloat(target.value));
}
};
const { getRootProps, getInputProps } = useDropzone({
accept: AttachmentValidation.avatarContentTypes.join(", "),
onDropAccepted,
});
const renderCropping = () => (
<Modal
onRequestClose={handleClose}
fullscreen={false}
title={<>&nbsp;</>}
isOpen
>
if (isCropping) {
return null; // onDropAccepted would have opened a modal for cropping the image.
}
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{children}
</div>
);
};
type AvatarEditorDialogProps = {
file: File;
onUpload: (blob: Blob, file: File) => void;
isUploading: boolean;
borderRadius: number;
submitText: string;
};
const AvatarEditorDialog: React.FC<AvatarEditorDialogProps> = observer(
({ file, onUpload, isUploading, borderRadius, submitText }) => {
const { ui } = useStores();
const { t } = useTranslation();
const [zoom, setZoom] = useState(1);
const avatarEditorRef = useRef<AvatarEditor>(null);
const handleUpload = React.useCallback(() => {
const canvas = avatarEditorRef.current?.getImage();
invariant(canvas, "canvas is not defined");
const blob = dataUrlToBlob(canvas.toDataURL());
onUpload(blob, file);
}, [file, onUpload]);
const handleZoom = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const target = event.target;
if (target instanceof HTMLInputElement) {
setZoom(parseFloat(target.value));
}
},
[]
);
return (
<Flex auto column align="center" justify="center">
{isUploading && <LoadingIndicator />}
<AvatarEditorContainer>
<AvatarEditor
ref={avatarEditorRef}
image={file!}
image={file}
width={250}
height={250}
border={25}
@@ -121,31 +177,13 @@ const ImageUpload: React.FC<Props> = ({
onChange={handleZoom}
/>
<br />
<ButtonLarge fullwidth onClick={handleCrop} disabled={isUploading}>
<ButtonLarge fullwidth onClick={handleUpload} disabled={isUploading}>
{isUploading ? `${t(`Uploading`)}` : submitText}
</ButtonLarge>
</Flex>
</Modal>
);
if (isCropping && file) {
return renderCropping();
);
}
return (
<Dropzone
accept={AttachmentValidation.avatarContentTypes.join(", ")}
onDropAccepted={onDropAccepted}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
{children}
</div>
)}
</Dropzone>
);
};
);
const AvatarEditorContainer = styled(Flex)`
margin-bottom: 30px;
+8 -1
View File
@@ -99,7 +99,14 @@ export default abstract class Store<T extends Model> {
const normalized = deburr((query ?? "").trim().toLocaleLowerCase());
if (!normalized) {
return this.orderedData.slice(0, options?.maxResults);
return this.orderedData
.filter((item) => {
if ("deletedAt" in item && item.deletedAt) {
return false;
}
return true;
})
.slice(0, options?.maxResults);
}
return this.orderedData
+10 -9
View File
@@ -48,11 +48,11 @@
"> 0.25%, not dead"
],
"dependencies": {
"@aws-sdk/client-s3": "3.782.0",
"@aws-sdk/lib-storage": "3.782.0",
"@aws-sdk/s3-presigned-post": "3.782.0",
"@aws-sdk/s3-request-presigner": "3.782.0",
"@aws-sdk/signature-v4-crt": "^3.782.0",
"@aws-sdk/client-s3": "3.787.0",
"@aws-sdk/lib-storage": "3.787.0",
"@aws-sdk/s3-presigned-post": "3.787.0",
"@aws-sdk/s3-request-presigner": "3.787.0",
"@aws-sdk/signature-v4-crt": "^3.787.0",
"@babel/core": "^7.26.10",
"@babel/plugin-proposal-decorators": "^7.25.9",
"@babel/plugin-transform-class-properties": "^7.25.9",
@@ -187,7 +187,7 @@
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.2",
"prosemirror-model": "^1.25.0",
"prosemirror-schema-list": "^1.4.1",
"prosemirror-schema-list": "^1.5.1",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-transform": "1.10.0",
@@ -210,7 +210,7 @@
"react-merge-refs": "^2.1.1",
"react-portal": "^4.2.2",
"react-router-dom": "^5.3.4",
"react-virtualized-auto-sizer": "^1.0.21",
"react-virtualized-auto-sizer": "^1.0.26",
"react-waypoint": "^10.3.0",
"react-window": "^1.8.11",
"reakit": "^1.3.11",
@@ -362,7 +362,7 @@
"rimraf": "^2.5.4",
"rollup-plugin-webpack-stats": "^2.0.3",
"terser": "^5.39.0",
"typescript": "^5.7.3",
"typescript": "^5.8.3",
"vite-plugin-static-copy": "^0.17.0",
"yarn-deduplicate": "^6.0.2"
},
@@ -377,5 +377,6 @@
"rollup": "^4.5.1",
"prismjs": "1.30.0"
},
"version": "0.83.0"
"version": "0.83.0",
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
-5
View File
@@ -13,9 +13,4 @@ PluginManager.add([
component: React.lazy(() => import("./Settings")),
},
},
{
...config,
type: Hook.Icon,
value: Icon,
},
]);
@@ -1,81 +0,0 @@
import { Endpoints } from "@octokit/types";
import Logger from "@server/logging/Logger";
import { Integration, User } from "@server/models";
import { CreateIssueResponse } from "@server/types";
import { BaseIssueProvider } from "@server/utils/IssueProvider";
import { IssueSource } from "@shared/schema";
import {
IntegrationService,
IntegrationType,
UnfurlResourceType,
} from "@shared/types";
import { GitHub } from "./github";
// This is needed to account for Octokit paginate response type mismatch.
type ReposForInstallation =
Endpoints["GET /installation/repositories"]["response"]["data"]["repositories"];
export class GitHubIssueProvider extends BaseIssueProvider {
constructor() {
super(IntegrationService.GitHub);
}
async fetchSources(
integration: Integration<IntegrationType.Embed>
): Promise<IssueSource[]> {
const client = await GitHub.authenticateAsInstallation(
integration.settings.github!.installation.id
);
const repos =
(await client.requestRepos()) as unknown as ReposForInstallation;
const sources = repos.map<IssueSource>((repo) => ({
id: String(repo.id),
name: repo.name,
owner: { id: String(repo.owner.id), name: repo.owner.login },
service: IntegrationService.GitHub,
}));
return sources;
}
async createIssue(
title: string,
source: IssueSource,
actor: User
): Promise<CreateIssueResponse | undefined> {
const integration = (await Integration.findOne({
where: {
service: IntegrationService.GitHub,
teamId: actor.teamId,
"settings.github.installation.account.name": source.owner.name,
},
})) as Integration<IntegrationType.Embed> | undefined;
if (!integration) {
return;
}
try {
const client = await GitHub.authenticateAsInstallation(
integration.settings.github!.installation.id
);
const { data } = await client.createIssue({
owner: source.owner.name,
repo: source.name,
title,
});
return {
...data,
type: UnfurlResourceType.Issue,
cacheKey: data.html_url,
};
} catch (err) {
Logger.warn("Failed to create issue in GitHub", err);
return;
}
}
}
+20 -18
View File
@@ -2,7 +2,6 @@ import Router from "koa-router";
import find from "lodash/find";
import { IntegrationService, IntegrationType } from "@shared/types";
import { parseDomain } from "@shared/utils/domains";
import { createContext } from "@server/context";
import Logger from "@server/logging/Logger";
import auth from "@server/middlewares/authentication";
import { transaction } from "@server/middlewares/transaction";
@@ -89,27 +88,30 @@ router.get(
},
{ transaction }
);
await Integration.createWithCtx(createContext({ user, transaction }), {
service: IntegrationService.GitHub,
type: IntegrationType.Embed,
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
settings: {
github: {
installation: {
id: installationId!,
account: {
id: installation.account?.id,
name:
// @ts-expect-error Property 'login' does not exist on type
installation.account?.login,
avatarUrl: installation.account?.avatar_url,
await Integration.create(
{
service: IntegrationService.GitHub,
type: IntegrationType.Embed,
userId: user.id,
teamId: user.teamId,
authenticationId: authentication.id,
settings: {
github: {
installation: {
id: installationId!,
account: {
id: installation.account?.id,
name:
// @ts-expect-error Property 'login' does not exist on type
installation.account?.login,
avatarUrl: installation.account?.avatar_url,
},
},
},
},
},
});
{ transaction }
);
ctx.redirect(GitHubUtils.url);
}
);
+2 -28
View File
@@ -40,32 +40,6 @@ const requestPlugin = (octokit: Octokit) => ({
},
}),
requestRepos: () =>
octokit.paginate(octokit.rest.apps.listReposAccessibleToInstallation, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}),
createIssue: async ({
owner,
repo,
title,
}: {
owner: string;
repo: string;
title: string;
}) =>
octokit.request(`POST /repos/{owner}/{repo}/issues`, {
owner,
repo,
title,
headers: {
Accept: "application/vnd.github.text+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}),
/**
* Fetches app installations accessible to the user
*
@@ -232,12 +206,12 @@ export class GitHub {
);
const { data } = await client.requestResource(resource);
if (!data) {
return;
return { error: "Resource not found" };
}
return { ...data, type: resource.type };
} catch (err) {
Logger.warn("Failed to fetch resource from GitHub", err);
return;
return { error: err.message || "Unknown error" };
}
};
}
-6
View File
@@ -1,8 +1,6 @@
import { IntegrationService } from "@shared/types";
import { Minute } from "@shared/utils/time";
import { PluginManager, Hook } from "@server/utils/PluginManager";
import config from "../plugin.json";
import { GitHubIssueProvider } from "./GitHubIssueProvider";
import router from "./api/github";
import env from "./env";
import { GitHub } from "./github";
@@ -26,10 +24,6 @@ if (enabled) {
type: Hook.UnfurlProvider,
value: { unfurl: GitHub.unfurl, cacheExpiry: Minute.seconds },
},
{
type: Hook.IssueProvider,
value: new GitHubIssueProvider(),
},
{
type: Hook.Uninstall,
value: uninstall,
+6 -4
View File
@@ -1,6 +1,6 @@
import { JSONObject, UnfurlResourceType } from "@shared/types";
import Logger from "@server/logging/Logger";
import { UnfurlSignature } from "@server/types";
import { UnfurlError, UnfurlSignature } from "@server/types";
import fetch from "@server/utils/fetch";
import env from "./env";
@@ -10,7 +10,7 @@ class Iframely {
public static async requestResource(
url: string,
type = "oembed"
): Promise<JSONObject | undefined> {
): Promise<JSONObject | UnfurlError> {
const isDefaultHost = env.IFRAMELY_URL === this.defaultUrl;
// Cloud Iframely requires /api path, while self-hosted does not.
@@ -25,7 +25,7 @@ class Iframely {
return await res.json();
} catch (err) {
Logger.error(`Error fetching data from Iframely for url: ${url}`, err);
return;
return { error: err.message || "Unknown error" };
}
}
@@ -36,7 +36,9 @@ class Iframely {
*/
public static unfurl: UnfurlSignature = async (url: string) => {
const data = await Iframely.requestResource(url);
return { ...data, type: UnfurlResourceType.OEmbed };
return "error" in data // In addition to our custom UnfurlError, sometimes iframely returns error in the response body.
? ({ error: data.error } as UnfurlError)
: { ...data, type: UnfurlResourceType.OEmbed };
};
}
+12 -1
View File
@@ -13,9 +13,11 @@ import {
RichTextItemResponse,
} from "@notionhq/client/build/src/api-endpoints";
import { RateLimit } from "async-sema";
import emojiRegex from "emoji-regex";
import compact from "lodash/compact";
import { z } from "zod";
import { Second } from "@shared/utils/time";
import { isUrl } from "@shared/utils/urls";
import { NotionUtils } from "../shared/NotionUtils";
import { Block, Page, PageType } from "../shared/types";
import env from "./env";
@@ -37,7 +39,16 @@ const AccessTokenResponseSchema = z.object({
bot_id: z.string(),
workspace_id: z.string(),
workspace_name: z.string().nullish(),
workspace_icon: z.string().url().nullish(),
workspace_icon: z
.string()
.nullish()
.transform((val) => {
const emojiRegexp = emojiRegex();
if (val && (isUrl(val) || emojiRegexp.test(val))) {
return val;
}
return undefined;
}),
});
export class NotionClient {
@@ -1,15 +0,0 @@
"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
queryInterface.addColumn("integrations", "issueSources", {
type: Sequelize.JSONB,
allowNull: true,
});
},
async down(queryInterface, Sequelize) {
queryInterface.removeColumn("integrations", "issueSources");
},
};
-4
View File
@@ -13,7 +13,6 @@ import {
IsIn,
AfterDestroy,
} from "sequelize-typescript";
import { IssueSource } from "@shared/schema";
import { IntegrationType, IntegrationService } from "@shared/types";
import type { IntegrationSettings } from "@shared/types";
import Collection from "@server/models/Collection";
@@ -54,9 +53,6 @@ class Integration<T = unknown> extends ParanoidModel<
@Column(DataType.ARRAY(DataType.STRING))
events: string[];
@Column(DataType.JSONB)
issueSources: IssueSource[] | null;
// associations
@BelongsTo(() => User, "userId")
@@ -251,6 +251,10 @@ describe("NotificationHelper", () => {
userId: subscribedUser.id,
collectionId: document.collectionId!,
});
await buildSubscription({
userId: subscribedUser.id,
documentId: document.id,
});
const recipients =
await NotificationHelper.getDocumentNotificationRecipients({
+9 -7
View File
@@ -1,4 +1,5 @@
import uniq from "lodash/uniq";
import uniqBy from "lodash/uniqBy";
import { Op } from "sequelize";
import {
NotificationEventType,
@@ -187,7 +188,6 @@ export default class NotificationHelper {
});
} else {
const subscriptions = await Subscription.findAll({
attributes: ["userId"],
where: {
userId: {
[Op.ne]: actorId,
@@ -206,17 +206,19 @@ export default class NotificationHelper {
],
});
recipients = subscriptions.map((s) => s.user);
recipients = uniqBy(
subscriptions.map((s) => s.user),
(user) => user.id
);
}
recipients = recipients.filter((recipient) =>
recipient.subscribedToEventType(notificationType)
);
const filtered = [];
for (const recipient of recipients) {
if (recipient.isSuspended) {
if (
recipient.isSuspended ||
!recipient.subscribedToEventType(notificationType)
) {
continue;
}
@@ -3,7 +3,6 @@ import { Integration } from "@server/models";
import BaseProcessor from "@server/queues/processors/BaseProcessor";
import { IntegrationEvent, Event } from "@server/types";
import { CacheHelper } from "@server/utils/CacheHelper";
import CacheIssueSourcesTask from "../tasks/CacheIssueSourcesTask";
export default class IntegrationCreatedProcessor extends BaseProcessor {
static applicableEvents: Event["name"][] = ["integrations.create"];
@@ -19,11 +18,6 @@ export default class IntegrationCreatedProcessor extends BaseProcessor {
return;
}
// Store the available issue sources in the integration record.
await CacheIssueSourcesTask.schedule({
integrationId: integration.id,
});
// Clear the cache of unfurled data for the team as it may be stale now.
await CacheHelper.clearData(CacheHelper.getUnfurlKey(integration.teamId));
}
@@ -1,35 +0,0 @@
import { Integration } from "@server/models";
import { sequelize } from "@server/storage/database";
import { Hook, PluginManager } from "@server/utils/PluginManager";
import BaseTask from "./BaseTask";
const plugins = PluginManager.getHooks(Hook.IssueProvider);
type Props = {
integrationId: string;
};
export default class CacheIssueSourcesTask extends BaseTask<Props> {
async perform({ integrationId }: Props) {
await sequelize.transaction(async (transaction) => {
const integration = await Integration.findByPk(integrationId, {
transaction,
lock: transaction.LOCK.UPDATE,
});
if (!integration) {
return;
}
const plugin = plugins.find(
(p) => p.value.service === integration.service
);
if (!plugin) {
return;
}
const sources = await plugin.value.fetchSources(integration);
integration.issueSources = sources;
await integration.save({ transaction });
});
}
}
@@ -85,16 +85,21 @@ export default class CommentCreatedNotificationsTask extends BaseTask<CommentEve
)
).filter((recipient) => !userIdsMentioned.includes(recipient.id));
for (const recipient of recipients) {
await Notification.create({
event: NotificationEventType.CreateComment,
userId: recipient.id,
actorId: comment.createdById,
teamId: document.teamId,
commentId: comment.id,
documentId: document.id,
});
}
await sequelize.transaction(async (transaction) => {
for (const recipient of recipients) {
await Notification.create(
{
event: NotificationEventType.CreateComment,
userId: recipient.id,
actorId: comment.createdById,
teamId: document.teamId,
commentId: comment.id,
documentId: document.id,
},
{ transaction }
);
}
});
}
public get options() {
-2
View File
@@ -23,7 +23,6 @@ import groups from "./groups";
import imports from "./imports";
import installation from "./installation";
import integrations from "./integrations";
import issues from "./issues";
import apiErrorHandler from "./middlewares/apiErrorHandler";
import apiResponse from "./middlewares/apiResponse";
import apiTracer from "./middlewares/apiTracer";
@@ -100,7 +99,6 @@ router.use("/", urls.routes());
router.use("/", userMemberships.routes());
router.use("/", reactions.routes());
router.use("/", imports.routes());
router.use("/", issues.routes());
if (!env.isCloudHosted) {
router.use("/", installation.routes());
-1
View File
@@ -1 +0,0 @@
export { default } from "./issues";
-70
View File
@@ -1,70 +0,0 @@
import { InternalError, InvalidRequestError } from "@server/errors";
import auth from "@server/middlewares/authentication";
import validate from "@server/middlewares/validate";
import { Integration } from "@server/models";
import presentUnfurl from "@server/presenters/unfurl";
import { APIContext } from "@server/types";
import { CacheHelper } from "@server/utils/CacheHelper";
import { Hook, PluginManager } from "@server/utils/PluginManager";
import { IssueSource } from "@shared/schema";
import { UserRole } from "@shared/types";
import Router from "koa-router";
import * as T from "./schema";
const router = new Router();
const plugins = PluginManager.getHooks(Hook.IssueProvider);
router.post(
"issues.list_sources",
auth({ role: UserRole.Member }),
async (ctx: APIContext) => {
const { user } = ctx.state.auth;
const integrations = await Integration.findAll({
attributes: ["issueSources"],
where: { teamId: user.teamId },
});
const sources = integrations
.flatMap((integration) => integration.issueSources)
.filter(Boolean) as IssueSource[];
ctx.body = {
data: sources,
};
}
);
router.post(
"issues.create",
auth({ role: UserRole.Member }),
validate(T.IssuesCreateSchema),
async (ctx: APIContext<T.IssuesCreateReq>) => {
const { title, source } = ctx.input.body;
const { user } = ctx.state.auth;
const plugin = plugins.find((p) => p.value.service === source.service);
if (!plugin) {
throw InvalidRequestError();
}
const issue = await plugin.value.createIssue(title, source, user);
if (!issue) {
throw InternalError();
}
await CacheHelper.setData(
CacheHelper.getUnfurlKey(user.teamId, issue.cacheKey),
issue,
plugin.value.cacheExpiry
);
ctx.body = {
data: await presentUnfurl(issue),
};
}
);
export default router;
-12
View File
@@ -1,12 +0,0 @@
import { IssueSource } from "@shared/schema";
import { z } from "zod";
import { BaseSchema } from "../schema";
export const IssuesCreateSchema = BaseSchema.extend({
body: z.object({
title: z.string().nonempty(),
source: IssueSource,
}),
});
export type IssuesCreateReq = z.infer<typeof IssuesCreateSchema>;
+1 -1
View File
@@ -20,7 +20,7 @@ jest.mock("dns", () => ({
jest
.spyOn(Iframely, "requestResource")
.mockImplementation(() => Promise.resolve(undefined));
.mockImplementation(() => Promise.resolve({}));
const server = getTestServer();
+4 -3
View File
@@ -98,11 +98,12 @@ router.post(
}
for (const plugin of plugins) {
const data = await plugin.value.unfurl(url, actor);
if (data) {
if ("error" in data) {
const unfurl = await plugin.value.unfurl(url, actor);
if (unfurl) {
if (unfurl.error) {
return (ctx.response.status = 204);
} else {
const data = unfurl as Unfurl;
await CacheHelper.setData(
CacheHelper.getUnfurlKey(actor.teamId, url),
data,
+3 -16
View File
@@ -2,7 +2,6 @@ import { ParameterizedContext, DefaultContext } from "koa";
import { IRouterParamContext } from "koa-router";
import { InferAttributes, Model, Transaction } from "sequelize";
import { z } from "zod";
import { IssueSource } from "@shared/schema";
import {
CollectionSort,
NavigationNode,
@@ -11,7 +10,6 @@ import {
JSONValue,
UnfurlResourceType,
ProsemirrorData,
IntegrationType,
} from "@shared/types";
import { BaseSchema } from "@server/routes/api/schema";
import { AccountProvisionerResult } from "./commands/accountProvisioner";
@@ -580,26 +578,15 @@ export type CollectionJSONExport = {
export type Unfurl = { [x: string]: JSONValue; type: UnfurlResourceType };
export type UnfurlError = { error: string };
export type UnfurlSignature = (
url: string,
actor?: User
) => Promise<Unfurl | void>;
) => Promise<Unfurl | UnfurlError | undefined>;
export type UninstallSignature = (integration: Integration) => Promise<void>;
export type CreateIssueResponse = Unfurl & { cacheKey: string };
export type IssueProvider = {
listSources: (
integration: Integration<IntegrationType.Embed>
) => Promise<IssueSource[]>;
createIssue: (
title: string,
source: IssueSource,
actor: User
) => Promise<CreateIssueResponse | undefined>;
};
export type Replace<T, K extends keyof T, N extends string> = {
[P in keyof T as P extends K ? N : P]: T[P extends K ? K : P];
};
-28
View File
@@ -1,28 +0,0 @@
import { Integration, User } from "@server/models";
import { CreateIssueResponse } from "@server/types";
import { IssueSource } from "@shared/schema";
import {
IntegrationType,
IssueProviderIntegrationService,
} from "@shared/types";
import { Minute } from "@shared/utils/time";
export abstract class BaseIssueProvider {
service: IssueProviderIntegrationService;
cacheExpiry: number;
constructor(service: IssueProviderIntegrationService, cacheExpiry?: number) {
this.service = service;
this.cacheExpiry = cacheExpiry ?? Minute.seconds;
}
abstract fetchSources(
integration: Integration<IntegrationType.Embed>
): Promise<IssueSource[]>;
abstract createIssue(
title: string,
source: IssueSource,
actor: User
): Promise<CreateIssueResponse | undefined>;
}
-3
View File
@@ -9,7 +9,6 @@ import Logger from "@server/logging/Logger";
import type BaseProcessor from "@server/queues/processors/BaseProcessor";
import type BaseTask from "@server/queues/tasks/BaseTask";
import { UnfurlSignature, UninstallSignature } from "@server/types";
import { BaseIssueProvider } from "./IssueProvider";
export enum PluginPriority {
VeryHigh = 0,
@@ -26,7 +25,6 @@ export enum Hook {
API = "api",
AuthProvider = "authProvider",
EmailTemplate = "emailTemplate",
IssueProvider = "issueProvider",
Processor = "processor",
Task = "task",
UnfurlProvider = "unfurl",
@@ -41,7 +39,6 @@ type PluginValueMap = {
[Hook.API]: Router;
[Hook.AuthProvider]: { router: Router; id: string };
[Hook.EmailTemplate]: typeof BaseEmail;
[Hook.IssueProvider]: BaseIssueProvider;
[Hook.Processor]: typeof BaseProcessor;
[Hook.Task]: typeof BaseTask<any>;
[Hook.Uninstall]: UninstallSignature;
-2
View File
@@ -287,8 +287,6 @@ export const MentionPullRequest = observer((props: IssuePrProps) => {
);
});
export const MentionPlaceholder = () => <MentionLoading className="mention" />;
const MentionLoading = ({ className }: { className: string }) => {
const { t } = useTranslation();
+3 -1
View File
@@ -128,6 +128,7 @@ const mathStyle = (props: Props) => css`
math-block .math-src .ProseMirror {
width: 100%;
display: block;
outline: none;
}
math-block .katex-display {
@@ -334,7 +335,7 @@ width: 100%;
box-sizing: content-box;
}
.ProseMirror {
& > .ProseMirror {
position: relative;
outline: none;
word-wrap: break-word;
@@ -707,6 +708,7 @@ img.ProseMirror-separator {
resize: none;
user-select: text;
margin: 0 auto !important;
max-width: 100vw;
}
.ProseMirror[contenteditable="false"] {
+45 -33
View File
@@ -4,7 +4,7 @@ import { Node } from "prosemirror-model";
import { Plugin, PluginKey, Transaction } from "prosemirror-state";
import { Decoration, DecorationSet } from "prosemirror-view";
import refractor from "refractor/core";
import { getRefractorLangForLanguage } from "../lib/code";
import { getLoaderForLanguage, getRefractorLangForLanguage } from "../lib/code";
import { isRemoteTransaction } from "../lib/multiplayer";
import { findBlockNodes } from "../queries/findChildren";
@@ -15,32 +15,42 @@ type ParsedNode = {
const cache: Record<number, { node: Node; decorations: Decoration[] }> = {};
const languagesToImport = new Set<string>();
const languagePromises: Record<
string,
Promise<string | undefined> | undefined
> = {};
async function loadLanguage(language: string) {
if (!language || refractor.registered(language)) {
return;
}
try {
// @ts-expect-error we are adding a module to the window object to work
// around the fact that refractor doesn't export ESM but import expects it.
// See the rules of dynamic imports:
// https://github.com/rollup/plugins/blob/e1a5ef99f1578eb38a8c87563cb9651db228f3bd/packages/dynamic-import-vars/README.md#limitations
window.module ??= {};
return import(`../../../node_modules/refractor/lang/${language}.js`).then(
() => {
refractor.register(window.module.exports);
return language;
}
);
} catch (err) {
// It will retry loading the language on the next render
// eslint-disable-next-line no-console
console.error(
`Failed to load language ${language} for code highlighting`,
err
);
if (languagePromises[language]) {
return languagePromises[language];
}
return;
const loader = getLoaderForLanguage(language);
if (!loader) {
return;
}
languagePromises[language] = loader()
.then((syntax) => {
refractor.register(syntax);
return language;
})
.catch((err) => {
// It will retry loading the language on the next render
// eslint-disable-next-line no-console
console.error(
`Failed to load language ${language} for code highlighting`,
err
);
delete languagePromises[language]; // Remove failed promise from cache
return undefined;
});
return languagePromises[language];
}
function getDecorations({
@@ -85,7 +95,8 @@ function getDecorations({
blocks.forEach((block) => {
let startPos = block.pos + 1;
const language = getRefractorLangForLanguage(block.node.attrs.language);
const language = block.node.attrs.language;
const lang = getRefractorLangForLanguage(language);
const lineDecorations = [];
if (!cache[block.pos] || !cache[block.pos].node.eq(block.node)) {
@@ -119,12 +130,12 @@ function getDecorations({
decorations: lineDecorations,
};
if (!language) {
if (!lang) {
// do nothing
} else if (refractor.registered(language)) {
} else if (refractor.registered(lang)) {
languagesToImport.delete(language);
const nodes = refractor.highlight(block.node.textContent, language);
const nodes = refractor.highlight(block.node.textContent, lang);
const newDecorations = parseNodes(nodes)
.map((node: ParsedNode) => {
const from = startPos;
@@ -229,14 +240,15 @@ export function CodeHighlighting({
}
void Promise.all([...languagesToImport].map(loadLanguage)).then(
(language) =>
languagesToImport.size
? view.dispatch(
view.state.tr.setMeta("codeHighlighting", {
langLoaded: language,
})
)
: null
(language) => {
if (language && languagesToImport.size) {
view.dispatch(
view.state.tr.setMeta("codeHighlighting", {
langLoaded: language,
})
);
}
}
);
},
};
+304 -58
View File
@@ -1,3 +1,4 @@
import type { RefractorSyntax } from "refractor";
import Storage from "../../utils/Storage";
const RecentlyUsedStorageKey = "rme-code-language";
@@ -5,6 +6,12 @@ const StorageKey = "frequent-code-languages";
const frequentLanguagesToGet = 5;
const frequentLanguagesToTrack = 10;
type CodeLanguage = {
lang: string;
label: string;
loader?: () => Promise<RefractorSyntax>;
};
/**
* List of supported code languages.
*
@@ -12,65 +19,295 @@ const frequentLanguagesToTrack = 10;
* language identifier used by Refractor. Note mismatches such as `markup` and
* `mermaid`.
*/
export const codeLanguages = {
export const codeLanguages: Record<string, CodeLanguage> = {
none: { lang: "", label: "Plain text" },
bash: { lang: "bash", label: "Bash" },
clike: { lang: "clike", label: "C" },
cpp: { lang: "cpp", label: "C++" },
csharp: { lang: "csharp", label: "C#" },
css: { lang: "css", label: "CSS" },
csv: { lang: "csv", label: "CSV" },
docker: { lang: "docker", label: "Docker" },
elixir: { lang: "elixir", label: "Elixir" },
erb: { lang: "erb", label: "ERB" },
erlang: { lang: "erlang", label: "Erlang" },
go: { lang: "go", label: "Go" },
graphql: { lang: "graphql", label: "GraphQL" },
groovy: { lang: "groovy", label: "Groovy" },
haskell: { lang: "haskell", label: "Haskell" },
hcl: { lang: "hcl", label: "HCL" },
markup: { lang: "markup", label: "HTML" },
ini: { lang: "ini", label: "INI" },
java: { lang: "java", label: "Java" },
javascript: { lang: "javascript", label: "JavaScript" },
json: { lang: "json", label: "JSON" },
jsx: { lang: "jsx", label: "JSX" },
kotlin: { lang: "kotlin", label: "Kotlin" },
kusto: { lang: "kusto", label: "Kusto" },
lisp: { lang: "lisp", label: "Lisp" },
lua: { lang: "lua", label: "Lua" },
makefile: { lang: "makefile", label: "Makefile" },
markdown: { lang: "markdown", label: "Markdown" },
mermaidjs: { lang: "mermaid", label: "Mermaid Diagram" },
nginx: { lang: "nginx", label: "Nginx" },
nix: { lang: "nix", label: "Nix" },
objectivec: { lang: "objectivec", label: "Objective-C" },
ocaml: { lang: "ocaml", label: "OCaml" },
perl: { lang: "perl", label: "Perl" },
php: { lang: "php", label: "PHP" },
powershell: { lang: "powershell", label: "Powershell" },
protobuf: { lang: "protobuf", label: "Protobuf" },
python: { lang: "python", label: "Python" },
r: { lang: "r", label: "R" },
regex: { lang: "regex", label: "Regex" },
ruby: { lang: "ruby", label: "Ruby" },
rust: { lang: "rust", label: "Rust" },
scala: { lang: "scala", label: "Scala" },
sass: { lang: "sass", label: "Sass" },
scss: { lang: "scss", label: "SCSS" },
"splunk-spl": { lang: "splunk-spl", label: "Splunk SPL" },
sql: { lang: "sql", label: "SQL" },
solidity: { lang: "solidity", label: "Solidity" },
swift: { lang: "swift", label: "Swift" },
toml: { lang: "toml", label: "TOML" },
tsx: { lang: "tsx", label: "TSX" },
typescript: { lang: "typescript", label: "TypeScript" },
vb: { lang: "vb", label: "Visual Basic" },
verilog: { lang: "verilog", label: "Verilog" },
vhdl: { lang: "vhdl", label: "VHDL" },
yaml: { lang: "yaml", label: "YAML" },
xml: { lang: "markup", label: "XML" },
zig: { lang: "zig", label: "Zig" },
bash: {
lang: "bash",
label: "Bash",
loader: () => import("refractor/lang/bash").then((m) => m.default),
},
clike: {
lang: "clike",
label: "C",
loader: () => import("refractor/lang/clike").then((m) => m.default),
},
cpp: {
lang: "cpp",
label: "C++",
loader: () => import("refractor/lang/cpp").then((m) => m.default),
},
csharp: {
lang: "csharp",
label: "C#",
loader: () => import("refractor/lang/csharp").then((m) => m.default),
},
css: {
lang: "css",
label: "CSS",
loader: () => import("refractor/lang/css").then((m) => m.default),
},
csv: {
lang: "csv",
label: "CSV",
loader: () => import("refractor/lang/csv").then((m) => m.default),
},
docker: {
lang: "docker",
label: "Docker",
loader: () => import("refractor/lang/docker").then((m) => m.default),
},
elixir: {
lang: "elixir",
label: "Elixir",
loader: () => import("refractor/lang/elixir").then((m) => m.default),
},
erb: {
lang: "erb",
label: "ERB",
loader: () => import("refractor/lang/erb").then((m) => m.default),
},
erlang: {
lang: "erlang",
label: "Erlang",
loader: () => import("refractor/lang/erlang").then((m) => m.default),
},
go: {
lang: "go",
label: "Go",
loader: () => import("refractor/lang/go").then((m) => m.default),
},
graphql: {
lang: "graphql",
label: "GraphQL",
loader: () => import("refractor/lang/graphql").then((m) => m.default),
},
groovy: {
lang: "groovy",
label: "Groovy",
loader: () => import("refractor/lang/groovy").then((m) => m.default),
},
haskell: {
lang: "haskell",
label: "Haskell",
loader: () => import("refractor/lang/haskell").then((m) => m.default),
},
hcl: {
lang: "hcl",
label: "HCL",
loader: () => import("refractor/lang/hcl").then((m) => m.default),
},
markup: {
lang: "markup",
label: "HTML",
loader: () => import("refractor/lang/markup").then((m) => m.default),
},
ini: {
lang: "ini",
label: "INI",
loader: () => import("refractor/lang/ini").then((m) => m.default),
},
java: {
lang: "java",
label: "Java",
loader: () => import("refractor/lang/java").then((m) => m.default),
},
javascript: {
lang: "javascript",
label: "JavaScript",
loader: () => import("refractor/lang/javascript").then((m) => m.default),
},
json: {
lang: "json",
label: "JSON",
loader: () => import("refractor/lang/json").then((m) => m.default),
},
jsx: {
lang: "jsx",
label: "JSX",
loader: () => import("refractor/lang/jsx").then((m) => m.default),
},
kotlin: {
lang: "kotlin",
label: "Kotlin",
loader: () => import("refractor/lang/kotlin").then((m) => m.default),
},
kusto: {
lang: "kusto",
label: "Kusto",
// @ts-expect-error Mermaid is not in types but exists
loader: () => import("refractor/lang/kusto").then((m) => m.default),
},
lisp: {
lang: "lisp",
label: "Lisp",
loader: () => import("refractor/lang/lisp").then((m) => m.default),
},
lua: {
lang: "lua",
label: "Lua",
loader: () => import("refractor/lang/lua").then((m) => m.default),
},
makefile: {
lang: "makefile",
label: "Makefile",
loader: () => import("refractor/lang/makefile").then((m) => m.default),
},
markdown: {
lang: "markdown",
label: "Markdown",
loader: () => import("refractor/lang/markdown").then((m) => m.default),
},
mermaidjs: {
lang: "mermaid",
label: "Mermaid Diagram",
// @ts-expect-error Mermaid is not in types but exists
loader: () => import("refractor/lang/mermaid").then((m) => m.default),
},
nginx: {
lang: "nginx",
label: "Nginx",
loader: () => import("refractor/lang/nginx").then((m) => m.default),
},
nix: {
lang: "nix",
label: "Nix",
loader: () => import("refractor/lang/nix").then((m) => m.default),
},
objectivec: {
lang: "objectivec",
label: "Objective-C",
loader: () => import("refractor/lang/objectivec").then((m) => m.default),
},
ocaml: {
lang: "ocaml",
label: "OCaml",
loader: () => import("refractor/lang/ocaml").then((m) => m.default),
},
perl: {
lang: "perl",
label: "Perl",
loader: () => import("refractor/lang/perl").then((m) => m.default),
},
php: {
lang: "php",
label: "PHP",
loader: () => import("refractor/lang/php").then((m) => m.default),
},
powershell: {
lang: "powershell",
label: "Powershell",
loader: () => import("refractor/lang/powershell").then((m) => m.default),
},
protobuf: {
lang: "protobuf",
label: "Protobuf",
loader: () => import("refractor/lang/protobuf").then((m) => m.default),
},
python: {
lang: "python",
label: "Python",
loader: () => import("refractor/lang/python").then((m) => m.default),
},
r: {
lang: "r",
label: "R",
loader: () => import("refractor/lang/r").then((m) => m.default),
},
regex: {
lang: "regex",
label: "Regex",
loader: () => import("refractor/lang/regex").then((m) => m.default),
},
ruby: {
lang: "ruby",
label: "Ruby",
loader: () => import("refractor/lang/ruby").then((m) => m.default),
},
rust: {
lang: "rust",
label: "Rust",
loader: () => import("refractor/lang/rust").then((m) => m.default),
},
scala: {
lang: "scala",
label: "Scala",
loader: () => import("refractor/lang/scala").then((m) => m.default),
},
sass: {
lang: "sass",
label: "Sass",
loader: () => import("refractor/lang/sass").then((m) => m.default),
},
scss: {
lang: "scss",
label: "SCSS",
loader: () => import("refractor/lang/scss").then((m) => m.default),
},
"splunk-spl": {
lang: "splunk-spl",
label: "Splunk SPL",
loader: () => import("refractor/lang/splunk-spl").then((m) => m.default),
},
sql: {
lang: "sql",
label: "SQL",
loader: () => import("refractor/lang/sql").then((m) => m.default),
},
solidity: {
lang: "solidity",
label: "Solidity",
loader: () => import("refractor/lang/solidity").then((m) => m.default),
},
swift: {
lang: "swift",
label: "Swift",
loader: () => import("refractor/lang/swift").then((m) => m.default),
},
toml: {
lang: "toml",
label: "TOML",
loader: () => import("refractor/lang/toml").then((m) => m.default),
},
tsx: {
lang: "tsx",
label: "TSX",
loader: () => import("refractor/lang/tsx").then((m) => m.default),
},
typescript: {
lang: "typescript",
label: "TypeScript",
loader: () => import("refractor/lang/typescript").then((m) => m.default),
},
vb: {
lang: "vbnet",
label: "Visual Basic",
loader: () => import("refractor/lang/vbnet").then((m) => m.default),
},
verilog: {
lang: "verilog",
label: "Verilog",
loader: () => import("refractor/lang/verilog").then((m) => m.default),
},
vhdl: {
lang: "vhdl",
label: "VHDL",
loader: () => import("refractor/lang/vhdl").then((m) => m.default),
},
yaml: {
lang: "yaml",
label: "YAML",
loader: () => import("refractor/lang/yaml").then((m) => m.default),
},
xml: {
lang: "markup",
label: "XML",
loader: () => import("refractor/lang/markup").then((m) => m.default),
},
zig: {
lang: "zig",
label: "Zig",
loader: () => import("refractor/lang/zig").then((m) => m.default),
},
};
/**
@@ -96,6 +333,15 @@ export const getRefractorLangForLanguage = (
): string | undefined =>
codeLanguages[language as keyof typeof codeLanguages]?.lang;
/**
* Get the loader function for a given language.
*
* @param language The language identifier.
* @returns The loader function for the language, or undefined if not available.
*/
export const getLoaderForLanguage = (language: string) =>
codeLanguages[language as keyof typeof codeLanguages]?.loader;
/**
* Set the most recent code language used.
*
+5 -1
View File
@@ -408,10 +408,14 @@ export default class Image extends SimpleImage {
if (!(state.selection instanceof NodeSelection)) {
return false;
}
let layoutClass: string | null = "full-width";
if (state.selection.node.attrs.layoutClass === layoutClass) {
layoutClass = null;
}
const attrs = {
...state.selection.node.attrs,
title: null,
layoutClass: "full-width",
layoutClass,
};
const { selection } = state;
dispatch?.(state.tr.setNodeMarkup(selection.from, undefined, attrs));
+1 -9
View File
@@ -16,17 +16,11 @@ import * as React from "react";
import { Primitive } from "utility-types";
import { v4 as uuidv4 } from "uuid";
import env from "../../env";
import {
MentionPlaceholder,
MentionType,
UnfurlResourceType,
UnfurlResponse,
} from "../../types";
import { MentionType, UnfurlResourceType, UnfurlResponse } from "../../types";
import {
MentionCollection,
MentionDocument,
MentionIssue,
MentionPlaceholder as MentionPlaceholderComp,
MentionPullRequest,
MentionUser,
} from "../components/Mentions";
@@ -147,8 +141,6 @@ export default class Mention extends Node {
onChangeUnfurl={this.handleChangeUnfurl(props)}
/>
);
case MentionPlaceholder:
return <MentionPlaceholderComp />;
default:
return null;
}
+16 -4
View File
@@ -1,5 +1,5 @@
import { NodeType } from "prosemirror-model";
import { EditorState } from "prosemirror-state";
import { EditorState, NodeSelection } from "prosemirror-state";
import { Primitive } from "utility-types";
import { findParentNode } from "./findParentNode";
@@ -25,12 +25,24 @@ export const isNodeActive =
return false;
}
let nodeWithPos;
const { from, to } = state.selection;
const nodeWithPos = findParentNode(
if (
state.selection instanceof NodeSelection &&
state.selection.node.type === type &&
state.selection.node.hasMarkup(type, {
...state.selection.node.attrs,
...attrs,
})
) {
nodeWithPos = { pos: from, node: state.selection.node };
}
nodeWithPos ??= findParentNode(
(node) =>
node.type === type &&
(!attrs ||
Object.keys(attrs).every((key) => node.attrs[key] === attrs[key]))
(!attrs || node.hasMarkup(type, { ...node.attrs, ...attrs }))
)(state.selection);
if (!nodeWithPos) {
@@ -520,7 +520,6 @@
"Groups": "Groups",
"Shared Links": "Shared Links",
"Import": "Import",
"Self Hosted": "Self Hosted",
"Integrations": "Integrations",
"Revoke token": "Revoke token",
"Revoke": "Revoke",
@@ -1045,10 +1044,6 @@
"Allow editors to create new collections within the workspace": "Allow editors to create new collections within the workspace",
"Workspace creation": "Workspace creation",
"Allow editors to create new workspaces": "Allow editors to create new workspaces",
"Draw.io deployment": "Draw.io deployment",
"Add your self-hosted draw.io installation url here to enable automatic embedding of diagrams within documents.": "Add your self-hosted draw.io installation url here to enable automatic embedding of diagrams within documents.",
"Grist deployment": "Grist deployment",
"Add your self-hosted grist installation URL here.": "Add your self-hosted grist installation URL here.",
"Could not load shares": "Could not load shares",
"Sharing is currently disabled.": "Sharing is currently disabled.",
"You can globally enable and disable public document sharing in the <em>security settings</em>.": "You can globally enable and disable public document sharing in the <em>security settings</em>.",
-13
View File
@@ -3,7 +3,6 @@ import {
CollectionPermission,
type ImportableIntegrationService,
IntegrationService,
IssueProviderIntegrationService,
ProsemirrorDoc,
} from "./types";
import { PageType } from "plugins/notion/shared/types";
@@ -58,15 +57,3 @@ export type ImportTaskOutput = {
createdAt?: Date;
updatedAt?: Date;
}[];
export const IssueSource = z.object({
id: z.string().nonempty(),
name: z.string().nonempty(),
owner: z.object({
id: z.string().nonempty(),
name: z.string().nonempty(),
}),
service: z.nativeEnum(IssueProviderIntegrationService),
});
export type IssueSource = z.infer<typeof IssueSource>;
-11
View File
@@ -79,8 +79,6 @@ export enum MentionType {
PullRequest = "pull_request",
}
export const MentionPlaceholder = "mention_placeholder";
export type PublicEnv = {
ROOT_SHARE_ID?: string;
analytics: {
@@ -148,15 +146,6 @@ export const UserCreatableIntegrationService = {
Umami: IntegrationService.Umami,
} as const;
export type IssueProviderIntegrationService = Extract<
IntegrationService,
IntegrationService.GitHub
>;
export const IssueProviderIntegrationService = {
Notion: IntegrationService.GitHub,
} as const;
export enum CollectionPermission {
Read = "read",
ReadWrite = "read_write",
+106 -106
View File
@@ -105,32 +105,32 @@
"@smithy/util-utf8" "^2.0.0"
tslib "^2.6.2"
"@aws-sdk/client-s3@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.782.0.tgz#ed807a1cd9a3e7a55bf41dfb486da0bbd1f4852d"
integrity sha512-V6JR2JAGYQY7J8wk5un5n/ja2nfCUyyoRCF8Du8JL91NGI8i41Mdr/TzuOGwTgFl6RSXb/ge1K1jk30OH4MugQ==
"@aws-sdk/client-s3@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.787.0.tgz#ebb55ec36cd8f0b7e5e89e48c4d1b6ed1f6156dc"
integrity sha512-eGLCWkN0NlntJ9yPU6OKUggVS4cFvuZJog+cFg1KD5hniLqz7Y0YRtB4uBxW212fK3XCfddgyscEOEeHaTQQTw==
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.775.0"
"@aws-sdk/credential-provider-node" "3.782.0"
"@aws-sdk/credential-provider-node" "3.787.0"
"@aws-sdk/middleware-bucket-endpoint" "3.775.0"
"@aws-sdk/middleware-expect-continue" "3.775.0"
"@aws-sdk/middleware-flexible-checksums" "3.775.0"
"@aws-sdk/middleware-flexible-checksums" "3.787.0"
"@aws-sdk/middleware-host-header" "3.775.0"
"@aws-sdk/middleware-location-constraint" "3.775.0"
"@aws-sdk/middleware-logger" "3.775.0"
"@aws-sdk/middleware-recursion-detection" "3.775.0"
"@aws-sdk/middleware-sdk-s3" "3.775.0"
"@aws-sdk/middleware-ssec" "3.775.0"
"@aws-sdk/middleware-user-agent" "3.782.0"
"@aws-sdk/middleware-user-agent" "3.787.0"
"@aws-sdk/region-config-resolver" "3.775.0"
"@aws-sdk/signature-v4-multi-region" "3.775.0"
"@aws-sdk/types" "3.775.0"
"@aws-sdk/util-endpoints" "3.782.0"
"@aws-sdk/util-endpoints" "3.787.0"
"@aws-sdk/util-user-agent-browser" "3.775.0"
"@aws-sdk/util-user-agent-node" "3.782.0"
"@aws-sdk/util-user-agent-node" "3.787.0"
"@aws-sdk/xml-builder" "3.775.0"
"@smithy/config-resolver" "^4.1.0"
"@smithy/core" "^3.2.0"
@@ -167,10 +167,10 @@
"@smithy/util-waiter" "^4.0.3"
tslib "^2.6.2"
"@aws-sdk/client-sso@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.782.0.tgz#072cbb23a90ec6fd53145ff75bef8329a857f362"
integrity sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg==
"@aws-sdk/client-sso@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.787.0.tgz#39f1182296b586cb957b449b5f0dabd8f378cf1a"
integrity sha512-L8R+Mh258G0DC73ktpSVrG4TT9i2vmDLecARTDR/4q5sRivdDQSL5bUp3LKcK80Bx+FRw3UETIlX6mYMLL9PJQ==
dependencies:
"@aws-crypto/sha256-browser" "5.2.0"
"@aws-crypto/sha256-js" "5.2.0"
@@ -178,12 +178,12 @@
"@aws-sdk/middleware-host-header" "3.775.0"
"@aws-sdk/middleware-logger" "3.775.0"
"@aws-sdk/middleware-recursion-detection" "3.775.0"
"@aws-sdk/middleware-user-agent" "3.782.0"
"@aws-sdk/middleware-user-agent" "3.787.0"
"@aws-sdk/region-config-resolver" "3.775.0"
"@aws-sdk/types" "3.775.0"
"@aws-sdk/util-endpoints" "3.782.0"
"@aws-sdk/util-endpoints" "3.787.0"
"@aws-sdk/util-user-agent-browser" "3.775.0"
"@aws-sdk/util-user-agent-node" "3.782.0"
"@aws-sdk/util-user-agent-node" "3.787.0"
"@smithy/config-resolver" "^4.1.0"
"@smithy/core" "^3.2.0"
"@smithy/fetch-http-handler" "^5.0.2"
@@ -255,18 +255,18 @@
"@smithy/util-stream" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/credential-provider-ini@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.782.0.tgz#4ffd90f6b3b6b34d1dabcba875e5d00fc5da23f1"
integrity sha512-wd4KdRy2YjLsE4Y7pz00470Iip06GlRHkG4dyLW7/hFMzEO2o7ixswCWp6J2VGZVAX64acknlv2Q0z02ebjmhw==
"@aws-sdk/credential-provider-ini@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.787.0.tgz#906ece004141462ae695504b6c07d1200688fd6c"
integrity sha512-hc2taRoDlXn2uuNuHWDJljVWYrp3r9JF1a/8XmOAZhVUNY+ImeeStylHXhXXKEA4JOjW+5PdJj0f1UDkVCHJiQ==
dependencies:
"@aws-sdk/core" "3.775.0"
"@aws-sdk/credential-provider-env" "3.775.0"
"@aws-sdk/credential-provider-http" "3.775.0"
"@aws-sdk/credential-provider-process" "3.775.0"
"@aws-sdk/credential-provider-sso" "3.782.0"
"@aws-sdk/credential-provider-web-identity" "3.782.0"
"@aws-sdk/nested-clients" "3.782.0"
"@aws-sdk/credential-provider-sso" "3.787.0"
"@aws-sdk/credential-provider-web-identity" "3.787.0"
"@aws-sdk/nested-clients" "3.787.0"
"@aws-sdk/types" "3.775.0"
"@smithy/credential-provider-imds" "^4.0.2"
"@smithy/property-provider" "^4.0.2"
@@ -274,17 +274,17 @@
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/credential-provider-node@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.782.0.tgz#81a798710d0567b26cd20a105790b49586e68d40"
integrity sha512-HZiAF+TCEyKjju9dgysjiPIWgt/+VerGaeEp18mvKLNfgKz1d+/82A2USEpNKTze7v3cMFASx3CvL8yYyF7mJw==
"@aws-sdk/credential-provider-node@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.787.0.tgz#3e5cdafb0fecca25b7430f848cbca85000b25c33"
integrity sha512-JioVi44B1vDMaK2CdzqimwvJD3uzvzbQhaEWXsGMBcMcNHajXAXf08EF50JG3ZhLrhhUsT1ObXpbTaPINOhh+g==
dependencies:
"@aws-sdk/credential-provider-env" "3.775.0"
"@aws-sdk/credential-provider-http" "3.775.0"
"@aws-sdk/credential-provider-ini" "3.782.0"
"@aws-sdk/credential-provider-ini" "3.787.0"
"@aws-sdk/credential-provider-process" "3.775.0"
"@aws-sdk/credential-provider-sso" "3.782.0"
"@aws-sdk/credential-provider-web-identity" "3.782.0"
"@aws-sdk/credential-provider-sso" "3.787.0"
"@aws-sdk/credential-provider-web-identity" "3.787.0"
"@aws-sdk/types" "3.775.0"
"@smithy/credential-provider-imds" "^4.0.2"
"@smithy/property-provider" "^4.0.2"
@@ -304,45 +304,45 @@
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/credential-provider-sso@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.782.0.tgz#f644884cae368204f750c35d8a61a04d77c02674"
integrity sha512-1y1ucxTtTIGDSNSNxriQY8msinilhe9gGvQpUDYW9gboyC7WQJPDw66imy258V6osdtdi+xoHzVCbCz3WhosMQ==
"@aws-sdk/credential-provider-sso@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.787.0.tgz#77ab6c01e4497d7ff2e6c7f081f3d8695744884b"
integrity sha512-fHc08bsvwm4+dEMEQKnQ7c1irEQmmxbgS+Fq41y09pPvPh31nAhoMcjBSTWAaPHvvsRbTYvmP4Mf12ZGr8/nfg==
dependencies:
"@aws-sdk/client-sso" "3.782.0"
"@aws-sdk/client-sso" "3.787.0"
"@aws-sdk/core" "3.775.0"
"@aws-sdk/token-providers" "3.782.0"
"@aws-sdk/token-providers" "3.787.0"
"@aws-sdk/types" "3.775.0"
"@smithy/property-provider" "^4.0.2"
"@smithy/shared-ini-file-loader" "^4.0.2"
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/credential-provider-web-identity@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.782.0.tgz#5dbab53a7b49dcf8390d71415855e78b911a4742"
integrity sha512-xCna0opVPaueEbJoclj5C6OpDNi0Gynj+4d7tnuXGgQhTHPyAz8ZyClkVqpi5qvHTgxROdUEDxWqEO5jqRHZHQ==
"@aws-sdk/credential-provider-web-identity@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.787.0.tgz#d492d1f4a90b70f3a71a65f11b8d3ef79fb2759e"
integrity sha512-SobmCwNbk6TfEsF283mZPQEI5vV2j6eY5tOCj8Er4Lzraxu9fBPADV+Bib2A8F6jlB1lMPJzOuDCbEasSt/RIw==
dependencies:
"@aws-sdk/core" "3.775.0"
"@aws-sdk/nested-clients" "3.782.0"
"@aws-sdk/nested-clients" "3.787.0"
"@aws-sdk/types" "3.775.0"
"@smithy/property-provider" "^4.0.2"
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/crt-loader@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/crt-loader/-/crt-loader-3.782.0.tgz#a2f8d34a22155d9730426cb727694c54dc0299c4"
integrity sha512-S3fDfZfVhGBvyseJEIcVwa7pumyjqhMKIGHYzcxpthBptJ9gcpYgyHSC6Z7hguQfsfurawe7MNvFnx8O6oLFLA==
"@aws-sdk/crt-loader@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/crt-loader/-/crt-loader-3.787.0.tgz#570c7e3cc79d20b2391beb1dafe2215343a9ac29"
integrity sha512-NQWFDkYF/lzz2m3icdVr+a0Ua/fN4dij3GPwU+Hr/nzrFR6z7txG3U4m2zkSELJ0PDT4k/1NsgmnQlpyxg0NDg==
dependencies:
"@aws-sdk/util-user-agent-node" "3.782.0"
"@aws-sdk/util-user-agent-node" "3.787.0"
aws-crt "^1.24.0"
tslib "^2.6.2"
"@aws-sdk/lib-storage@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.782.0.tgz#721fd62f01805a9eed661f19f556dc7c6f17a70e"
integrity sha512-UQYnIzpBReLko2XhDgG/rWpoHTWv4/zqUNl4XJXZRo9akLzrxGKtPrp5nJ4OLUkH3tIm1cvmI3XlSjHUW/OxWw==
"@aws-sdk/lib-storage@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.787.0.tgz#fb7af2a869c31948073e7480ed1003c9755d35c4"
integrity sha512-iIMbmd9uASD3KFfGeH/OeVo4oxAeqbuXDmhoVEJb4M0hZ4yJ2o9v1V0TEaHwIXcrdlc0H8rCpd9opmV74MBxrA==
dependencies:
"@smithy/abort-controller" "^4.0.2"
"@smithy/middleware-endpoint" "^4.1.0"
@@ -375,10 +375,10 @@
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/middleware-flexible-checksums@3.775.0":
version "3.775.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.775.0.tgz#d76a5eabb13ddc3d29b834335387ebe321e0291e"
integrity sha512-OmHLfRIb7IIXsf9/X/pMOlcSV3gzW/MmtPSZTkrz5jCTKzWXd7eRoyOJqewjsaC6KMAxIpNU77FoAd16jOZ21A==
"@aws-sdk/middleware-flexible-checksums@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.787.0.tgz#bc887dcfd0193a41eef6b58c18c682478a108b07"
integrity sha512-X71qEwWoixFmwowWzlPoZUR3u1CWJ7iAzU0EzIxqmPhQpQJLFmdL1+SRjqATynDPZQzLs1a5HBtPT++EnZ+Quw==
dependencies:
"@aws-crypto/crc32" "5.2.0"
"@aws-crypto/crc32c" "5.2.0"
@@ -461,23 +461,23 @@
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/middleware-user-agent@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.782.0.tgz#60e47e365a39cfa64aa81c3c881896face74da45"
integrity sha512-i32H2R6IItX+bQ2p4+v2gGO2jA80jQoJO2m1xjU9rYWQW3+ErWy4I5YIuQHTBfb6hSdAHbaRfqPDgbv9J2rjEg==
"@aws-sdk/middleware-user-agent@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.787.0.tgz#3d657c0ba1aec72bca079f4691ba20f25569fcfc"
integrity sha512-Lnfj8SmPLYtrDFthNIaNj66zZsBCam+E4XiUDr55DIHTGstH6qZ/q6vg0GfbukxwSmUcGMwSR4Qbn8rb8yd77g==
dependencies:
"@aws-sdk/core" "3.775.0"
"@aws-sdk/types" "3.775.0"
"@aws-sdk/util-endpoints" "3.782.0"
"@aws-sdk/util-endpoints" "3.787.0"
"@smithy/core" "^3.2.0"
"@smithy/protocol-http" "^5.1.0"
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/nested-clients@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.782.0.tgz#73f56fc4d22e1be342e00b7eba9de4d192521a05"
integrity sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA==
"@aws-sdk/nested-clients@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/nested-clients/-/nested-clients-3.787.0.tgz#e8a5a6e7d0b599a7f9f15b900d3223ad080b0a81"
integrity sha512-xk03q1xpKNHgbuo+trEf1dFrI239kuMmjKKsqLEsHlAZbuFq4yRGMlHBrVMnKYOPBhVFDS/VineM991XI52fKg==
dependencies:
"@aws-crypto/sha256-browser" "5.2.0"
"@aws-crypto/sha256-js" "5.2.0"
@@ -485,12 +485,12 @@
"@aws-sdk/middleware-host-header" "3.775.0"
"@aws-sdk/middleware-logger" "3.775.0"
"@aws-sdk/middleware-recursion-detection" "3.775.0"
"@aws-sdk/middleware-user-agent" "3.782.0"
"@aws-sdk/middleware-user-agent" "3.787.0"
"@aws-sdk/region-config-resolver" "3.775.0"
"@aws-sdk/types" "3.775.0"
"@aws-sdk/util-endpoints" "3.782.0"
"@aws-sdk/util-endpoints" "3.787.0"
"@aws-sdk/util-user-agent-browser" "3.775.0"
"@aws-sdk/util-user-agent-node" "3.782.0"
"@aws-sdk/util-user-agent-node" "3.787.0"
"@smithy/config-resolver" "^4.1.0"
"@smithy/core" "^3.2.0"
"@smithy/fetch-http-handler" "^5.0.2"
@@ -530,12 +530,12 @@
"@smithy/util-middleware" "^4.0.2"
tslib "^2.6.2"
"@aws-sdk/s3-presigned-post@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.782.0.tgz#bf6fd60c20a511e6e039c6740f1a80a078d2469e"
integrity sha512-JlgZ3wZ3wId0zT0hiEkL6GapAWWh6m4P3uJsDWpcSv1yOA+9bqA3fLcxofnNodL2/7fxam2elI+jVvx/wXIqlQ==
"@aws-sdk/s3-presigned-post@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-presigned-post/-/s3-presigned-post-3.787.0.tgz#06b73ff35afc165a3208fc9a858b12a1e53d5148"
integrity sha512-/mM9VvdjC5fBa1WqlygmjTZ4R2fNsMjSc03JfdtcLWDShFZ61gungGZVSvsBbwh/YcVe1sKOnJz4qBQxl/ey8g==
dependencies:
"@aws-sdk/client-s3" "3.782.0"
"@aws-sdk/client-s3" "3.787.0"
"@aws-sdk/types" "3.775.0"
"@aws-sdk/util-format-url" "3.775.0"
"@smithy/middleware-endpoint" "^4.1.0"
@@ -545,10 +545,10 @@
"@smithy/util-utf8" "^4.0.0"
tslib "^2.6.2"
"@aws-sdk/s3-request-presigner@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.782.0.tgz#bc71d5b9377f5c9e4170368da56745bb5164e466"
integrity sha512-Er8hdjc9zkxTh15MjdnMYggtUrGknDiuD1FwdW035kn/kwWop587G9rnRa1crhmyKRjLMn0Ki3fsyFUm/943XA==
"@aws-sdk/s3-request-presigner@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.787.0.tgz#1a8eadf330446e53cdca11fa0c08ae3d45a00f9c"
integrity sha512-WBm0AS3RRURNN0yjYXHaiI692boVwWXGt3RLdI7tYBX58E1Zb5nzC8rlk81O9Xe7ZTgTC1KCr8y4+jcBD+zwJg==
dependencies:
"@aws-sdk/signature-v4-multi-region" "3.775.0"
"@aws-sdk/types" "3.775.0"
@@ -559,12 +559,12 @@
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/signature-v4-crt@^3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-crt/-/signature-v4-crt-3.782.0.tgz#c90ef0f30bd73a0b9fcdaf31049a2adc7c15b405"
integrity sha512-QuVSoJITPZxK0lBA2uIDQFjvLdqEvjXrnLYGpRIvIoHdL4RtcDoad9NRKP6Lb34AoB4tubVF6g85mNNOVsqVAw==
"@aws-sdk/signature-v4-crt@^3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-crt/-/signature-v4-crt-3.787.0.tgz#6932f0f437536fc528d4cc45a79daf08819d64a3"
integrity sha512-TATbx7B/54UIyLawAM0eTkQfnfn9KlEXV1jymniEHQtsfL68VND9/uFdOp51Ob9eTo5Q3qghH0RMHZaOpRVuGA==
dependencies:
"@aws-sdk/crt-loader" "3.782.0"
"@aws-sdk/crt-loader" "3.787.0"
"@aws-sdk/signature-v4-multi-region" "3.775.0"
"@aws-sdk/types" "3.775.0"
"@smithy/querystring-parser" "^4.0.2"
@@ -585,12 +585,12 @@
"@smithy/types" "^4.2.0"
tslib "^2.6.2"
"@aws-sdk/token-providers@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.782.0.tgz#a6a12f9358f8897f5d1af311da60f90a7d384eac"
integrity sha512-4tPuk/3+THPrzKaXW4jE2R67UyGwHLFizZ47pcjJWbhb78IIJAy94vbeqEQ+veS84KF5TXcU7g5jGTXC0D70Wg==
"@aws-sdk/token-providers@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.787.0.tgz#18c761fb21ee25c8c3a35703876f0c733b4ae743"
integrity sha512-d7/NIqxq308Zg0RPMNrmn0QvzniL4Hx8Qdwzr6YZWLYAbUSvZYS2ppLR3BFWSkV6SsTJUx8BuDaj3P8vttkrog==
dependencies:
"@aws-sdk/nested-clients" "3.782.0"
"@aws-sdk/nested-clients" "3.787.0"
"@aws-sdk/types" "3.775.0"
"@smithy/property-provider" "^4.0.2"
"@smithy/shared-ini-file-loader" "^4.0.2"
@@ -612,10 +612,10 @@
dependencies:
tslib "^2.6.2"
"@aws-sdk/util-endpoints@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.782.0.tgz#3b8de42c5fe951337d070432d4a5eba166c07bb7"
integrity sha512-/RJOAO7o7HI6lEa4ASbFFLHGU9iPK876BhsVfnl54MvApPVYWQ9sHO0anOUim2S5lQTwd/6ghuH3rFYSq/+rdw==
"@aws-sdk/util-endpoints@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.787.0.tgz#1398f0bd87f19e615ae920c73e16d9d5e5cb76d1"
integrity sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==
dependencies:
"@aws-sdk/types" "3.775.0"
"@smithy/types" "^4.2.0"
@@ -649,12 +649,12 @@
bowser "^2.11.0"
tslib "^2.6.2"
"@aws-sdk/util-user-agent-node@3.782.0":
version "3.782.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.782.0.tgz#795f2c22882f1ddbbe83bd324f0ceac1c4b07c89"
integrity sha512-dMFkUBgh2Bxuw8fYZQoH/u3H4afQ12VSkzEi//qFiDTwbKYq+u+RYjc8GLDM6JSK1BShMu5AVR7HD4ap1TYUnA==
"@aws-sdk/util-user-agent-node@3.787.0":
version "3.787.0"
resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.787.0.tgz#58e63e99586cde1c1314f74b94596780321442f5"
integrity sha512-mG7Lz8ydfG4SF9e8WSXiPQ/Lsn3n8A5B5jtPROidafi06I3ckV2WxyMLdwG14m919NoS6IOfWHyRGSqWIwbVKA==
dependencies:
"@aws-sdk/middleware-user-agent" "3.782.0"
"@aws-sdk/middleware-user-agent" "3.787.0"
"@aws-sdk/types" "3.775.0"
"@smithy/node-config-provider" "^4.0.2"
"@smithy/types" "^4.2.0"
@@ -13122,10 +13122,10 @@ prosemirror-model@^1.0.0, prosemirror-model@^1.20.0, prosemirror-model@^1.21.0,
dependencies:
orderedmap "^2.0.0"
prosemirror-schema-list@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz#78b8d25531db48ca9688836dbde50e13ac19a4a1"
integrity sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==
prosemirror-schema-list@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz#5869c8f749e8745c394548bb11820b0feb1e32f5"
integrity sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==
dependencies:
prosemirror-model "^1.0.0"
prosemirror-state "^1.0.0"
@@ -13492,10 +13492,10 @@ react-virtual@^2.8.2:
dependencies:
"@reach/observe-rect" "^1.1.0"
react-virtualized-auto-sizer@^1.0.21:
version "1.0.25"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.25.tgz#b13cbc528ac200be2bd1ffa40c8bb19bcc60ac3f"
integrity sha512-YHsksEGDfsHbHuaBVDYwJmcktblcHGafz4ZVuYPQYuSHMUGjpwmUCrAOcvMSGMwwk1eFWj1M/1GwYpNPuyhaBg==
react-virtualized-auto-sizer@^1.0.26:
version "1.0.26"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz#e9470ef6a778dc4f1d5fd76305fa2d8b610c357a"
integrity sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==
react-waypoint@^10.3.0:
version "10.3.0"
@@ -15263,10 +15263,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
typescript@^5.0.4, typescript@^5.7.3:
version "5.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
typescript@^5.0.4, typescript@^5.8.3:
version "5.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"