Compare commits

...

15 Commits

Author SHA1 Message Date
Tom Moor e01423a2ac perf refactor 2024-11-18 21:26:11 -05:00
Tom Moor 265151cff0 Add inline resolve action on comment threads 2024-11-18 18:42:55 -05:00
Tom Moor 326f733d4c fix: Further improvements to diacritics matching in CMD+F 2024-11-18 18:04:10 -05:00
Tom Moor d4d683c046 fix: Missing space character in invite modal, related #7968 2024-11-18 17:51:49 -05:00
Tom Moor 8204ac343f chore: Upgrade Sentry/AWS 2024-11-18 17:48:36 -05:00
dependabot[bot] cae8de7c7a chore(deps): bump @octokit/auth-app from 6.1.2 to 6.1.3 (#7974)
Bumps [@octokit/auth-app](https://github.com/octokit/auth-app.js) from 6.1.2 to 6.1.3.
- [Release notes](https://github.com/octokit/auth-app.js/releases)
- [Commits](https://github.com/octokit/auth-app.js/compare/v6.1.2...v6.1.3)

---
updated-dependencies:
- dependency-name: "@octokit/auth-app"
  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>
2024-11-18 14:41:05 -08:00
dependabot[bot] 8efa601967 chore(deps-dev): bump @relative-ci/agent from 4.2.12 to 4.2.13 (#7975)
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 4.2.12 to 4.2.13.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v4.2.12...v4.2.13)

---
updated-dependencies:
- dependency-name: "@relative-ci/agent"
  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>
2024-11-18 14:40:53 -08:00
Tom Moor 86c3ea8e9d fix: Copy toolbar positioning 2024-11-17 10:00:52 -05:00
Tom Moor c222782534 Upgrade Mermaid script in exported HTML, related #7964 2024-11-17 09:51:46 -05:00
Tom Moor 19ea7ee52b Remove sourcemap generation in bundle size calc (#7966) 2024-11-16 16:57:19 -08:00
Tom Moor d1de84a07e Reduce build time (#7965)
* Test using xlarge

* wip

* wip
2024-11-16 10:45:14 -08:00
Tom Moor d73b4c55bf Mermaid v11 (#7964)
* mermaid-v11

* fix: Various rendering incompatibilities
2024-11-16 08:10:55 -08:00
Hemachandar 9843c4c995 Improvements around templates (#7952)
* hide new-doc-from-template menu item

* change trash path for deleted templates

* conditional show templates in command bar
2024-11-16 07:48:58 -08:00
dependabot[bot] 685397b057 chore(deps): bump cross-spawn from 7.0.3 to 7.0.5 (#7963)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.5.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.5)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-16 06:43:06 -08:00
Tom Moor 13d37d4207 perf: Fix increase in initial bundle size, prosemirror-transform must be fixed at 1.10.0 until paragraph join is fixed 2024-11-16 09:14:27 -05:00
15 changed files with 1524 additions and 1017 deletions
+8 -6
View File
@@ -4,12 +4,6 @@ defaults: &defaults
working_directory: ~/outline
docker:
- image: cimg/node:20.10
- image: cimg/redis:5.0
- image: cimg/postgres:14.2
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: circle_test
resource_class: large
environment:
NODE_ENV: test
@@ -78,6 +72,14 @@ jobs:
test-server:
<<: *defaults
parallelism: 3
docker:
- image: cimg/node:20.10
- image: cimg/redis:5.0
- image: cimg/postgres:14.2
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: circle_test
steps:
- checkout
- restore_cache:
+24 -5
View File
@@ -131,11 +131,30 @@ export const createDocumentFromTemplate = createAction({
section: DocumentSection,
icon: <NewDocumentIcon />,
keywords: "create",
visible: ({ currentTeamId, activeDocumentId, stores }) =>
!!currentTeamId &&
!!activeDocumentId &&
!!stores.documents.get(activeDocumentId)?.template &&
stores.policies.abilities(currentTeamId).createDocument,
visible: ({
currentTeamId,
activeCollectionId,
activeDocumentId,
stores,
}) => {
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
if (
!currentTeamId ||
!document?.isTemplate ||
!!document?.isDraft ||
!!document?.isDeleted
) {
return false;
}
if (activeCollectionId) {
return stores.policies.abilities(activeCollectionId).createDocument;
}
return stores.policies.abilities(currentTeamId).createDocument;
},
perform: ({ activeCollectionId, activeDocumentId, sidebarContext }) =>
history.push(
newDocumentPath(activeCollectionId, { templateId: activeDocumentId }),
@@ -2,7 +2,11 @@ import { NewDocumentIcon, ShapesIcon } from "outline-icons";
import * as React from "react";
import Icon from "~/components/Icon";
import { createAction } from "~/actions";
import { DocumentSection } from "~/actions/sections";
import {
ActiveCollectionSection,
DocumentSection,
TeamSection,
} from "~/actions/sections";
import useStores from "~/hooks/useStores";
import history from "~/utils/history";
import { newDocumentPath } from "~/utils/routeHelpers";
@@ -16,21 +20,37 @@ const useTemplatesAction = () => {
const actions = React.useMemo(
() =>
documents.templatesAlphabetical.map((item) =>
documents.templatesAlphabetical.map((template) =>
createAction({
name: item.titleWithDefault,
name: template.titleWithDefault,
analyticsName: "New document",
section: DocumentSection,
icon: item.icon ? (
<Icon value={item.icon} color={item.color ?? undefined} />
section: template.isWorkspaceTemplate
? TeamSection
: ActiveCollectionSection,
icon: template.icon ? (
<Icon value={template.icon} color={template.color ?? undefined} />
) : (
<NewDocumentIcon />
),
keywords: "create",
visible: ({ currentTeamId, activeCollectionId, stores }) => {
if (activeCollectionId) {
return (
stores.policies.abilities(activeCollectionId).createDocument &&
(template.collectionId === activeCollectionId ||
template.isWorkspaceTemplate)
);
}
return (
!!currentTeamId &&
stores.policies.abilities(currentTeamId).createDocument &&
template.isWorkspaceTemplate
);
},
perform: ({ activeCollectionId, sidebarContext }) =>
history.push(
newDocumentPath(item.collectionId ?? activeCollectionId, {
templateId: item.id,
newDocumentPath(template.collectionId ?? activeCollectionId, {
templateId: template.id,
}),
{
sidebarContext,
@@ -49,9 +69,15 @@ const useTemplatesAction = () => {
placeholder: ({ t }) => t("Choose a template"),
section: DocumentSection,
icon: <ShapesIcon />,
visible: ({ currentTeamId, stores }) =>
!!currentTeamId &&
stores.policies.abilities(currentTeamId).createDocument,
visible: ({ currentTeamId, activeCollectionId, stores }) => {
if (activeCollectionId) {
return stores.policies.abilities(activeCollectionId).createDocument;
}
return (
!!currentTeamId &&
stores.policies.abilities(currentTeamId).createDocument
);
},
children: () => actions,
}),
[actions]
+16 -13
View File
@@ -2,7 +2,6 @@ import { ReactionIcon } from "outline-icons";
import React from "react";
import { useTranslation } from "react-i18next";
import { PopoverDisclosure, usePopoverState } from "reakit";
import styled from "styled-components";
import EventBoundary from "@shared/components/EventBoundary";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
@@ -11,6 +10,7 @@ import Popover from "~/components/Popover";
import useMobile from "~/hooks/useMobile";
import useOnClickOutside from "~/hooks/useOnClickOutside";
import useWindowSize from "~/hooks/useWindowSize";
import Tooltip from "../Tooltip";
const EmojiPanel = React.lazy(
() => import("~/components/IconPicker/components/EmojiPanel")
@@ -98,15 +98,22 @@ const ReactionPicker: React.FC<Props> = ({
<>
<PopoverDisclosure {...popover}>
{(props) => (
<PopoverButton
{...props}
aria-label={t("Reaction picker")}
className={className}
onClick={handlePopoverButtonClick}
size={size}
<Tooltip
content={t("Add reaction")}
placement="top"
delay={500}
hideOnClick
>
<ReactionIcon size={22} />
</PopoverButton>
<NudeButton
{...props}
aria-label={t("Reaction picker")}
className={className}
onClick={handlePopoverButtonClick}
size={size}
>
<ReactionIcon size={22} />
</NudeButton>
</Tooltip>
)}
</PopoverDisclosure>
<Popover
@@ -151,8 +158,4 @@ const Placeholder = React.memo(
);
Placeholder.displayName = "ReactionPickerPlaceholder";
const PopoverButton = styled(NudeButton)`
border-radius: 50%;
`;
export default ReactionPicker;
+8 -3
View File
@@ -248,14 +248,19 @@ export default class FindAndReplaceExtension extends Extension {
let m;
const search = this.findRegExp;
while ((m = search.exec(deburr(text)))) {
// We construct a string with the text stripped of diacritics plus the original text for
// search allowing to search for diacritics-insensitive matches easily.
while ((m = search.exec(deburr(text) + text))) {
if (m[0] === "") {
break;
}
// Reconstruct the correct match position
const i = m.index > text.length ? m.index - text.length : m.index;
this.results.push({
from: pos + m.index,
to: pos + m.index + m[0].length,
from: pos + i,
to: pos + i + m[0].length,
});
}
} catch (e) {
+2 -1
View File
@@ -254,7 +254,8 @@ export default class Document extends ArchivableModel {
@computed
get path(): string {
const prefix = this.template ? settingsPath("templates") : "/doc";
const prefix =
this.template && !this.isDeleted ? settingsPath("templates") : "/doc";
if (!this.title) {
return `${prefix}/untitled-${this.urlId}`;
@@ -1,6 +1,7 @@
import { differenceInMilliseconds } from "date-fns";
import { action } from "mobx";
import { observer } from "mobx-react";
import { DoneIcon } from "outline-icons";
import { darken } from "polished";
import * as React from "react";
import { useTranslation } from "react-i18next";
@@ -16,10 +17,14 @@ import Comment from "~/models/Comment";
import { Avatar } from "~/components/Avatar";
import ButtonSmall from "~/components/ButtonSmall";
import Flex from "~/components/Flex";
import NudeButton from "~/components/NudeButton";
import ReactionList from "~/components/Reactions/ReactionList";
import ReactionPicker from "~/components/Reactions/ReactionPicker";
import Text from "~/components/Text";
import Time from "~/components/Time";
import Tooltip from "~/components/Tooltip";
import { resolveCommentFactory } from "~/actions/definitions/comments";
import useActionContext from "~/hooks/useActionContext";
import useBoolean from "~/hooks/useBoolean";
import useCurrentUser from "~/hooks/useCurrentUser";
import CommentMenu from "~/menus/CommentMenu";
@@ -242,11 +247,13 @@ function CommentThreadItem({
onRemoveReaction={handleRemoveReaction}
picker={
!comment.isResolved ? (
<StyledReactionPicker
<Action
as={ReactionPicker}
onSelect={handleAddReaction}
onOpen={disableScroll}
onClose={enableScroll}
size={28}
rounded
/>
) : undefined
}
@@ -257,14 +264,20 @@ function CommentThreadItem({
<EventBoundary>
{!isEditing && (
<Actions gap={4} dir={dir}>
{firstOfThread && (
<ResolveButton onUpdate={handleUpdate} comment={comment} />
)}
{!comment.isResolved && (
<StyledReactionPicker
<Action
as={ReactionPicker}
onSelect={handleAddReaction}
onOpen={disableScroll}
onClose={enableScroll}
rounded
/>
)}
<StyledMenu
<Action
as={CommentMenu}
comment={comment}
onEdit={setEditing}
onDelete={handleDelete}
@@ -278,6 +291,38 @@ function CommentThreadItem({
);
}
const ResolveButton = ({
comment,
onUpdate,
}: {
comment: Comment;
onUpdate: (attrs: { resolved: boolean }) => void;
}) => {
const context = useActionContext();
const { t } = useTranslation();
return (
<Tooltip
content={t("Mark as resolved")}
placement="top"
delay={500}
hideOnClick
>
<Action
as={NudeButton}
context={context}
action={resolveCommentFactory({
comment,
onResolve: () => onUpdate({ resolved: true }),
})}
rounded
>
<DoneIcon size={22} outline />
</Action>
</Tooltip>
);
};
const StyledCommentEditor = styled(CommentEditor)`
${(props) =>
!props.readOnly &&
@@ -308,25 +353,13 @@ const Body = styled.form`
border-radius: 2px;
`;
const StyledMenu = styled(CommentMenu)`
color: ${s("textSecondary")};
svg {
fill: currentColor;
opacity: 0.5;
}
&: ${hover}, &[aria-expanded= "true"] {
background: ${s("backgroundQuaternary")};
svg {
opacity: 0.75;
}
}
`;
const StyledReactionPicker = styled(ReactionPicker)`
const Action = styled.span<{ rounded?: boolean }>`
color: ${s("textSecondary")};
${(props) =>
props.rounded &&
css`
border-radius: 50%;
`}
svg {
fill: currentColor;
@@ -352,7 +385,7 @@ const Actions = styled(Flex)<{ dir?: "rtl" | "ltr" }>`
background: ${s("backgroundSecondary")};
padding-left: 4px;
&:has(${StyledReactionPicker}[aria-expanded="true"], ${StyledMenu}[aria-expanded="true"]) {
&:has(${Action}[aria-expanded="true"]) {
opacity: 1;
}
`;
+1 -1
View File
@@ -127,7 +127,7 @@ function Invite({ onSubmit }: Props) {
<Trans>{{ collectionCount }} collections</Trans>
</strong>
</Tooltip>
.
.{" "}
</span>
) : undefined;
+10 -10
View File
@@ -48,11 +48,11 @@
"> 0.25%, not dead"
],
"dependencies": {
"@aws-sdk/client-s3": "3.616.0",
"@aws-sdk/lib-storage": "3.616.0",
"@aws-sdk/s3-presigned-post": "3.616.0",
"@aws-sdk/s3-request-presigner": "3.616.0",
"@aws-sdk/signature-v4-crt": "^3.616.0",
"@aws-sdk/client-s3": "3.693.0",
"@aws-sdk/lib-storage": "3.693.0",
"@aws-sdk/s3-presigned-post": "3.693.0",
"@aws-sdk/s3-request-presigner": "3.693.0",
"@aws-sdk/signature-v4-crt": "^3.693.0",
"@babel/core": "^7.24.7",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/plugin-transform-class-properties": "^7.24.7",
@@ -79,11 +79,11 @@
"@hocuspocus/server": "1.1.2",
"@joplin/turndown-plugin-gfm": "^1.0.49",
"@juggle/resize-observer": "^3.4.0",
"@octokit/auth-app": "^6.1.2",
"@octokit/auth-app": "^6.1.3",
"@outlinewiki/koa-passport": "^4.2.1",
"@outlinewiki/passport-azure-ad-oauth2": "^0.1.0",
"@renderlesskit/react": "^0.11.0",
"@sentry/node": "^7.117.0",
"@sentry/node": "^7.119.0",
"@sentry/react": "^7.119.0",
"@tippyjs/react": "^4.2.6",
"@types/form-data": "^2.5.0",
@@ -150,7 +150,7 @@
"markdown-it": "^13.0.2",
"markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^2.0.0",
"mermaid": "9.3.0",
"mermaid": "11.4.0",
"mime-types": "^2.1.35",
"mobx": "^4.15.4",
"mobx-react": "^6.3.1",
@@ -254,7 +254,7 @@
"@babel/cli": "^7.25.9",
"@babel/preset-typescript": "^7.24.1",
"@faker-js/faker": "^8.4.1",
"@relative-ci/agent": "^4.2.12",
"@relative-ci/agent": "^4.2.13",
"@testing-library/react": "^12.0.0",
"@types/addressparser": "^1.0.3",
"@types/body-scroll-lock": "^3.1.2",
@@ -285,7 +285,6 @@
"@types/markdown-it": "^14.1.2",
"@types/markdown-it-container": "^2.0.9",
"@types/markdown-it-emoji": "^2.0.4",
"@types/mermaid": "^9.2.0",
"@types/mime-types": "^2.1.4",
"@types/natural-sort": "^0.0.24",
"@types/node": "20.14.2",
@@ -360,6 +359,7 @@
"yarn-deduplicate": "^6.0.2"
},
"resolutions": {
"prosemirror-transform": "1.10.0",
"body-scroll-lock": "^4.0.0-beta.0",
"d3": "^7.0.0",
"debug": "4.3.4",
+1 -1
View File
@@ -558,7 +558,7 @@ export class ProsemirrorHelper {
// Inject Mermaid script
if (mermaidElements.length) {
element.innerHTML = `
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({
startOnLoad: true,
fontFamily: "inherit",
+3 -1
View File
@@ -1310,7 +1310,9 @@ mark {
}
.ProseMirror[contenteditable="false"] .code-block[data-language=mermaidjs] {
display: none;
height: 0;
overflow: hidden;
margin: -0.5em 0 0 0;
}
.code-block.with-line-numbers {
+20 -15
View File
@@ -1,6 +1,7 @@
import debounce from "lodash/debounce";
import last from "lodash/last";
import sortBy from "lodash/sortBy";
import type MermaidUnsafe from "mermaid";
import { Node } from "prosemirror-model";
import {
Plugin,
@@ -36,7 +37,7 @@ class Cache {
private static data: Map<string, string> = new Map();
}
let mermaid: typeof import("mermaid")["default"];
let mermaid: typeof MermaidUnsafe;
type RendererFunc = (
block: { node: Node; pos: number },
@@ -81,7 +82,7 @@ class MermaidRenderer {
document.body.appendChild(renderElement);
try {
mermaid = mermaid ?? (await import("mermaid")).default;
mermaid ??= (await import("mermaid")).default;
mermaid.initialize({
startOnLoad: true,
// TODO: Make dynamic based on the width of the editor or remove in
@@ -92,23 +93,25 @@ class MermaidRenderer {
theme: isDark ? "dark" : "default",
darkMode: isDark,
});
mermaid.render(
const { svg, bindFunctions } = await mermaid.render(
`mermaid-diagram-${this.diagramId}`,
text,
(svgCode, bindFunctions) => {
this.currentTextContent = text;
if (text) {
Cache.set(cacheKey, svgCode);
}
element.classList.remove("parse-error", "empty");
element.innerHTML = svgCode;
bindFunctions?.(element);
renderElement.remove();
},
renderElement
// If the element is not visible we use an off-screen element to render the diagram
element.offsetParent === null ? renderElement : element
);
this.currentTextContent = text;
// Cache the rendered SVG so we won't need to calculate it again in the same session
if (text) {
Cache.set(cacheKey, svg);
}
element.classList.remove("parse-error", "empty");
element.innerHTML = svg;
// Allow the user to interact with the diagram
bindFunctions?.(element);
} catch (error) {
renderElement.remove();
const isEmpty = block.node.textContent.trim().length === 0;
if (isEmpty) {
@@ -118,6 +121,8 @@ class MermaidRenderer {
element.innerText = error;
element.classList.add("parse-error");
}
} finally {
renderElement.remove();
}
};
@@ -308,6 +308,7 @@
"{{ firstUsername }} and {{ secondUsername }} reacted with {{ emoji }}": "{{ firstUsername }} and {{ secondUsername }} reacted with {{ emoji }}",
"{{ firstUsername }} and {{ count }} others reacted with {{ emoji }}": "{{ firstUsername }} and {{ count }} other reacted with {{ emoji }}",
"{{ firstUsername }} and {{ count }} others reacted with {{ emoji }}_plural": "{{ firstUsername }} and {{ count }} others reacted with {{ emoji }}",
"Add reaction": "Add reaction",
"Reaction picker": "Reaction picker",
"Could not load reactions": "Could not load reactions",
"Reaction": "Reaction",
+1 -1
View File
@@ -159,7 +159,7 @@ export default () =>
build: {
outDir: "./build/app",
manifest: true,
sourcemap: true,
sourcemap: process.env.CI ? false : "hidden",
minify: "terser",
// Prevent asset inling as it does not conform to CSP rules
assetsInlineLimit: 0,
+1337 -927
View File
File diff suppressed because it is too large Load Diff