Compare commits

..

7 Commits

Author SHA1 Message Date
e7651e4b8c switch position 2025-06-08 21:39:06 -07:00
b58dc6dee9 feat: toggle table header row and column 2025-05-22 11:30:36 -07:00
69447fc375 Merge branch 'main' of https://github.com/docmost/docmost 2025-05-21 08:43:56 -07:00
858ff9da06 sync 2025-05-20 09:27:30 -07:00
343b2976c2 #1186/chore: add support language abap syntax highlight (#1188) 2025-05-19 20:05:31 +01:00
7491224d0f hide shared page branding in EE (#1193)
* hide shared page branding in EE

* Hide branding in business plan
2025-05-17 19:17:34 +01:00
4a0b4040ed Add second plan (#1187) 2025-05-17 19:03:01 +01:00
13 changed files with 94 additions and 21 deletions

View File

@ -29,6 +29,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"emoji-mart": "^5.6.0", "emoji-mart": "^5.6.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"highlightjs-sap-abap": "^0.3.0",
"i18next": "^23.14.0", "i18next": "^23.14.0",
"i18next-http-backend": "^2.6.1", "i18next-http-backend": "^2.6.1",
"jotai": "^2.12.1", "jotai": "^2.12.1",

View File

@ -1,5 +1,6 @@
export enum BillingPlan { export enum BillingPlan {
STANDARD = "standard", STANDARD = "standard",
BUSINESS = "business",
} }
export interface IBilling { export interface IBilling {

View File

@ -2,14 +2,18 @@ import { useAtom } from "jotai";
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
import { BillingPlan } from "@/ee/billing/types/billing.types.ts"; import { BillingPlan } from "@/ee/billing/types/billing.types.ts";
export const usePlan = () => { const usePlan = () => {
const [workspace] = useAtom(workspaceAtom); const [workspace] = useAtom(workspaceAtom);
const isStandard = const isStandard =
typeof workspace?.plan === "string" && typeof workspace?.plan === "string" &&
workspace?.plan.toLowerCase() === BillingPlan.STANDARD.toLowerCase(); workspace?.plan.toLowerCase() === BillingPlan.STANDARD.toLowerCase();
return { isStandard }; const isBusiness =
typeof workspace?.plan === "string" &&
workspace?.plan.toLowerCase() === BillingPlan.BUSINESS.toLowerCase();
return { isStandard, isBusiness };
}; };
export default usePlan; export default usePlan;

View File

@ -10,11 +10,13 @@ import EnforceSso from "@/ee/security/components/enforce-sso.tsx";
import AllowedDomains from "@/ee/security/components/allowed-domains.tsx"; import AllowedDomains from "@/ee/security/components/allowed-domains.tsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import useLicense from "@/ee/hooks/use-license.tsx"; import useLicense from "@/ee/hooks/use-license.tsx";
import usePlan from "@/ee/hooks/use-plan.tsx";
export default function Security() { export default function Security() {
const { t } = useTranslation(); const { t } = useTranslation();
const { isAdmin } = useUserRole(); const { isAdmin } = useUserRole();
const { hasLicenseKey } = useLicense(); const { hasLicenseKey } = useLicense();
const { isBusiness } = usePlan();
if (!isAdmin) { if (!isAdmin) {
return null; return null;
@ -35,8 +37,7 @@ export default function Security() {
Single sign-on (SSO) Single sign-on (SSO)
</Title> </Title>
{/*TODO: revisit when we add a second plan */} {(isCloud() && isBusiness) || (!isCloud() && hasLicenseKey) ? (
{!isCloud() && hasLicenseKey ? (
<> <>
<EnforceSso /> <EnforceSso />
<Divider my="lg" /> <Divider my="lg" />

View File

@ -17,9 +17,9 @@ import {
IconColumnRemove, IconColumnRemove,
IconRowInsertBottom, IconRowInsertBottom,
IconRowInsertTop, IconRowInsertTop,
IconRowRemove, IconRowRemove, IconTableColumn, IconTableRow,
IconTrashX, IconTrashX,
} from "@tabler/icons-react"; } from '@tabler/icons-react';
import { isCellSelection } from "@docmost/editor-ext"; import { isCellSelection } from "@docmost/editor-ext";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -50,6 +50,14 @@ export const TableMenu = React.memo(
return posToDOMRect(editor.view, selection.from, selection.to); return posToDOMRect(editor.view, selection.from, selection.to);
}, [editor]); }, [editor]);
const toggleHeaderColumn = useCallback(() => {
editor.chain().focus().toggleHeaderColumn().run();
}, [editor]);
const toggleHeaderRow = useCallback(() => {
editor.chain().focus().toggleHeaderRow().run();
}, [editor]);
const addColumnLeft = useCallback(() => { const addColumnLeft = useCallback(() => {
editor.chain().focus().addColumnBefore().run(); editor.chain().focus().addColumnBefore().run();
}, [editor]); }, [editor]);
@ -180,6 +188,30 @@ export const TableMenu = React.memo(
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip position="top" label={t("Toggle header row")}
>
<ActionIcon
onClick={toggleHeaderRow}
variant="default"
size="lg"
aria-label={t("Toggle header row")}
>
<IconTableRow size={18} />
</ActionIcon>
</Tooltip>
<Tooltip position="top" label={t("Toggle header column")}
>
<ActionIcon
onClick={toggleHeaderColumn}
variant="default"
size="lg"
aria-label={t("Toggle header column")}
>
<IconTableColumn size={18} />
</ActionIcon>
</Tooltip>
<Tooltip position="top" label={t("Delete table")}> <Tooltip position="top" label={t("Delete table")}>
<ActionIcon <ActionIcon
onClick={deleteTable} onClick={deleteTable}

View File

@ -58,6 +58,7 @@ import ExcalidrawView from "@/features/editor/components/excalidraw/excalidraw-v
import EmbedView from "@/features/editor/components/embed/embed-view.tsx"; import EmbedView from "@/features/editor/components/embed/embed-view.tsx";
import plaintext from "highlight.js/lib/languages/plaintext"; import plaintext from "highlight.js/lib/languages/plaintext";
import powershell from "highlight.js/lib/languages/powershell"; import powershell from "highlight.js/lib/languages/powershell";
import abap from "highlightjs-sap-abap";
import elixir from "highlight.js/lib/languages/elixir"; import elixir from "highlight.js/lib/languages/elixir";
import erlang from "highlight.js/lib/languages/erlang"; import erlang from "highlight.js/lib/languages/erlang";
import dockerfile from "highlight.js/lib/languages/dockerfile"; import dockerfile from "highlight.js/lib/languages/dockerfile";
@ -76,7 +77,7 @@ import { CharacterCount } from "@tiptap/extension-character-count";
const lowlight = createLowlight(common); const lowlight = createLowlight(common);
lowlight.register("mermaid", plaintext); lowlight.register("mermaid", plaintext);
lowlight.register("powershell", powershell); lowlight.register("powershell", powershell);
lowlight.register("powershell", powershell); lowlight.register("abap", abap);
lowlight.register("erlang", erlang); lowlight.register("erlang", erlang);
lowlight.register("elixir", elixir); lowlight.register("elixir", elixir);
lowlight.register("dockerfile", dockerfile); lowlight.register("dockerfile", dockerfile);

View File

@ -0,0 +1,16 @@
import { Affix, Button } from "@mantine/core";
export default function ShareBranding() {
return (
<Affix position={{ bottom: 20, right: 20 }}>
<Button
variant="default"
component="a"
target="_blank"
href="https://docmost.com?ref=public-share"
>
Powered by Docmost
</Button>
</Affix>
);
}

View File

@ -36,6 +36,7 @@ import {
} from "@/features/search/components/search-control.tsx"; } from "@/features/search/components/search-control.tsx";
import { ShareSearchSpotlight } from "@/features/search/share-search-spotlight"; import { ShareSearchSpotlight } from "@/features/search/share-search-spotlight";
import { shareSearchSpotlight } from "@/features/search/constants"; import { shareSearchSpotlight } from "@/features/search/constants";
import ShareBranding from '@/features/share/components/share-branding.tsx';
const MemoizedSharedTree = React.memo(SharedTree); const MemoizedSharedTree = React.memo(SharedTree);
@ -163,16 +164,7 @@ export default function ShareShell({
<AppShell.Main> <AppShell.Main>
{children} {children}
<Affix position={{ bottom: 20, right: 20 }}> {data && shareId && !data.hasLicenseKey && <ShareBranding />}
<Button
variant="default"
component="a"
target="_blank"
href="https://docmost.com?ref=public-share"
>
Powered by Docmost
</Button>
</Affix>
</AppShell.Main> </AppShell.Main>
<AppShell.Aside <AppShell.Aside

View File

@ -41,6 +41,7 @@ export interface ISharedPage extends IShare {
level: number; level: number;
sharedPage: { id: string; slugId: string; title: string; icon: string }; sharedPage: { id: string; slugId: string; title: string; icon: string };
}; };
hasLicenseKey: boolean;
} }
export interface IShareForPage extends IShare { export interface IShareForPage extends IShare {
@ -70,4 +71,5 @@ export interface IShareInfoInput {
export interface ISharedPageTree { export interface ISharedPageTree {
share: IShare; share: IShare;
pageTree: Partial<IPage[]>; pageTree: Partial<IPage[]>;
hasLicenseKey: boolean;
} }

View File

@ -7,8 +7,9 @@ import React, { useEffect } from "react";
import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx"; import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx";
import { extractPageSlugId } from "@/lib"; import { extractPageSlugId } from "@/lib";
import { Error404 } from "@/components/ui/error-404.tsx"; import { Error404 } from "@/components/ui/error-404.tsx";
import ShareBranding from "@/features/share/components/share-branding.tsx";
export default function SingleSharedPage() { export default function SharedPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const { pageSlug } = useParams(); const { pageSlug } = useParams();
const { shareId } = useParams(); const { shareId } = useParams();
@ -53,6 +54,8 @@ export default function SingleSharedPage() {
content={data.page.content} content={data.page.content}
/> />
</Container> </Container>
{data && !shareId && !data.hasLicenseKey && <ShareBranding />}
</div> </div>
); );
} }

View File

@ -30,6 +30,7 @@ import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { Public } from '../../common/decorators/public.decorator'; import { Public } from '../../common/decorators/public.decorator';
import { ShareRepo } from '@docmost/db/repos/share/share.repo'; import { ShareRepo } from '@docmost/db/repos/share/share.repo';
import { PaginationOptions } from '@docmost/db/pagination/pagination-options'; import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
import { EnvironmentService } from '../../integrations/environment/environment.service';
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Controller('shares') @Controller('shares')
@ -39,6 +40,7 @@ export class ShareController {
private readonly spaceAbility: SpaceAbilityFactory, private readonly spaceAbility: SpaceAbilityFactory,
private readonly shareRepo: ShareRepo, private readonly shareRepo: ShareRepo,
private readonly pageRepo: PageRepo, private readonly pageRepo: PageRepo,
private readonly environmentService: EnvironmentService,
) {} ) {}
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@ -61,7 +63,12 @@ export class ShareController {
throw new BadRequestException(); throw new BadRequestException();
} }
return this.shareService.getSharedPage(dto, workspace.id); return {
...(await this.shareService.getSharedPage(dto, workspace.id)),
hasLicenseKey:
Boolean(workspace.licenseKey) ||
(this.environmentService.isCloud() && workspace.plan === 'business'),
};
} }
@Public() @Public()
@ -166,6 +173,11 @@ export class ShareController {
@Body() dto: ShareIdDto, @Body() dto: ShareIdDto,
@AuthWorkspace() workspace: Workspace, @AuthWorkspace() workspace: Workspace,
) { ) {
return this.shareService.getShareTree(dto.shareId, workspace.id); return {
...(await this.shareService.getShareTree(dto.shareId, workspace.id)),
hasLicenseKey:
Boolean(workspace.licenseKey) ||
(this.environmentService.isCloud() && workspace.plan === 'business'),
};
} }
} }

8
pnpm-lock.yaml generated
View File

@ -257,6 +257,9 @@ importers:
file-saver: file-saver:
specifier: ^2.0.5 specifier: ^2.0.5
version: 2.0.5 version: 2.0.5
highlightjs-sap-abap:
specifier: ^0.3.0
version: 0.3.0
i18next: i18next:
specifier: ^23.14.0 specifier: ^23.14.0
version: 23.14.0 version: 23.14.0
@ -5872,6 +5875,9 @@ packages:
resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
highlightjs-sap-abap@0.3.0:
resolution: {integrity: sha512-nSiUvEOCycjtFA3pHaTowrbAAk5+lciBHyoVkDsd6FTRBtW9sT2dt42o2jAKbXjZVUidtacdk+j0Y2xnd233Mw==}
hoist-non-react-statics@3.3.2: hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
@ -15436,6 +15442,8 @@ snapshots:
highlight.js@11.10.0: {} highlight.js@11.10.0: {}
highlightjs-sap-abap@0.3.0: {}
hoist-non-react-statics@3.3.2: hoist-non-react-statics@3.3.2:
dependencies: dependencies:
react-is: 16.13.1 react-is: 16.13.1