mirror of
https://github.com/outline/outline.git
synced 2026-06-13 19:35:02 +03:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b70453d36b | |||
| d79f40e522 | |||
| baa79337fb | |||
| 692c7ca8a2 | |||
| 97f65ae250 |
@@ -1,11 +1,13 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { BulletedListIcon, MenuIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Icon from "@shared/components/Icon";
|
||||
import { randomElement } from "@shared/random";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import { s } from "@shared/styles";
|
||||
import { CollectionDisplay, CollectionPermission } from "@shared/types";
|
||||
import { IconLibrary } from "@shared/utils/IconLibrary";
|
||||
import { colorPalette } from "@shared/utils/collections";
|
||||
import { CollectionValidation } from "@shared/validations";
|
||||
@@ -13,12 +15,13 @@ import Collection from "~/models/Collection";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input from "~/components/Input";
|
||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
||||
import InputSelect from "~/components/InputSelect";
|
||||
import Switch from "~/components/Switch";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import { EmptySelectValue } from "~/types";
|
||||
import { Label } from "../Labeled";
|
||||
|
||||
const IconPicker = React.lazy(() => import("~/components/IconPicker"));
|
||||
|
||||
@@ -27,6 +30,7 @@ export interface FormData {
|
||||
icon: string;
|
||||
color: string | null;
|
||||
sharing: boolean;
|
||||
display: CollectionDisplay | undefined;
|
||||
permission: CollectionPermission | undefined;
|
||||
}
|
||||
|
||||
@@ -62,6 +66,7 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
defaultValues: {
|
||||
name: collection?.name ?? "",
|
||||
icon: collection?.icon,
|
||||
display: collection?.display ?? CollectionDisplay.List,
|
||||
sharing: collection?.sharing ?? true,
|
||||
permission: collection?.permission,
|
||||
color: iconColor,
|
||||
@@ -139,9 +144,25 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
control={control}
|
||||
name="permission"
|
||||
render={({ field }) => (
|
||||
<InputSelectPermission
|
||||
<InputSelect
|
||||
ref={field.ref}
|
||||
value={field.value}
|
||||
label={t("Permission")}
|
||||
options={[
|
||||
{
|
||||
label: t("View only"),
|
||||
value: CollectionPermission.Read,
|
||||
},
|
||||
{
|
||||
label: t("Can edit"),
|
||||
value: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
label: t("No access"),
|
||||
value: EmptySelectValue,
|
||||
},
|
||||
]}
|
||||
ariaLabel={t("Default access")}
|
||||
value={field.value || EmptySelectValue}
|
||||
onChange={(
|
||||
value: CollectionPermission | typeof EmptySelectValue
|
||||
) => {
|
||||
@@ -155,6 +176,44 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
/>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="display"
|
||||
render={({ field }) => (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Label>{t("Display")}</Label>
|
||||
<Flex gap={8}>
|
||||
<Toggle>
|
||||
<Text weight="bold" as={Flex} gap={4} align="center">
|
||||
<MenuIcon /> {t("List")}
|
||||
</Text>
|
||||
<Text type="secondary">{t("Show only titles")}</Text>
|
||||
<input
|
||||
type="radio"
|
||||
name="display"
|
||||
value={CollectionDisplay.List}
|
||||
checked={field.value === CollectionDisplay.List}
|
||||
onClick={(ev) => field.onChange(ev.currentTarget.value)}
|
||||
/>
|
||||
</Toggle>
|
||||
<Toggle>
|
||||
<Text weight="bold" as={Flex} gap={4} align="center">
|
||||
<BulletedListIcon /> {t("Post")}
|
||||
</Text>
|
||||
<Text type="secondary">{t("Show document preview")}</Text>
|
||||
<input
|
||||
type="radio"
|
||||
name="display"
|
||||
value={CollectionDisplay.Post}
|
||||
checked={field.value === CollectionDisplay.Post}
|
||||
onClick={(ev) => field.onChange(ev.currentTarget.value)}
|
||||
/>
|
||||
</Toggle>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
{team.sharing && (
|
||||
<Switch
|
||||
id="sharing"
|
||||
@@ -188,3 +247,25 @@ const StyledIconPicker = styled(IconPicker)`
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
`;
|
||||
|
||||
const Toggle = styled.label`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
border-radius: 4px;
|
||||
border: 1px solid ${s("inputBorder")};
|
||||
font-size: 14px;
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
width: 50%;
|
||||
cursor: var(--pointer);
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:has(input:checked) {
|
||||
border-color: ${s("accent")};
|
||||
box-shadow: 0 0 0 1px ${s("accent")};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
import {
|
||||
useFocusEffect,
|
||||
useRovingTabIndex,
|
||||
} from "@getoutline/react-roving-tabindex";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import EventBoundary from "@shared/components/EventBoundary";
|
||||
import { richExtensions, withComments } from "@shared/editor/nodes";
|
||||
import { s, hover } from "@shared/styles";
|
||||
import Document from "~/models/Document";
|
||||
import Badge from "~/components/Badge";
|
||||
import Flex from "~/components/Flex";
|
||||
import Highlight from "~/components/Highlight";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import StarButton from "~/components/Star";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
import { useLocationSidebarContext } from "~/hooks/useLocationSidebarContext";
|
||||
import DocumentMenu from "~/menus/DocumentMenu";
|
||||
import { documentPath } from "~/utils/routeHelpers";
|
||||
import { Avatar, AvatarSize } from "./Avatar";
|
||||
import Editor from "./Editor";
|
||||
import { determineSidebarContext } from "./Sidebar/components/SidebarContext";
|
||||
import Text from "./Text";
|
||||
import Time from "./Time";
|
||||
|
||||
const extensions = withComments(richExtensions);
|
||||
|
||||
type Props = {
|
||||
document: Document;
|
||||
highlight?: string | undefined;
|
||||
context?: string | undefined;
|
||||
showParentDocuments?: boolean;
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
showPin?: boolean;
|
||||
showDraft?: boolean;
|
||||
};
|
||||
|
||||
function DocumentPostItem(
|
||||
props: Props,
|
||||
ref: React.RefObject<HTMLAnchorElement>
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const user = useCurrentUser();
|
||||
const locationSidebarContext = useLocationSidebarContext();
|
||||
const [menuOpen, handleMenuOpen, handleMenuClose] = useBoolean();
|
||||
|
||||
let itemRef: React.Ref<HTMLAnchorElement> =
|
||||
React.useRef<HTMLAnchorElement>(null);
|
||||
if (ref) {
|
||||
itemRef = ref;
|
||||
}
|
||||
|
||||
const { focused, ...rovingTabIndex } = useRovingTabIndex(itemRef, false);
|
||||
useFocusEffect(focused, itemRef);
|
||||
|
||||
const {
|
||||
document,
|
||||
showParentDocuments,
|
||||
showCollection,
|
||||
showPublished,
|
||||
showPin,
|
||||
showDraft = true,
|
||||
highlight,
|
||||
context,
|
||||
...rest
|
||||
} = props;
|
||||
const canStar = !document.isArchived && !document.isTemplate;
|
||||
|
||||
const sidebarContext = determineSidebarContext({
|
||||
document,
|
||||
user,
|
||||
currentContext: locationSidebarContext,
|
||||
});
|
||||
|
||||
const to = React.useMemo(
|
||||
() => ({
|
||||
pathname: documentPath(document),
|
||||
state: {
|
||||
title: document.titleWithDefault,
|
||||
sidebarContext,
|
||||
},
|
||||
}),
|
||||
[document, sidebarContext]
|
||||
);
|
||||
|
||||
return (
|
||||
<Post
|
||||
dir={document.dir}
|
||||
role="menuitem"
|
||||
$isStarred={document.isStarred}
|
||||
$menuOpen={menuOpen}
|
||||
{...rest}
|
||||
{...rovingTabIndex}
|
||||
>
|
||||
<Content>
|
||||
<Heading ref={itemRef} dir={document.dir} to={to}>
|
||||
<Title
|
||||
text={document.titleWithDefault}
|
||||
highlight={highlight}
|
||||
dir={document.dir}
|
||||
/>
|
||||
{document.isBadgedNew && document.createdBy?.id !== user.id && (
|
||||
<Badge yellow>{t("New")}</Badge>
|
||||
)}
|
||||
{document.isDraft && showDraft && (
|
||||
<Tooltip content={t("Only visible to you")} placement="top">
|
||||
<Badge>{t("Draft")}</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
{canStar && (
|
||||
<StarPositioner>
|
||||
<StarButton document={document} />
|
||||
</StarPositioner>
|
||||
)}
|
||||
<Actions>
|
||||
<DocumentMenu
|
||||
document={document}
|
||||
showPin={showPin}
|
||||
onOpen={handleMenuOpen}
|
||||
onClose={handleMenuClose}
|
||||
modal={false}
|
||||
/>
|
||||
</Actions>
|
||||
</Heading>
|
||||
|
||||
<Flex justify="space-between" style={{ marginBottom: 8 }}>
|
||||
<Flex gap={6} align="center">
|
||||
<Avatar model={document.createdBy} size={AvatarSize.Medium} />
|
||||
<Text type="secondary" size="small">
|
||||
{t("By {{ author }}", { author: document.createdBy?.name })}{" "}
|
||||
<Link to={to}>
|
||||
<Text type="secondary" size="small">
|
||||
<Time dateTime={document.updatedAt} addSuffix />
|
||||
</Text>
|
||||
</Link>
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Editor defaultValue={document.data} extensions={extensions} readOnly />
|
||||
</Content>
|
||||
</Post>
|
||||
);
|
||||
}
|
||||
|
||||
const Content = styled.div`
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
const Actions = styled(EventBoundary)`
|
||||
display: none;
|
||||
align-items: center;
|
||||
margin: 8px;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
color: ${s("textSecondary")};
|
||||
|
||||
${NudeButton} {
|
||||
&: ${hover}, &[aria-expanded= "true"] {
|
||||
background: ${s("sidebarControlHoverBackground")};
|
||||
}
|
||||
}
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: flex;
|
||||
`};
|
||||
`;
|
||||
|
||||
const Post = styled.div<{
|
||||
$isStarred?: boolean;
|
||||
$menuOpen?: boolean;
|
||||
}>`
|
||||
position: relative;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 3em;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
const Heading = styled(Link)<{ rtl?: boolean }>`
|
||||
display: flex;
|
||||
justify-content: ${(props) => (props.rtl ? "flex-end" : "flex-start")};
|
||||
align-items: center;
|
||||
margin-top: -2px;
|
||||
margin-bottom: -4px;
|
||||
white-space: nowrap;
|
||||
color: ${s("text")};
|
||||
font-size: 20px;
|
||||
font-family: ${s("fontFamily")};
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StarPositioner = styled(Flex)`
|
||||
margin-left: 4px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Title = styled(Highlight)`
|
||||
max-width: 90%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&: ${hover} {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
export default observer(React.forwardRef(DocumentPostItem));
|
||||
@@ -324,6 +324,8 @@ const StyledButton = styled(Button)<{ $nude?: boolean }>`
|
||||
display: block;
|
||||
width: 100%;
|
||||
cursor: var(--pointer);
|
||||
background: ${s("background")};
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: ${s("buttonNeutralBackground")};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CollectionDisplay } from "@shared/types";
|
||||
import Document from "~/models/Document";
|
||||
import DocumentListItem from "~/components/DocumentListItem";
|
||||
import Error from "~/components/List/Error";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import DocumentPostItem from "./DocumentPostItem";
|
||||
|
||||
type Props = {
|
||||
documents: Document[];
|
||||
@@ -11,6 +13,7 @@ type Props = {
|
||||
options?: Record<string, any>;
|
||||
heading?: React.ReactNode;
|
||||
empty?: React.ReactNode;
|
||||
display?: CollectionDisplay;
|
||||
showParentDocuments?: boolean;
|
||||
showCollection?: boolean;
|
||||
showPublished?: boolean;
|
||||
@@ -24,6 +27,7 @@ const PaginatedDocumentList = React.memo<Props>(function PaginatedDocumentList({
|
||||
documents,
|
||||
fetch,
|
||||
options,
|
||||
display = CollectionDisplay.List,
|
||||
showParentDocuments,
|
||||
showCollection,
|
||||
showPublished,
|
||||
@@ -42,18 +46,30 @@ const PaginatedDocumentList = React.memo<Props>(function PaginatedDocumentList({
|
||||
fetch={fetch}
|
||||
options={options}
|
||||
renderError={(props) => <Error {...props} />}
|
||||
renderItem={(item: Document, _index) => (
|
||||
<DocumentListItem
|
||||
key={item.id}
|
||||
document={item}
|
||||
showPin={!!options?.collectionId}
|
||||
showParentDocuments={showParentDocuments}
|
||||
showCollection={showCollection}
|
||||
showPublished={showPublished}
|
||||
showTemplate={showTemplate}
|
||||
showDraft={showDraft}
|
||||
/>
|
||||
)}
|
||||
renderItem={(item: Document, _index) =>
|
||||
display === CollectionDisplay.List ? (
|
||||
<DocumentListItem
|
||||
key={item.id}
|
||||
document={item}
|
||||
showPin={!!options?.collectionId}
|
||||
showParentDocuments={showParentDocuments}
|
||||
showCollection={showCollection}
|
||||
showPublished={showPublished}
|
||||
showTemplate={showTemplate}
|
||||
showDraft={showDraft}
|
||||
/>
|
||||
) : (
|
||||
<DocumentPostItem
|
||||
key={item.id}
|
||||
document={item}
|
||||
showPin={!!options?.collectionId}
|
||||
showParentDocuments={showParentDocuments}
|
||||
showCollection={showCollection}
|
||||
showPublished={showPublished}
|
||||
showDraft={showDraft}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import invariant from "invariant";
|
||||
import { action, computed, observable, runInAction } from "mobx";
|
||||
import {
|
||||
CollectionDisplay,
|
||||
CollectionPermission,
|
||||
FileOperationFormat,
|
||||
type NavigationNode,
|
||||
@@ -42,6 +43,11 @@ export default class Collection extends ParanoidModel {
|
||||
@observable
|
||||
color?: string | null;
|
||||
|
||||
/** The display mode of the collection index. */
|
||||
@Field
|
||||
@observable
|
||||
display: CollectionDisplay;
|
||||
|
||||
/** The default permission for workspace users. */
|
||||
@Field
|
||||
@observable
|
||||
|
||||
@@ -68,11 +68,13 @@ class Comment extends Model {
|
||||
* The user who resolved this comment, if it has been resolved.
|
||||
*/
|
||||
@Relation(() => User)
|
||||
@observable
|
||||
resolvedBy: User | null;
|
||||
|
||||
/**
|
||||
* The ID of the user who resolved this comment, if it has been resolved.
|
||||
*/
|
||||
@observable
|
||||
resolvedById: string | null;
|
||||
|
||||
/**
|
||||
|
||||
@@ -188,10 +188,10 @@ export default class Document extends ArchivableModel implements Searchable {
|
||||
@observable
|
||||
collaboratorIds: string[];
|
||||
|
||||
@observable
|
||||
@Relation(() => User)
|
||||
createdBy: User | undefined;
|
||||
|
||||
@observable
|
||||
@Relation(() => User)
|
||||
updatedBy: User | undefined;
|
||||
|
||||
@observable
|
||||
|
||||
@@ -290,6 +290,7 @@ const CollectionScene = observer(function _CollectionScene() {
|
||||
>
|
||||
<PaginatedDocumentList
|
||||
key="alphabetical"
|
||||
display={collection.display}
|
||||
documents={documents.alphabeticalInCollection(
|
||||
collection.id
|
||||
)}
|
||||
@@ -304,6 +305,7 @@ const CollectionScene = observer(function _CollectionScene() {
|
||||
>
|
||||
<PaginatedDocumentList
|
||||
key="old"
|
||||
display={collection.display}
|
||||
documents={documents.leastRecentlyUpdatedInCollection(
|
||||
collection.id
|
||||
)}
|
||||
@@ -321,6 +323,7 @@ const CollectionScene = observer(function _CollectionScene() {
|
||||
>
|
||||
<PaginatedDocumentList
|
||||
key="published"
|
||||
display={collection.display}
|
||||
documents={documents.recentlyPublishedInCollection(
|
||||
collection.id
|
||||
)}
|
||||
@@ -339,6 +342,7 @@ const CollectionScene = observer(function _CollectionScene() {
|
||||
>
|
||||
<PaginatedDocumentList
|
||||
key="updated"
|
||||
display={collection.display}
|
||||
documents={documents.recentlyUpdatedInCollection(
|
||||
collection.id
|
||||
)}
|
||||
@@ -356,6 +360,8 @@ const CollectionScene = observer(function _CollectionScene() {
|
||||
exact
|
||||
>
|
||||
<PaginatedDocumentList
|
||||
key="recent"
|
||||
display={collection.display}
|
||||
documents={documents.rootInCollection(collection.id)}
|
||||
fetch={documents.fetchPage}
|
||||
options={{
|
||||
@@ -374,6 +380,7 @@ const CollectionScene = observer(function _CollectionScene() {
|
||||
exact
|
||||
>
|
||||
<PaginatedDocumentList
|
||||
display={collection.display}
|
||||
documents={documents.archivedInCollection(collection.id)}
|
||||
fetch={documents.fetchPage}
|
||||
options={{
|
||||
|
||||
@@ -19,7 +19,7 @@ export function DocumentFilter(props: Props) {
|
||||
<div>
|
||||
<Tooltip content={t("Remove document filter")}>
|
||||
<StyledButton onClick={props.onClick} icon={<CloseIcon />} neutral>
|
||||
{props.document.title}
|
||||
{props.document.titleWithDefault}
|
||||
</StyledButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"use strict";
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.sequelize.transaction(async transaction => {
|
||||
await queryInterface.addColumn("collections", "display", {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: "list",
|
||||
}, { transaction });
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.sequelize.transaction(async transaction => {
|
||||
await queryInterface.removeColumn("collections", "display", { transaction });
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -35,7 +35,11 @@ import {
|
||||
} from "sequelize-typescript";
|
||||
import isUUID from "validator/lib/isUUID";
|
||||
import type { CollectionSort, ProsemirrorData } from "@shared/types";
|
||||
import { CollectionPermission, NavigationNode } from "@shared/types";
|
||||
import {
|
||||
CollectionDisplay,
|
||||
CollectionPermission,
|
||||
NavigationNode,
|
||||
} from "@shared/types";
|
||||
import { UrlHelper } from "@shared/utils/UrlHelper";
|
||||
import { sortNavigationNodes } from "@shared/utils/collections";
|
||||
import slugify from "@shared/utils/slugify";
|
||||
@@ -210,6 +214,10 @@ class Collection extends ParanoidModel<
|
||||
@Column
|
||||
icon: string | null;
|
||||
|
||||
/** The display mode of the collection index. */
|
||||
@Column(DataType.STRING)
|
||||
display: CollectionDisplay;
|
||||
|
||||
/** The color of the icon. */
|
||||
@IsHexColor
|
||||
@Column
|
||||
|
||||
@@ -20,6 +20,7 @@ export default async function presentCollection(
|
||||
icon: collection.icon,
|
||||
index: collection.index,
|
||||
color: collection.color,
|
||||
display: collection.display,
|
||||
permission: collection.permission,
|
||||
sharing: collection.sharing,
|
||||
createdAt: collection.createdAt,
|
||||
|
||||
@@ -55,8 +55,17 @@ router.post(
|
||||
transaction(),
|
||||
async (ctx: APIContext<T.CollectionsCreateReq>) => {
|
||||
const { transaction } = ctx.state;
|
||||
const { name, color, description, data, permission, sharing, icon, sort } =
|
||||
ctx.input.body;
|
||||
const {
|
||||
name,
|
||||
color,
|
||||
description,
|
||||
data,
|
||||
display,
|
||||
permission,
|
||||
sharing,
|
||||
icon,
|
||||
sort,
|
||||
} = ctx.input.body;
|
||||
let { index } = ctx.input.body;
|
||||
|
||||
const { user } = ctx.state.auth;
|
||||
@@ -80,6 +89,7 @@ router.post(
|
||||
color,
|
||||
teamId: user.teamId,
|
||||
createdById: user.id,
|
||||
display,
|
||||
permission,
|
||||
sharing,
|
||||
sort,
|
||||
@@ -570,6 +580,7 @@ router.post(
|
||||
name,
|
||||
description,
|
||||
data,
|
||||
display,
|
||||
icon,
|
||||
permission,
|
||||
color,
|
||||
@@ -623,6 +634,10 @@ router.post(
|
||||
collection.description = DocumentHelper.toMarkdown(collection);
|
||||
}
|
||||
|
||||
if (display !== undefined) {
|
||||
collection.display = display;
|
||||
}
|
||||
|
||||
if (icon !== undefined) {
|
||||
collection.icon = icon;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import isUndefined from "lodash/isUndefined";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
CollectionDisplay,
|
||||
CollectionPermission,
|
||||
CollectionStatusFilter,
|
||||
FileOperationFormat,
|
||||
@@ -24,6 +25,7 @@ export const CollectionsCreateSchema = BaseSchema.extend({
|
||||
.nullish(),
|
||||
description: z.string().nullish(),
|
||||
data: ProsemirrorSchema({ allowEmpty: true }).nullish(),
|
||||
display: z.nativeEnum(CollectionDisplay).optional(),
|
||||
permission: z
|
||||
.nativeEnum(CollectionPermission)
|
||||
.nullish()
|
||||
@@ -159,6 +161,7 @@ export const CollectionsUpdateSchema = BaseSchema.extend({
|
||||
data: ProsemirrorSchema({ allowEmpty: true }).nullish(),
|
||||
icon: zodIconType().nullish(),
|
||||
permission: z.nativeEnum(CollectionPermission).nullish(),
|
||||
display: z.nativeEnum(CollectionDisplay).optional(),
|
||||
color: z
|
||||
.string()
|
||||
.regex(ValidateColor.regex, { message: ValidateColor.message })
|
||||
|
||||
@@ -155,7 +155,17 @@
|
||||
"Viewers": "Viewers",
|
||||
"Collections are used to group documents and choose permissions": "Collections are used to group documents and choose permissions",
|
||||
"Name": "Name",
|
||||
"Permission": "Permission",
|
||||
"View only": "View only",
|
||||
"Can edit": "Can edit",
|
||||
"No access": "No access",
|
||||
"Default access": "Default access",
|
||||
"The default access for workspace members, you can share with more users or groups later.": "The default access for workspace members, you can share with more users or groups later.",
|
||||
"Display": "Display",
|
||||
"List": "List",
|
||||
"Show only titles": "Show only titles",
|
||||
"Post": "Post",
|
||||
"Show document preview": "Show document preview",
|
||||
"Public document sharing": "Public document sharing",
|
||||
"Allow documents within this collection to be shared publicly on the internet.": "Allow documents within this collection to be shared publicly on the internet.",
|
||||
"Saving": "Saving",
|
||||
@@ -227,6 +237,7 @@
|
||||
"in": "in",
|
||||
"nested document": "nested document",
|
||||
"nested document_plural": "nested documents",
|
||||
"By {{ author }}": "By {{ author }}",
|
||||
"{{ total }} task": "{{ total }} task",
|
||||
"{{ total }} task_plural": "{{ total }} tasks",
|
||||
"{{ completed }} task done": "{{ completed }} task done",
|
||||
@@ -291,11 +302,6 @@
|
||||
"Select a color": "Select a color",
|
||||
"Loading": "Loading",
|
||||
"Search": "Search",
|
||||
"Permission": "Permission",
|
||||
"View only": "View only",
|
||||
"Can edit": "Can edit",
|
||||
"No access": "No access",
|
||||
"Default access": "Default access",
|
||||
"Change Language": "Change Language",
|
||||
"Dismiss": "Dismiss",
|
||||
"You’re offline.": "You’re offline.",
|
||||
@@ -610,7 +616,6 @@
|
||||
"Add a comment": "Add a comment",
|
||||
"Add a reply": "Add a reply",
|
||||
"Reply": "Reply",
|
||||
"Post": "Post",
|
||||
"Cancel": "Cancel",
|
||||
"Upload image": "Upload image",
|
||||
"No resolved comments": "No resolved comments",
|
||||
@@ -911,7 +916,6 @@
|
||||
"Unable to upload new logo": "Unable to upload new logo",
|
||||
"Delete workspace": "Delete workspace",
|
||||
"These settings affect the way that your workspace appears to everyone on the team.": "These settings affect the way that your workspace appears to everyone on the team.",
|
||||
"Display": "Display",
|
||||
"The logo is displayed at the top left of the application.": "The logo is displayed at the top left of the application.",
|
||||
"The workspace name, usually the same as your company name.": "The workspace name, usually the same as your company name.",
|
||||
"Theme": "Theme",
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
export enum CollectionDisplay {
|
||||
List = "list",
|
||||
Post = "post",
|
||||
}
|
||||
|
||||
export enum UserRole {
|
||||
Admin = "admin",
|
||||
Member = "member",
|
||||
|
||||
Reference in New Issue
Block a user