Compare commits

..

1 Commits

Author SHA1 Message Date
Salihu 98445e9996 option to show more 'shared with me' documents 2025-10-31 21:09:55 +01:00
32 changed files with 210 additions and 339 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ function Avatar(props: Props) {
<Image onError={handleError} src={src} {...rest} />
) : model ? (
<Initials color={model.color} {...rest}>
{model.initial?.toUpperCase()}
{model.initial}
</Initials>
) : (
<Initials {...rest} />
+1 -21
View File
@@ -117,31 +117,12 @@ const HoverPreviewDesktop = observer(
<Position top={cardTop} left={cardLeft} aria-hidden>
{isVisible ? (
<Animate
initial={{
opacity: 0,
y: -20,
filter: "blur(5px)",
pointerEvents: "none",
}}
initial={{ opacity: 0, y: -20, pointerEvents: "none" }}
animate={{
opacity: 1,
y: 0,
filter: "blur(0px)",
transitionEnd: { pointerEvents: "auto" },
}}
transition={{
y: {
type: "spring",
stiffness: 400,
damping: 25,
},
opacity: {
duration: 0.2,
},
filter: {
duration: 0.2,
},
}}
>
{data.type === UnfurlResourceType.Mention ? (
<HoverPreviewMention
@@ -156,7 +137,6 @@ const HoverPreviewDesktop = observer(
<HoverPreviewGroup
ref={cardRef}
name={data.name}
description={data.description}
memberCount={data.memberCount}
users={data.users}
/>
@@ -1,5 +1,4 @@
import * as React from "react";
import { useTranslation } from "react-i18next";
import { UnfurlResourceType, UnfurlResponse } from "@shared/types";
import { MAX_AVATAR_DISPLAY } from "@shared/constants";
import User from "~/models/User";
@@ -18,30 +17,21 @@ import ErrorBoundary from "../ErrorBoundary";
type Props = Omit<UnfurlResponse[UnfurlResourceType.Group], "type">;
const HoverPreviewGroup = React.forwardRef(function _HoverPreviewGroup(
{ name, description, memberCount, users }: Props,
{ name, memberCount, users }: Props,
ref: React.Ref<HTMLDivElement>
) {
const { t } = useTranslation();
return (
<Preview as="div">
<Card fadeOut={false} ref={ref}>
<CardContent>
<ErrorBoundary showTitle={false} reloadOnChunkMissing={false}>
<Flex column gap={2} align="start">
<Flex
justify="space-between"
gap={4}
style={{ width: "100%" }}
auto
>
<Flex column align="start">
<Title>{name}</Title>
<Info>
{t("{{ count }} members", { count: memberCount })}
</Info>
</Flex>
{users.length > 0 && (
<Title>{name}</Title>
<Info>
{memberCount === 1 ? "1 member" : `${memberCount} members`}
</Info>
{users.length > 0 && (
<Description>
<Facepile
users={users.map(
(member) =>
@@ -56,9 +46,8 @@ const HoverPreviewGroup = React.forwardRef(function _HoverPreviewGroup(
overflow={Math.max(0, memberCount - users.length)}
limit={MAX_AVATAR_DISPLAY}
/>
)}
</Flex>
{description && <Description>{description}</Description>}
</Description>
)}
</Flex>
</ErrorBoundary>
</CardContent>
+4 -15
View File
@@ -23,7 +23,6 @@ import { SharedDocumentLink } from "./components/SharedDocumentLink";
import SidebarButton from "./components/SidebarButton";
import ToggleButton from "./components/ToggleButton";
import { useEffect } from "react";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
type Props = {
share: Share;
@@ -32,16 +31,12 @@ type Props = {
function SharedSidebar({ share }: Props) {
const team = useTeamContext();
const user = useCurrentUser({ rejectOnEmpty: false });
const { ui, documents, collections } = useStores();
const { ui, documents } = useStores();
const { t } = useTranslation();
const teamAvailable = !!team?.name;
const rootNode = share.tree;
const shareId = share.urlId || share.id;
const collection = collections.get(rootNode?.id);
const hideRootNode = collection
? ProsemirrorHelper.isEmptyData(collection?.data)
: false;
useEffect(() => {
ui.tocVisible = share.showTOC;
@@ -59,10 +54,8 @@ function SharedSidebar({ share }: Props) {
image={
<TeamLogo model={team} size={AvatarSize.XLarge} alt={t("Logo")} />
}
onClick={
hideRootNode
? undefined
: () => history.push(user ? homePath() : sharedModelPath(shareId))
onClick={() =>
history.push(user ? homePath() : sharedModelPath(shareId))
}
/>
)}
@@ -79,11 +72,7 @@ function SharedSidebar({ share }: Props) {
</TopSection>
<Section>
{share.collectionId ? (
<SharedCollectionLink
node={rootNode}
shareId={shareId}
hideRootNode={hideRootNode}
/>
<SharedCollectionLink node={rootNode} shareId={shareId} />
) : (
<SharedDocumentLink
index={0}
@@ -8,19 +8,32 @@ import Relative from "./Relative";
import SharedWithMeLink from "./SharedWithMeLink";
import SidebarContext, { groupSidebarContext } from "./SidebarContext";
import SidebarLink from "./SidebarLink";
import { RequestResponse } from "~/hooks/usePaginatedRequest";
import GroupMembership from "~/models/GroupMembership";
import { t } from "i18next";
import { toast } from "sonner";
type Props = {
/** The group to render */
group: Group;
/** The response from the group memberships request */
response: RequestResponse<GroupMembership>;
};
const GroupLink: React.FC<Props> = ({ group }) => {
const GroupLink: React.FC<Props> = ({ group, response }) => {
const locationSidebarContext = useLocationSidebarContext();
const sidebarContext = groupSidebarContext(group.id);
const { loading, next, end, error } = response;
const [expanded, setExpanded] = React.useState(
locationSidebarContext === sidebarContext
);
React.useEffect(() => {
if (error) {
toast.error(t("Could not load shared documents"));
}
}, [error, t]);
const handleDisclosureClick = React.useCallback((ev) => {
ev?.preventDefault();
setExpanded((e) => !e);
@@ -50,6 +63,14 @@ const GroupLink: React.FC<Props> = ({ group }) => {
depth={1}
/>
))}
{!end && (
<SidebarLink
onClick={next}
label={`${t("Show more")}`}
disabled={loading}
depth={0}
/>
)}
</Folder>
</SidebarContext.Provider>
</Relative>
@@ -10,37 +10,35 @@ import SidebarLink from "./SidebarLink";
type Props = {
node: NavigationNode;
shareId: string;
hideRootNode?: boolean;
};
function CollectionLink({ node, shareId, hideRootNode }: Props) {
function CollectionLink({ node, shareId }: Props) {
const { t } = useTranslation();
const { documents, ui } = useStores();
const icon = node.icon ?? node.emoji;
return (
<>
{!hideRootNode && (
<SidebarLink
to={{
pathname: sharedModelPath(shareId),
state: {
title: node.title,
},
}}
icon={icon && <Icon value={icon} color={node.color} />}
label={node.title || t("Untitled")}
depth={0}
exact={false}
scrollIntoViewIfNeeded={true}
isActive={() => ui.activeCollectionId === node.id}
/>
)}
<SidebarLink
to={{
pathname: sharedModelPath(shareId),
state: {
title: node.title,
},
}}
icon={icon && <Icon value={icon} color={node.color} />}
label={node.title || t("Untitled")}
depth={0}
exact={false}
scrollIntoViewIfNeeded={true}
isActive={() => ui.activeCollectionId === node.id}
/>
{node.children.map((childNode, index) => (
<SharedDocumentLink
key={childNode.id}
index={index}
depth={hideRootNode ? 0 : 2}
depth={2}
shareId={shareId}
node={childNode}
prefetchDocument={documents.prefetchDocument}
@@ -30,7 +30,9 @@ function SharedWithMe() {
const history = useHistory();
const locationSidebarContext = useLocationSidebarContext();
usePaginatedRequest<GroupMembership>(groupMemberships.fetchAll);
const gmResponse = usePaginatedRequest<GroupMembership>(
groupMemberships.fetchAll
);
const { loading, next, end, error, page } =
usePaginatedRequest<UserMembership>(userMemberships.fetchPage, {
@@ -108,7 +110,7 @@ function SharedWithMe() {
<Flex column>
<Header id="shared" title={t("Shared with me")}>
{user.groupsWithDocumentMemberships.map((group) => (
<GroupLink key={group.id} group={group} />
<GroupLink key={group.id} group={group} response={gmResponse} />
))}
<Relative>
{reorderProps.isDragging && (
@@ -25,7 +25,6 @@ const SidebarButton = React.forwardRef<HTMLButtonElement, SidebarButtonProps>(
image,
title,
children,
onClick,
...rest
}: SidebarButtonProps,
ref
@@ -39,12 +38,10 @@ const SidebarButton = React.forwardRef<HTMLButtonElement, SidebarButtonProps>(
>
<Button
{...rest}
onClick={onClick}
$position={position}
as="button"
ref={ref}
role="button"
disabled={!onClick}
>
<Content gap={8} align="center">
{image}
@@ -99,17 +96,17 @@ const Button = styled(Flex)<{
text-decoration: none;
text-align: left;
user-select: none;
cursor: var(--pointer);
position: relative;
${undraggableOnDesktop()}
${extraArea(4)}
&:not(:disabled):active,
&:not(:disabled):${hover},
&:not(:disabled)[aria-expanded="true"] {
&:active,
&:${hover},
&[aria-expanded="true"] {
color: ${s("sidebarText")};
background: ${s("sidebarActiveBackground")};
cursor: var(--pointer);
}
&:last-child {
+2
View File
@@ -1,4 +1,5 @@
import styled from "styled-components";
import { s } from "@shared/styles";
import { Avatar } from "./Avatar";
import { AvatarVariant } from "./Avatar/Avatar";
@@ -6,6 +7,7 @@ const TeamLogo = styled(Avatar).attrs({
variant: AvatarVariant.Square,
})`
border-radius: 4px;
box-shadow: inset 0 0 0 1px ${s("divider")};
border: 0;
`;
+3 -4
View File
@@ -30,10 +30,9 @@ const Theme: React.FC = ({ children }: Props) => {
<ThemeProvider theme={theme}>
<>
<GlobalStyles
useCursorPointer={
// Default to showing the cursor pointer if no user is logged in (public share)
auth.user?.getPreference(UserPreference.UseCursorPointer) ?? true
}
useCursorPointer={auth.user?.getPreference(
UserPreference.UseCursorPointer
)}
/>
{children}
</>
+52 -27
View File
@@ -22,13 +22,13 @@ import Input from "./Input";
import SuggestionsMenuItem from "./SuggestionsMenuItem";
import ToolbarButton from "./ToolbarButton";
import Tooltip from "./Tooltip";
import useOnClickOutside from "~/hooks/useOnClickOutside";
type Props = {
mark?: Mark;
from: number;
to: number;
dictionary: Dictionary;
onRemoveLink?: () => void;
onSelectLink: (options: {
href: string;
title?: string;
@@ -47,14 +47,16 @@ const LinkEditor: React.FC<Props> = ({
from,
to,
dictionary,
onRemoveLink,
onSelectLink,
onClickLink,
view,
}) => {
const getHref = () => sanitizeUrl(mark?.attrs.href) ?? "";
const initialValue = getHref();
const initialSelectionLength = to - from;
const inputRef = useRef<HTMLInputElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
const discardRef = useRef(false);
const [query, setQuery] = useState(initialValue);
const [selectedIndex, setSelectedIndex] = useState(-1);
const { documents } = useStores();
@@ -77,19 +79,35 @@ const LinkEditor: React.FC<Props> = ({
}
}, [trimmedQuery, request]);
useOnClickOutside(wrapperRef, () => {
// If the link in input is non-empty and same as it was when the editor opened, nothing to do
if (trimmedQuery.length && trimmedQuery === initialValue) {
return;
}
useEffect(() => {
const handleGlobalKeyDown = (event: KeyboardEvent) => {
if (event.key === "k" && event.metaKey) {
inputRef.current?.select();
}
};
// If the link is totally empty or only spaces then remove the mark
if (!trimmedQuery) {
return handleRemoveLink();
}
window.addEventListener("keydown", handleGlobalKeyDown);
return () => {
window.removeEventListener("keydown", handleGlobalKeyDown);
save(trimmedQuery, trimmedQuery);
});
// If we discarded the changes then nothing to do
if (discardRef.current) {
return;
}
// If the link is the same as it was when the editor opened, nothing to do
if (trimmedQuery === initialValue) {
return;
}
// If the link is totally empty or only spaces then remove the mark
if (!trimmedQuery) {
return handleRemoveLink();
}
save(trimmedQuery, trimmedQuery);
};
}, [trimmedQuery, initialValue]);
const save = (href: string, title?: string) => {
href = href.trim();
@@ -98,10 +116,10 @@ const LinkEditor: React.FC<Props> = ({
return;
}
discardRef.current = true;
href = sanitizeUrl(href) ?? "";
onSelectLink({ href, title, from, to });
moveSelectionToEnd();
};
const moveSelectionToEnd = () => {
@@ -138,20 +156,20 @@ const LinkEditor: React.FC<Props> = ({
save(trimmedQuery, trimmedQuery);
}
if (initialSelectionLength) {
moveSelectionToEnd();
}
return;
}
case "Escape": {
event.preventDefault();
if (!initialValue) {
if (initialValue) {
setQuery(initialValue);
moveSelectionToEnd();
} else {
handleRemoveLink();
}
// Moving selection to end causes editor state to change,
// forcing a re-render of the top-level editor component. As
// a result, the new selection, being devoid of any link mark,
// prevents LinkEditor from re-rendering.
moveSelectionToEnd();
return;
}
}
@@ -178,19 +196,23 @@ const LinkEditor: React.FC<Props> = ({
};
const handleRemoveLink = () => {
discardRef.current = true;
const { state, dispatch } = view;
if (mark) {
dispatch(state.tr.removeMark(from, to, mark));
}
moveSelectionToEnd();
onRemoveLink?.();
view.focus();
};
const isInternal = isInternalUrl(query);
const hasResults = !!results.length;
return (
<div ref={wrapperRef}>
<InputWrapper ref={wrapperRef}>
<>
<Wrapper>
<Input
ref={inputRef}
value={query}
@@ -216,7 +238,7 @@ const LinkEditor: React.FC<Props> = ({
</ToolbarButton>
</Tooltip>
)}
</InputWrapper>
</Wrapper>
<SearchResults $hasResults={hasResults}>
<ResizingHeightContainer>
{hasResults && (
@@ -225,6 +247,9 @@ const LinkEditor: React.FC<Props> = ({
<SuggestionsMenuItem
onClick={() => {
save(doc.url, doc.title);
if (initialSelectionLength) {
moveSelectionToEnd();
}
}}
onPointerMove={() => setSelectedIndex(index)}
selected={index === selectedIndex}
@@ -251,11 +276,11 @@ const LinkEditor: React.FC<Props> = ({
)}
</ResizingHeightContainer>
</SearchResults>
</div>
</>
);
};
const InputWrapper = styled(Flex)`
const Wrapper = styled(Flex)`
pointer-events: all;
gap: 6px;
padding: 6px;
-1
View File
@@ -123,7 +123,6 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
</Flex>
),
title: group.name,
subtitle: t("{{ count }} members", { count: group.memberCount }),
section: GroupSection,
appendSpace: true,
attrs: {
+13 -15
View File
@@ -30,33 +30,31 @@ export default function codeMenuItems(
)
.map(([value, item]) => langToMenuItem({ node, value, label: item.label }));
const getLanguageMenuItems = () =>
frequentLangMenuItems.length
? [
...frequentLangMenuItems,
{ name: "separator" },
...remainingLangMenuItems,
]
: remainingLangMenuItems;
const languageMenuItems = frequentLangMenuItems.length
? [
...frequentLangMenuItems,
{ name: "separator" },
...remainingLangMenuItems,
]
: remainingLangMenuItems;
return [
{
name: "copyToClipboard",
icon: <CopyIcon />,
label: readOnly
? getLabelForLanguage(node.attrs.language ?? "none")
: undefined,
label: readOnly ? dictionary.copy : undefined,
tooltip: dictionary.copy,
},
{
name: "separator",
visible: !readOnly,
},
{
name: "code_block",
label: getLabelForLanguage(node.attrs.language ?? "none"),
icon: <ExpandedIcon />,
children: getLanguageMenuItems(),
visible: !readOnly,
name: "code_block",
icon: <ExpandedIcon />,
label: getLabelForLanguage(node.attrs.language ?? "none"),
children: languageMenuItems,
},
];
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { useState, useEffect, useCallback } from "react";
import { PaginationParams } from "~/types";
import useRequest from "./useRequest";
type RequestResponse<T> = {
export type RequestResponse<T> = {
/** The return value of the paginated request function. */
data: T[] | undefined;
/** The request error, if any. */
+1 -5
View File
@@ -12,10 +12,6 @@ class Group extends Model implements Searchable {
@observable
name: string;
@Field
@observable
description: string;
@observable
externalId: string | undefined;
@@ -37,7 +33,7 @@ class Group extends Model implements Searchable {
@computed
get searchContent(): string[] {
return [this.name, this.description].filter(Boolean);
return [this.name].filter(Boolean);
}
@computed
@@ -164,11 +164,6 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
if (ev.event.code === EditorUpdateError.code) {
setEditorVersionBehind(true);
}
if (ev.event.code === 4403) {
void auth.fetchAuth().catch(() => {
history.replace(homePath());
});
}
}
});
@@ -27,7 +27,6 @@ import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
import { GroupPermission } from "@shared/types";
import { GroupValidation } from "@shared/validations";
import { EmptySelectValue, Permission } from "~/types";
import GroupUser from "~/models/GroupUser";
import Switch from "~/components/Switch";
@@ -41,7 +40,6 @@ export function CreateGroupDialog() {
const { dialogs, groups } = useStores();
const { t } = useTranslation();
const [name, setName] = React.useState<string | undefined>();
const [description, setDescription] = React.useState<string | undefined>();
const [isSaving, setIsSaving] = React.useState(false);
const handleSubmit = React.useCallback(
@@ -52,7 +50,6 @@ export function CreateGroupDialog() {
const group = new Group(
{
name,
description,
},
groups
);
@@ -70,7 +67,7 @@ export function CreateGroupDialog() {
setIsSaving(false);
}
},
[t, dialogs, groups, name, description]
[t, dialogs, groups, name]
);
return (
@@ -82,7 +79,7 @@ export function CreateGroupDialog() {
example.
</Trans>
</Text>
<Flex column>
<Flex>
<Input
type="text"
label="Name"
@@ -92,15 +89,6 @@ export function CreateGroupDialog() {
autoFocus
flex
/>
<Input
type="textarea"
label="Description"
placeholder={t("Optional")}
onChange={(e) => setDescription(e.target.value)}
value={description || ""}
maxLength={GroupValidation.maxDescriptionLength}
flex
/>
</Flex>
<Text as="p" type="secondary">
<Trans>Youll be able to add people to the group next.</Trans>
@@ -116,7 +104,6 @@ export function CreateGroupDialog() {
export function EditGroupDialog({ group, onSubmit }: Props) {
const { t } = useTranslation();
const [name, setName] = React.useState(group.name);
const [description, setDescription] = React.useState(group.description || "");
const [disableMentions, setDisableMentions] = React.useState(
group.disableMentions || false
);
@@ -129,7 +116,6 @@ export function EditGroupDialog({ group, onSubmit }: Props) {
try {
await group.save({
name,
description,
disableMentions,
});
onSubmit();
@@ -139,7 +125,7 @@ export function EditGroupDialog({ group, onSubmit }: Props) {
setIsSaving(false);
}
},
[group, onSubmit, name, description, disableMentions]
[group, onSubmit, name, disableMentions]
);
const handleNameChange = React.useCallback(
@@ -167,15 +153,6 @@ export function EditGroupDialog({ group, onSubmit }: Props) {
autoFocus
flex
/>
<Input
type="textarea"
label={t("Description")}
placeholder={t("Optional")}
onChange={(e) => setDescription(e.target.value)}
value={description}
maxLength={GroupValidation.maxDescriptionLength}
flex
/>
<Switch
id="mentions"
label={t("Disable mentions")}
+24 -12
View File
@@ -70,18 +70,6 @@ export function GroupsTable(props: Props) {
),
width: "2fr",
},
{
type: "data",
id: "description",
header: t("Description"),
accessor: (group) => group.description || "",
component: (group) => (
<Text type="secondary" size="small" weight="normal">
{group.description}
</Text>
),
width: "2fr",
},
{
type: "data",
id: "members",
@@ -109,6 +97,30 @@ export function GroupsTable(props: Props) {
width: "1fr",
sortable: false,
},
{
type: "data",
id: "admins",
header: t("Admins"),
accessor: (group) => `${group.memberCount} admins`,
component: (group) => {
const users = group.admins.slice(0, MAX_AVATAR_DISPLAY);
if (users.length === 0) {
return null;
}
return (
<GroupMembers
onClick={() => handleViewMembers(group)}
width={users.length * AvatarSize.Large}
>
<Facepile users={users} />
</GroupMembers>
);
},
width: "1fr",
sortable: false,
},
{
type: "data",
id: "createdAt",
+18 -25
View File
@@ -3,31 +3,7 @@ import "styled-components";
// and extend them!
declare module "styled-components" {
interface CodeTheme {
code: string;
codeComment: string;
codePunctuation: string;
codeNumber: string;
codeProperty: string;
codeTag: string;
codeString: string;
codeClassName: string;
codeConstant: string;
codeParameter: string;
codeSelector: string;
codeAttrName: string;
codeAttrValue: string;
codeEntity: string;
codeKeyword: string;
codeFunction: string;
codeStatement: string;
codePlaceholder: string;
codeInserted: string;
codeImportant: string;
codeOperator: string;
}
interface EditorTheme extends CodeTheme {
interface EditorTheme {
isDark: boolean;
background: string;
text: string;
@@ -53,6 +29,23 @@ declare module "styled-components" {
textHighlight: string;
textHighlightForeground: string;
selected: string;
code: string;
codeComment: string;
codePunctuation: string;
codeNumber: string;
codeProperty: string;
codeTag: string;
codeString: string;
codeClassName: string;
codeSelector: string;
codeAttr: string;
codeEntity: string;
codeKeyword: string;
codeFunction: string;
codeStatement: string;
codePlaceholder: string;
codeInserted: string;
codeImportant: string;
noticeInfoBackground: string;
noticeInfoText: string;
noticeTipBackground: string;
@@ -1,15 +0,0 @@
"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn("groups", "description", {
type: Sequelize.TEXT,
allowNull: true,
});
},
async down(queryInterface, Sequelize) {
await queryInterface.removeColumn("groups", "description");
},
};
-5
View File
@@ -9,7 +9,6 @@ import {
DataType,
Scopes,
} from "sequelize-typescript";
import { GroupValidation } from "@shared/validations";
import GroupMembership from "./GroupMembership";
import GroupUser from "./GroupUser";
import Team from "./Team";
@@ -66,10 +65,6 @@ class Group extends ParanoidModel<
@Column
name: string;
@Length({ min: 0, max: GroupValidation.maxDescriptionLength, msg: `description must be ${GroupValidation.maxDescriptionLength} characters or less` })
@Column(DataType.TEXT)
description: string;
@Column
externalId: string;
-1
View File
@@ -4,7 +4,6 @@ export default async function presentGroup(group: Group) {
return {
id: group.id,
name: group.name,
description: group.description,
externalId: group.externalId,
memberCount: await group.memberCount,
disableMentions: group.disableMentions,
-1
View File
@@ -65,7 +65,6 @@ const presentGroup = async (
return {
type: UnfurlResourceType.Group,
name: group.name,
description: group.description,
memberCount,
users: (data.users as User[]).map((user) => ({
id: user.id,
-5
View File
@@ -1,6 +1,5 @@
import { z } from "zod";
import { GroupPermission } from "@shared/types";
import { GroupValidation } from "@shared/validations";
import { Group } from "@server/models";
const BaseIdSchema = z.object({
@@ -50,8 +49,6 @@ export const GroupsCreateSchema = z.object({
body: z.object({
/** Group name */
name: z.string(),
/** Group description */
description: z.string().max(GroupValidation.maxDescriptionLength).optional(),
/** Optionally link this group to an external source. */
externalId: z.string().optional(),
/** Whether mentions are disabled for this group */
@@ -65,8 +62,6 @@ export const GroupsUpdateSchema = z.object({
body: BaseIdSchema.extend({
/** Group name */
name: z.string().optional(),
/** Group description */
description: z.string().max(GroupValidation.maxDescriptionLength).optional(),
/** Optionally link this group to an external source. */
externalId: z.string().optional(),
/** Whether mentions are disabled for this group */
+1 -1
View File
@@ -86,7 +86,7 @@ router.post(
ctx.body = {
data: {
shares: [presentShare(share, user?.isAdmin ?? false)],
sharedTree,
sharedTree: sharedTree,
team: serializedTeam,
collection: serializedCollection,
document: serializedDocument,
+2 -19
View File
@@ -1,12 +1,12 @@
import { exitCode } from "prosemirror-commands";
import { Command, EditorState, TextSelection } from "prosemirror-state";
import { Command, TextSelection } from "prosemirror-state";
import { findNextNewline, findPreviousNewline } from "../queries/findNewlines";
import { isInCode } from "../queries/isInCode";
import { findParentNode } from "../queries/findParentNode";
import { isCode } from "../lib/isCode";
import { languagesWithFourSpaceIndent } from "../lib/code";
const newline = "\n";
const tabSize = 2;
/**
* Moves the current selection to the previous newline, this is used inside
@@ -93,7 +93,6 @@ export const indentInCode: Command = (state, dispatch) => {
return false;
}
const tabSize = getTabSize(state);
const spaces = " ".repeat(tabSize);
const { tr, selection } = state;
const { $from, from, to } = selection;
@@ -156,7 +155,6 @@ export const outdentInCode: Command = (state, dispatch) => {
let totalSpacesRemoved = 0;
let spacesRemovedOnFirstLine = 0;
const startOfFirstLine = findPreviousNewline($from);
const tabSize = getTabSize(state);
while (index >= startOfFirstLine - line * tabSize) {
const newLineBefore =
@@ -270,18 +268,3 @@ export const splitCodeBlockOnTripleBackticks: Command = (state, dispatch) => {
return true;
};
function getTabSize(state: EditorState): number {
const codeBlock = findParentNode(isCode)(state.selection);
if (!codeBlock) {
return 2;
}
if (languagesWithFourSpaceIndent.includes(codeBlock.node.attrs.language)) {
return 4;
}
const existingText = codeBlock.node.textContent;
const usesFourSpaces = existingText.includes(" ");
return usesFourSpaces ? 4 : 2;
}
+4 -28
View File
@@ -163,13 +163,13 @@ const codeBlockStyle = (props: Props) => css`
opacity: 0.7;
}
.token.operator,
.token.boolean,
.token.number {
color: ${props.theme.codeNumber};
}
.token.property,
.token.variable {
.token.property {
color: ${props.theme.codeProperty};
}
@@ -177,8 +177,6 @@ const codeBlockStyle = (props: Props) => css`
color: ${props.theme.codeTag};
}
.token.char,
.token.builtin,
.token.string {
color: ${props.theme.codeString};
}
@@ -188,20 +186,7 @@ const codeBlockStyle = (props: Props) => css`
}
.token.attr-name {
color: ${props.theme.codeAttrName};
}
.token.attr-value,
.token.attr-value .token.punctuation {
color: ${props.theme.codeAttrValue};
}
.token.operator {
color: ${props.theme.codeOperator};
}
.token.namespace {
opacity: 0.8;
color: ${props.theme.codeAttr};
}
.token.entity,
@@ -257,14 +242,6 @@ const codeBlockStyle = (props: Props) => css`
font-weight: bold;
}
.token.constant {
color: ${props.theme.codeConstant};
}
.token.parameter {
color: ${props.theme.codeParameter};
}
.token.important {
color: ${props.theme.codeImportant};
}
@@ -1354,13 +1331,12 @@ code {
border: 1px solid ${props.theme.codeBorder};
background: ${props.theme.codeBackground};
padding: 3px 4px;
color: ${props.theme.code};
color: ${props.theme.codeString};
font-family: ${props.theme.fontFamilyMono};
font-size: 90%;
.${EditorStyleHelper.codeWord} {
white-space: nowrap;
color: ${props.theme.codeKeyword};
}
}
-8
View File
@@ -425,11 +425,3 @@ export const getFrequentCodeLanguages = () => {
const sortFrequencies = <T>(freqs: [T, number][]) =>
freqs.sort((a, b) => (a[1] >= b[1] ? -1 : 1));
export const languagesWithFourSpaceIndent = [
"python",
"java",
"cpp",
"csharp",
"rust",
];
+5 -7
View File
@@ -291,8 +291,6 @@
"Filter options": "Filter options",
"Filter": "Filter",
"No results": "No results",
"{{ count }} members": "{{ count }} member",
"{{ count }} members_plural": "{{ count }} members",
"{{authorName}} created <3></3>": "{{authorName}} created <3></3>",
"{{authorName}} opened <3></3>": "{{authorName}} opened <3></3>",
"Search emoji": "Search emoji",
@@ -429,11 +427,11 @@
"Expand": "Expand",
"Document not supported try Markdown, Plain text, HTML, or Word": "Document not supported try Markdown, Plain text, HTML, or Word",
"Import files": "Import files",
"Could not load shared documents": "Could not load shared documents",
"Show more": "Show more",
"Go back": "Go back",
"Go forward": "Go forward",
"Could not load shared documents": "Could not load shared documents",
"Shared with me": "Shared with me",
"Show more": "Show more",
"Link options": "Link options",
"Could not load starred documents": "Could not load starred documents",
"Starred": "Starred",
@@ -998,10 +996,8 @@
"Check server logs for more details.": "Check server logs for more details.",
"{{userName}} requested": "{{userName}} requested",
"Groups are for organizing your team. They work best when centered around a function or a responsibility — Support or Engineering for example.": "Groups are for organizing your team. They work best when centered around a function or a responsibility — Support or Engineering for example.",
"Optional": "Optional",
"Youll be able to add people to the group next.": "Youll be able to add people to the group next.",
"You can edit the name of this group at any time, however doing so too often might confuse your team mates.": "You can edit the name of this group at any time, however doing so too often might confuse your team mates.",
"Description": "Description",
"Disable mentions": "Disable mentions",
"Prevent this group from being mentionable in documents or comments": "Prevent this group from being mentionable in documents or comments",
"Are you sure about that? Deleting the <em>{{groupName}}</em> group will cause its members to lose access to collections and documents that it is associated with.": "Are you sure about that? Deleting the <em>{{groupName}}</em> group will cause its members to lose access to collections and documents that it is associated with.",
@@ -1022,6 +1018,7 @@
"No people left to add": "No people left to add",
"Group admin": "Group admin",
"Member": "Member",
"Admins": "Admins",
"Date created": "Date created",
"Crop Image": "Crop Image",
"Crop image": "Crop image",
@@ -1051,7 +1048,6 @@
"Domain": "Domain",
"Views": "Views",
"All roles": "All roles",
"Admins": "Admins",
"Editors": "Editors",
"All status": "All status",
"Active": "Active",
@@ -1066,6 +1062,7 @@
"The logo is displayed at the top left of the application.": "The logo is displayed at the top left of the application.",
"Workspace logo": "Workspace logo",
"The workspace name, usually the same as your company name.": "The workspace name, usually the same as your company name.",
"Description": "Description",
"A short description of your workspace.": "A short description of your workspace.",
"Theme": "Theme",
"Customize the interface look and feel.": "Customize the interface look and feel.",
@@ -1223,6 +1220,7 @@
"Deleting this version of the document will permanently and irrevocably remove it from the history.": "Deleting this version of the document will permanently and irrevocably remove it from the history.",
"Format": "Format",
"Add option": "Add option",
"Optional": "Optional",
"Choose a size for your exported document": "Choose a size for your exported document",
"Revision renamed": "Revision renamed",
"Failed to save revision": "Failed to save revision",
+19 -35
View File
@@ -73,29 +73,25 @@ const buildBaseTheme = (input: Partial<Colors>) => {
textHighlightForeground: colors.almostBlack,
commentMarkBackground: transparentize(0.5, "#2BC2FF"),
code: colors.lightBlack,
codeComment: "#008000",
codePunctuation: "#393a34",
codeNumber: "#0550ae",
codeProperty: "#ff0000",
codeTag: "#800000",
codeClassName: "#00578a",
codeString: "#a31515",
codeSelector: "#800000",
codeAttrName: "#ff0000",
codeAttrValue: colors.lightBlack,
codeEntity: "#ff0000",
codeKeyword: "#00009f",
codeFunction: "#393A34",
codeStatement: "#ff0000",
codeComment: "#6a737d",
codePunctuation: "#5e6687",
codeNumber: "#d73a49",
codeProperty: "#c08b30",
codeTag: "#3d8fd1",
codeClassName: "#3d8fd1",
codeString: "#032f62",
codeSelector: "#6679cc",
codeAttr: "#c76b29",
codeEntity: "#22a2c9",
codeKeyword: "#d73a49",
codeFunction: "#6f42c1",
codeStatement: "#22a2c9",
codePlaceholder: "#3d8fd1",
codeInserted: "#0550ae",
codeImportant: "#e90e90",
codeConstant: "#0550ae",
codeParameter: colors.lightBlack,
codeOperator: "#393a34",
codeInserted: "#202746",
codeImportant: "#c94922",
noticeInfoBackground: colors.brand.blue,
noticeInfoText: colors.almostBlack,
noticeTipBackground: "#f5be31",
noticeTipBackground: "#F5BE31",
noticeTipText: colors.almostBlack,
noticeWarningBackground: "#d73a49",
noticeWarningText: colors.almostBlack,
@@ -227,25 +223,13 @@ export const buildDarkTheme = (input: Partial<Colors>): DefaultTheme => {
code: colors.almostWhite,
codeBackground: "#1d202a",
codeBorder: colors.white10,
codeComment: "#6a9955",
codePunctuation: "#b3b3b3",
codeProperty: "#b5cea8",
codeNumber: "#b5cea8",
codeTag: "#b5cea8",
codeOperator: "#d4d4d4",
codeConstant: "#9cdcfe",
codeParameter: "#9cdcfe",
codeSelector: "#ce9178",
codeEntity: "#d4d4d4",
codeStatement: "#d16969",
codeInserted: "#b5cea8",
codeString: "#ce9178",
codeKeyword: "#569Cd6",
codeKeyword: "#569CD6",
codeFunction: "#dcdcaa",
codeClassName: "#4ec9b0",
codeImportant: "#569Cd6",
codeAttrName: "#9cdcfe",
codeAttrValue: "#ce9178",
codeImportant: "#569CD6",
codeAttr: "#9cdcfe",
embedBorder: colors.black50,
horizontalRule: lighten(0.1, colors.almostBlack),
noticeInfoText: colors.white,
-2
View File
@@ -452,8 +452,6 @@ export type UnfurlResponse = {
type: UnfurlResourceType.Group;
/** Group name */
name: string;
/** Group description */
description: string | null;
/** Number of members in the group */
memberCount: number;
/** Array of group members (limited to display count) */
-5
View File
@@ -54,11 +54,6 @@ export const DocumentValidation = {
maxRecommendedLength: 250000,
};
export const GroupValidation = {
/** The maximum length of the group description */
maxDescriptionLength: 2000,
};
export const ImportValidation = {
/** The maximum length of the import name */
maxNameLength: 100,