mirror of
https://github.com/AppFlowy-IO/AppFlowy
synced 2026-05-05 22:48:27 +00:00
feat: support publish outline (#6173)
This commit is contained in:
parent
009c2ed402
commit
aa621289e9
40 changed files with 550 additions and 234 deletions
|
|
@ -1,7 +1,9 @@
|
|||
import { GetViewRowsMap, LoadView, LoadViewMeta } from '@/application/collab.type';
|
||||
import { db } from '@/application/db';
|
||||
import { ViewMeta } from '@/application/db/tables/view_metas';
|
||||
import { AFConfigContext } from '@/components/app/app.hooks';
|
||||
import { View } from '@/application/types';
|
||||
import { useService } from '@/components/app/app.hooks';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { useLiveQuery } from 'dexie-react-hooks';
|
||||
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
|
@ -14,8 +16,8 @@ export interface PublishContextType {
|
|||
toView: (viewId: string) => Promise<void>;
|
||||
loadViewMeta: LoadViewMeta;
|
||||
getViewRowsMap?: GetViewRowsMap;
|
||||
|
||||
loadView: LoadView;
|
||||
outline?: View;
|
||||
}
|
||||
|
||||
export const PublishContext = createContext<PublishContextType | null>(null);
|
||||
|
|
@ -36,6 +38,9 @@ export const PublishProvider = ({
|
|||
|
||||
return db.view_metas.get(name);
|
||||
}, [namespace, publishName]);
|
||||
|
||||
const [outline, setOutline] = useState<View>();
|
||||
|
||||
const [subscribers, setSubscribers] = useState<Map<string, (meta: ViewMeta) => void>>(new Map());
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -43,6 +48,7 @@ export const PublishProvider = ({
|
|||
setSubscribers(new Map());
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
db.view_metas.hook('creating', (primaryKey, obj) => {
|
||||
const subscriber = subscribers.get(primaryKey);
|
||||
|
|
@ -72,7 +78,8 @@ export const PublishProvider = ({
|
|||
|
||||
const prevViewMeta = useRef(viewMeta);
|
||||
|
||||
const service = useContext(AFConfigContext)?.service;
|
||||
const service = useService();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const toView = useCallback(
|
||||
async (viewId: string) => {
|
||||
|
|
@ -83,9 +90,13 @@ export const PublishProvider = ({
|
|||
throw new Error('Not found');
|
||||
}
|
||||
|
||||
const { namespace, publishName } = res;
|
||||
const { namespace: viewNamespace, publishName } = res;
|
||||
|
||||
navigate(`/${namespace}/${publishName}`);
|
||||
prevViewMeta.current = undefined;
|
||||
navigate(`/${viewNamespace}/${publishName}`, {
|
||||
replace: true,
|
||||
});
|
||||
return;
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
|
@ -93,6 +104,22 @@ export const PublishProvider = ({
|
|||
[navigate, service],
|
||||
);
|
||||
|
||||
const loadOutline = useCallback(async () => {
|
||||
if (!service || !namespace) return;
|
||||
console.log('loadOutline', namespace);
|
||||
try {
|
||||
const res = await service?.getPublishOutline(namespace);
|
||||
|
||||
if (!res) {
|
||||
throw new Error('Publish outline not found');
|
||||
}
|
||||
|
||||
setOutline(res);
|
||||
} catch (e) {
|
||||
notify.error('Publish outline not found');
|
||||
}
|
||||
}, [namespace, service]);
|
||||
|
||||
const loadViewMeta = useCallback(
|
||||
async (viewId: string, callback?: (meta: ViewMeta) => void) => {
|
||||
try {
|
||||
|
|
@ -188,6 +215,10 @@ export const PublishProvider = ({
|
|||
prevViewMeta.current = viewMeta;
|
||||
}, [viewMeta]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadOutline();
|
||||
}, [loadOutline]);
|
||||
|
||||
return (
|
||||
<PublishContext.Provider
|
||||
value={{
|
||||
|
|
@ -199,6 +230,7 @@ export const PublishProvider = ({
|
|||
namespace,
|
||||
publishName,
|
||||
isTemplateThumb,
|
||||
outline,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
TemplateCreator, TemplateCreatorFormValues, TemplateSummary,
|
||||
UploadTemplatePayload,
|
||||
} from '@/application/template.type';
|
||||
import { FolderView, User, Workspace } from '@/application/types';
|
||||
import { FolderView, User, View, Workspace } from '@/application/types';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
|
|
@ -233,6 +233,23 @@ export async function getPublishInfoWithViewId (viewId: string) {
|
|||
return Promise.reject(data);
|
||||
}
|
||||
|
||||
export async function getPublishOutline (publishNamespace: string) {
|
||||
const url = `/api/workspace/published-outline/${publishNamespace}`;
|
||||
const response = await axiosInstance?.get<{
|
||||
code: number;
|
||||
data?: View;
|
||||
message: string;
|
||||
}>(url);
|
||||
|
||||
const data = response?.data;
|
||||
|
||||
if (data?.code === 0 && data.data) {
|
||||
return data.data;
|
||||
}
|
||||
|
||||
return Promise.reject(data);
|
||||
}
|
||||
|
||||
export async function getPublishViewComments (viewId: string): Promise<GlobalComment[]> {
|
||||
const url = `/api/workspace/published-info/${viewId}/comment`;
|
||||
const response = await axiosInstance?.get<{
|
||||
|
|
|
|||
|
|
@ -164,6 +164,10 @@ export class AFClientService implements AFService {
|
|||
return data;
|
||||
}
|
||||
|
||||
async getPublishOutline (namespace: string) {
|
||||
return APIService.getPublishOutline(namespace);
|
||||
}
|
||||
|
||||
async loginAuth (url: string) {
|
||||
try {
|
||||
console.log('loginAuth', url);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
UploadTemplatePayload,
|
||||
} from '@/application/template.type';
|
||||
import * as Y from 'yjs';
|
||||
import { DuplicatePublishView, FolderView, User, Workspace } from '@/application/types';
|
||||
import { DuplicatePublishView, FolderView, User, View, Workspace } from '@/application/types';
|
||||
|
||||
export type AFService = PublishService;
|
||||
|
||||
|
|
@ -37,6 +37,8 @@ export interface PublishService {
|
|||
destroy: () => void;
|
||||
}>;
|
||||
|
||||
getPublishOutline (namespace: string): Promise<View>;
|
||||
|
||||
getPublishViewGlobalComments: (viewId: string) => Promise<GlobalComment[]>;
|
||||
createCommentOnPublishView: (viewId: string, content: string, replyCommentId?: string) => Promise<void>;
|
||||
deleteCommentOnPublishView: (viewId: string, commentId: string) => Promise<void>;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ export class AFClientService implements AFService {
|
|||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
|
||||
async getPublishOutline (_namespace: string) {
|
||||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
|
||||
async getPublishViewMeta (_namespace: string, _publishName: string) {
|
||||
return Promise.reject('Method not implemented');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CollabType } from '@/application/collab.type';
|
||||
import { CollabType, ViewLayout } from '@/application/collab.type';
|
||||
|
||||
export interface Workspace {
|
||||
icon: string;
|
||||
|
|
@ -38,3 +38,25 @@ export interface DuplicatePublishView {
|
|||
collabType: CollabType;
|
||||
viewId: string;
|
||||
}
|
||||
|
||||
export interface ViewIcon {
|
||||
ty: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ViewExtra {
|
||||
is_space: boolean;
|
||||
space_created_at?: number;
|
||||
space_icon?: string;
|
||||
space_icon_color?: string;
|
||||
space_permission?: number;
|
||||
}
|
||||
|
||||
export interface View {
|
||||
view_id: string;
|
||||
name: string;
|
||||
icon: ViewIcon | null;
|
||||
layout: ViewLayout;
|
||||
extra: ViewExtra | null;
|
||||
children: View[];
|
||||
}
|
||||
|
|
@ -1,25 +1,34 @@
|
|||
<svg width="57" height="14" viewBox="0 0 57 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Group 1171274570">
|
||||
<path id="Vector"
|
||||
d="M8.35547 4.01126H10.1226V4.67123C10.5568 4.21099 11.3679 3.84192 12.2589 3.84192C14.1641 3.84192 15.3938 5.31819 15.3938 7.36065C15.3938 9.46563 13.9488 11.0947 11.7361 11.0947C11.1213 11.0947 10.5204 10.9871 10.1226 10.7569V13.9995H8.35547V4.01126ZM10.1226 6.25258V9.04967C10.5994 9.35708 10.9989 9.44913 11.5824 9.44913C12.8121 9.44913 13.5189 8.55729 13.5189 7.40494C13.5189 6.2986 12.8737 5.49968 11.7057 5.49968C11.1065 5.50142 10.5534 5.77844 10.1226 6.25258Z"
|
||||
fill="currentColor"/>
|
||||
<path id="Vector_2"
|
||||
d="M3.98189 3.88086C1.76836 3.88086 0.324219 5.50996 0.324219 7.61495C0.324219 9.65915 1.55386 11.1345 3.45912 11.1345C4.35009 11.1345 5.16464 10.7655 5.59536 10.3035V10.9652H7.36862V5.16348C6.05561 4.16917 4.60019 3.88086 3.98189 3.88086ZM5.59536 8.72215C5.16117 9.19716 4.61147 9.47418 4.01228 9.47418C2.84516 9.47418 2.19908 8.67526 2.19908 7.56979C2.19908 6.41657 2.90595 5.52473 4.13473 5.52473C4.71916 5.52473 5.11861 5.61765 5.59536 5.92506V8.72215Z"
|
||||
fill="currentColor"/>
|
||||
<path id="Vector_3"
|
||||
d="M16.1484 4.01126H17.9156V4.67123C18.3498 4.21099 19.16 3.84192 20.051 3.84192C21.9571 3.84192 23.1859 5.31819 23.1859 7.36065C23.1859 9.46563 21.7418 11.0947 19.5291 11.0947C18.9143 11.0947 18.3133 10.9871 17.9156 10.7569V13.9995H16.1484V4.01126ZM17.9156 6.25258V9.04967C18.3915 9.35708 18.791 9.44913 19.3754 9.44913C20.6042 9.44913 21.311 8.55729 21.311 7.40494C21.311 6.2986 20.6658 5.49968 19.4978 5.49968C18.8986 5.50142 18.3455 5.77844 17.9156 6.25258Z"
|
||||
fill="currentColor"/>
|
||||
<path id="Vector_4"
|
||||
d="M28.3102 2.02887C27.9904 1.80209 27.6113 1.6738 27.2195 1.6598C26.4206 1.6598 26.0037 2.0749 26.0037 3.02752V4.01141H27.2481V5.67091H26.0055V10.9264H24.2383V2.88945C24.2383 0.968563 25.1605 0.000305176 26.6967 0.000305176C27.4036 0.000305176 28.0497 0.173984 28.5099 0.461422H30.0774V8.29867C30.0774 9.08022 30.2771 9.41976 30.6766 9.41976C30.9692 9.41976 31.1976 9.29732 31.4147 9.14361L31.7829 10.4957C31.3834 10.8187 30.7842 11.0793 29.9593 11.0793C28.8989 11.0793 28.3145 10.434 28.3145 9.0203L28.3102 2.02887Z"
|
||||
fill="currentColor"/>
|
||||
<path id="Vector_5"
|
||||
d="M35.5466 3.84192C37.8357 3.84192 39.3111 5.39374 39.3111 7.46833C39.3111 9.54292 37.8348 11.0947 35.5466 11.0947C33.2584 11.0947 31.7812 9.54292 31.7812 7.46833C31.7812 5.39374 33.2566 3.84192 35.5466 3.84192ZM35.5466 9.45087C36.6373 9.45087 37.4362 8.66932 37.4362 7.46833C37.4362 6.30034 36.6225 5.50142 35.5466 5.50142C34.5045 5.50142 33.6717 6.26995 33.6717 7.46833C33.6717 8.62069 34.4707 9.45087 35.5466 9.45087Z"
|
||||
fill="currentColor"/>
|
||||
<path id="Vector_6"
|
||||
d="M48.0544 10.9565H46.4713L44.995 7.11477C44.8874 6.85425 44.8413 6.56161 44.7641 6.30022C44.7077 6.58869 44.6255 6.87148 44.5183 7.14517L43.0594 10.9565H41.5545L39.3105 4.0094H41.201L42.3074 7.77388C42.3943 8.07652 42.4611 8.38461 42.5071 8.69611C42.5772 8.38422 42.6642 8.07635 42.7676 7.77388L44.0581 4.0094H45.6212L46.9672 7.75999C47.0701 8.0738 47.157 8.39262 47.2277 8.71522C47.2893 8.39305 47.3666 8.05437 47.443 7.71657L48.4573 4.01374H50.2401L48.0544 10.9565Z"
|
||||
fill="currentColor"/>
|
||||
<path id="Vector_7"
|
||||
d="M53.9557 10.9265C52.9414 13.3997 52.2962 13.9997 51.2819 13.9997C50.6514 13.9997 50.1903 13.7531 49.7909 13.4613L50.3596 12.1249C50.5437 12.2325 50.8051 12.3706 51.0822 12.3706C51.5276 12.3706 51.8498 12.0172 52.1425 11.2947L52.2962 10.9265L49.0996 4.01147H51.222L52.8354 7.79159C52.9735 8.12939 53.0656 8.45157 53.1732 8.79024C53.2298 8.4465 53.3119 8.10746 53.419 7.77596L54.6643 4.01147H56.6755L53.9557 10.9265Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<svg width="88" height="16" viewBox="0 0 88 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.9826 9.6731C15.524 12.17 13.5133 14.3481 11.2028 15.57C10.9206 15.7294 10.5855 15.8179 10.2681 15.8357H15.1712C15.6475 15.8357 15.9826 15.4815 15.9826 15.0388V9.6731Z"
|
||||
fill="#F7931E"/>
|
||||
<path d="M6.17473 4.85546C6.10418 4.90859 6.03363 4.96171 5.96308 5.01484C4.76374 5.86484 1.13045 8.59196 0.301491 7.40549C-0.509827 6.25443 0.354403 2.99607 2.43561 1.42001C2.47089 1.38459 2.5238 1.36688 2.55907 1.33147C4.83429 -0.280007 6.52748 -0.0497963 7.35643 1.11897C8.13248 2.21689 7.26825 3.95233 6.17473 4.85546Z"
|
||||
fill="#8427E0"/>
|
||||
<path d="M15.0644 7.38918C13.9356 8.18606 12.1719 7.26522 11.29 6.11417C11.2548 6.06104 11.2195 6.02562 11.1842 5.9725C10.3376 4.76832 7.62147 1.12037 8.78554 0.305776C9.9496 -0.526524 13.3183 0.37661 14.8528 2.57247C14.8881 2.62559 14.9233 2.66101 14.9586 2.71413C16.4402 4.9277 16.2109 6.57459 15.0644 7.38918Z"
|
||||
fill="#00B5FF"/>
|
||||
<path d="M13.5466 14.5783C13.5113 14.6138 13.4761 14.6315 13.4232 14.6669C11.1479 16.2784 9.45475 16.0481 8.62579 14.8794C7.84975 13.7815 8.71398 12.046 9.80749 11.1429C9.87804 11.0898 9.94859 11.0366 10.0191 10.9835C11.2185 10.1512 14.8518 7.40639 15.6631 8.59286C16.4921 9.74391 15.6455 13.0023 13.5466 14.5783Z"
|
||||
fill="#FFBD00"/>
|
||||
<path d="M7.19914 15.6944C6.03507 16.5267 2.68398 15.6236 1.14953 13.4277C1.11425 13.3923 1.07898 13.3392 1.06134 13.3038C-0.437833 11.0902 -0.19091 9.4256 0.955517 8.62871C2.08431 7.83183 3.84804 8.75267 4.72991 9.90373C4.76518 9.95685 4.80046 9.99227 4.83573 10.0454C5.66469 11.2319 8.38084 14.8798 7.19914 15.6944Z"
|
||||
fill="#E3006D"/>
|
||||
<path d="M6.17718 4.85546C4.55454 5.58151 1.09762 7.06902 0.586141 5.86485C0.145207 4.85546 0.956526 2.80127 2.43806 1.42001C2.47334 1.38459 2.52625 1.36688 2.56152 1.33147C4.83674 -0.280007 6.52993 -0.0497963 7.35888 1.11897C8.13493 2.21689 7.2707 3.95233 6.17718 4.85546Z"
|
||||
fill="#9327FF"/>
|
||||
<path d="M15.0647 7.38845C13.9359 8.18533 12.1722 7.26449 11.2903 6.11344C10.5672 4.43113 9.17383 1.11964 10.3379 0.623799C11.3961 0.163377 13.5832 1.08422 14.9765 2.7134C16.4404 4.92697 16.2111 6.57386 15.0647 7.38845Z"
|
||||
fill="#00C8FF"/>
|
||||
<path d="M13.5466 14.5775C13.5113 14.6129 13.4761 14.6306 13.4232 14.666C11.1479 16.2775 9.45475 16.0473 8.62579 14.8785C7.84975 13.7806 8.71398 12.0452 9.80749 11.142C11.4301 10.416 14.8871 8.92848 15.3985 10.1327C15.8571 11.142 15.0458 13.1962 13.5466 14.5775Z"
|
||||
fill="#FFCE00"/>
|
||||
<path d="M5.68233 15.3757C4.62409 15.8361 2.43705 14.9329 1.06134 13.3038C-0.437833 11.0902 -0.19091 9.4256 0.955517 8.62871C2.08431 7.83183 3.84804 8.75267 4.72991 9.90373C5.45304 11.5683 6.84639 14.8798 5.68233 15.3757Z"
|
||||
fill="#FB006D"/>
|
||||
<path d="M33.036 4.63777H35.0241V5.38025C35.5126 4.86247 36.425 4.44727 37.4274 4.44727C39.5708 4.44727 40.9541 6.10807 40.9541 8.40584C40.9541 10.7739 39.3285 12.6067 36.8392 12.6067C36.1476 12.6067 35.4715 12.4855 35.0241 12.2267V15.8746H33.036V4.63777ZM35.0241 7.15926V10.306C35.5604 10.6518 36.0098 10.7554 36.6663 10.7554C38.0497 10.7554 38.8449 9.75206 38.8449 8.45566C38.8449 7.21103 38.119 6.31225 36.8051 6.31225C36.131 6.3142 35.5086 6.62585 35.0241 7.15926Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M28.1149 4.49121C25.6247 4.49121 24 6.32395 24 8.69206C24 10.9918 25.3833 12.6516 27.5268 12.6516C28.5291 12.6516 29.4455 12.2364 29.93 11.7167V12.4611H31.925V5.93415C30.4478 4.81556 28.8105 4.49121 28.1149 4.49121ZM29.93 9.93766C29.4416 10.4721 28.8232 10.7837 28.1491 10.7837C26.8361 10.7837 26.1092 9.88491 26.1092 8.64126C26.1092 7.34388 26.9044 6.34056 28.2868 6.34056C28.9443 6.34056 29.3937 6.4451 29.93 6.79093V9.93766Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M41.8025 4.63777H43.7906V5.38025C44.279 4.86247 45.1905 4.44727 46.1929 4.44727C48.3373 4.44727 49.7196 6.10807 49.7196 8.40584C49.7196 10.7739 48.095 12.6067 45.6057 12.6067C44.9141 12.6067 44.238 12.4855 43.7906 12.2267V15.8746H41.8025V4.63777ZM43.7906 7.15926V10.306C44.3259 10.6518 44.7753 10.7554 45.4328 10.7554C46.8152 10.7554 47.6104 9.75206 47.6104 8.45566C47.6104 7.21103 46.8845 6.31225 45.5706 6.31225C44.8965 6.3142 44.2742 6.62585 43.7906 7.15926Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M55.4834 2.40762C55.1236 2.15249 54.6971 2.00817 54.2563 1.99242C53.3575 1.99242 52.8886 2.4594 52.8886 3.53111V4.63798H54.2886V6.50492H52.8905V12.4174H50.9025V3.37577C50.9025 1.21478 51.94 0.125488 53.6682 0.125488C54.4634 0.125488 55.1903 0.320877 55.708 0.644244H57.4714V9.46115C57.4714 10.3404 57.6961 10.7224 58.1455 10.7224C58.4747 10.7224 58.7317 10.5846 58.9759 10.4117L59.3901 11.9328C58.9407 12.2962 58.2667 12.5893 57.3386 12.5893C56.1457 12.5893 55.4882 11.8634 55.4882 10.273L55.4834 2.40762Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M63.626 4.44727C66.2013 4.44727 67.8611 6.19306 67.8611 8.52698C67.8611 10.8609 66.2003 12.6067 63.626 12.6067C61.0518 12.6067 59.39 10.8609 59.39 8.52698C59.39 6.19306 61.0498 4.44727 63.626 4.44727ZM63.626 10.7573C64.8531 10.7573 65.7519 9.87809 65.7519 8.52698C65.7519 7.21299 64.8365 6.3142 63.626 6.3142C62.4537 6.3142 61.5168 7.1788 61.5168 8.52698C61.5168 9.82338 62.4156 10.7573 63.626 10.7573Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M77.6962 12.4513H75.9152L74.2544 8.12929C74.1333 7.8362 74.0815 7.50697 73.9946 7.21291C73.9312 7.53744 73.8387 7.85559 73.7181 8.16348L72.0768 12.4513H70.3838L67.8594 4.63574H69.9862L71.2308 8.87078C71.3286 9.21125 71.4037 9.55786 71.4555 9.9083C71.5344 9.55741 71.6322 9.21107 71.7486 8.87078L73.2003 4.63574H74.9588L76.4731 8.85515C76.5888 9.2082 76.6866 9.56687 76.7662 9.92979C76.8355 9.56734 76.9225 9.18634 77.0084 8.80631L78.1495 4.64063H80.1552L77.6962 12.4513Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M84.3345 12.4173C83.1934 15.1997 82.4676 15.8747 81.3265 15.8747C80.6173 15.8747 80.0985 15.5973 79.6491 15.269L80.289 13.7655C80.4961 13.8866 80.7902 14.042 81.1018 14.042C81.603 14.042 81.9654 13.6444 82.2947 12.8316L82.4676 12.4173L78.8715 4.63794H81.2591L83.0743 8.89057C83.2296 9.2706 83.3331 9.63304 83.4543 10.0141C83.5179 9.62734 83.6103 9.24592 83.7308 8.87298L85.1317 4.63794H87.3943L84.3345 12.4173Z"
|
||||
fill="currentColor"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.1 KiB |
|
|
@ -1,5 +1,5 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon_小三角" opacity="0.5">
|
||||
<g id="Icon_小三角">
|
||||
<path id="Vector 31"
|
||||
d="M5.83607 8.09961L3.51977 4.91469C3.30345 4.61725 3.51592 4.20001 3.8837 4.20001H8.5163C8.88408 4.20001 9.09655 4.61725 8.88023 4.91469L6.56393 8.09961C6.38422 8.3467 6.01578 8.3467 5.83607 8.09961Z"
|
||||
fill="currentColor"/>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 480 B After Width: | Height: | Size: 466 B |
|
|
@ -0,0 +1,41 @@
|
|||
import { Divider } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { ReactComponent as AppFlowyLogo } from '@/assets/appflowy.svg';
|
||||
|
||||
function AppFlowyPower ({
|
||||
divider,
|
||||
width,
|
||||
}: {
|
||||
divider?: boolean;
|
||||
width?: number;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backdropFilter: 'saturate(180%) blur(16px)',
|
||||
width,
|
||||
boxShadow: 'var(--footer) 0px -4px 14px 13px',
|
||||
}}
|
||||
className={'flex bg-bg-body sticky bottom-0 w-full flex-col items-center justify-center'}
|
||||
>
|
||||
{divider && <Divider className={'w-full my-0'} />}
|
||||
|
||||
<div
|
||||
onClick={() => {
|
||||
window.open('https://appflowy.io', '_blank');
|
||||
}}
|
||||
style={{
|
||||
width,
|
||||
}}
|
||||
className={
|
||||
'flex w-full cursor-pointer gap-2 items-center justify-center py-4 text-sm text-text-title opacity-50'
|
||||
}
|
||||
>
|
||||
Powered by
|
||||
<AppFlowyLogo className={'w-[88px]'} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppFlowyPower;
|
||||
|
|
@ -7,9 +7,13 @@ interface Props {
|
|||
open: boolean;
|
||||
onClose: () => void;
|
||||
placement?: PopperPlacementType;
|
||||
PaperProps?: {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export const RichTooltip = ({ placement = 'top', open, onClose, content, children }: Props) => {
|
||||
export const RichTooltip = ({ placement = 'top', open, onClose, content, children, PaperProps }: Props) => {
|
||||
const [childNode, setChildNode] = React.useState<HTMLElement | null>(null);
|
||||
const [, setTransitioning] = React.useState(false);
|
||||
|
||||
|
|
@ -48,7 +52,8 @@ export const RichTooltip = ({ placement = 'top', open, onClose, content, childre
|
|||
>
|
||||
<Paper className={'bg-transparent shadow-none'}>
|
||||
<ClickAwayListener onClickAway={onClose}>
|
||||
<Paper className={'m-2 rounded-md border border-line-divider bg-bg-body overflow-hidden'}>
|
||||
<Paper
|
||||
className={'m-2 rounded-md border border-line-divider bg-bg-body overflow-hidden'} {...PaperProps}>
|
||||
<Box>{content}</Box>
|
||||
</Paper>
|
||||
</ClickAwayListener>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import { Skeleton, Box } from '@mui/material';
|
||||
|
||||
export const BreadcrumbsSkeleton = () => {
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Skeleton variant="text" width={60} height={20} />
|
||||
<Skeleton variant="text" width={20} height={20} sx={{ mx: 1 }} />
|
||||
<Skeleton variant="text" width={80} height={20} />
|
||||
<Skeleton variant="text" width={20} height={20} sx={{ mx: 1 }} />
|
||||
<Skeleton variant="text" width={100} height={20} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreadcrumbsSkeleton;
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import Skeleton from '@mui/material/Skeleton';
|
||||
import React from 'react';
|
||||
|
||||
function DocumentSkeleton() {
|
||||
function DocumentSkeleton () {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '50vh',
|
||||
}}
|
||||
className={'mx-16 w-[964px] min-w-0 max-w-full px-16'}
|
||||
className={'mx-16 w-[964px] min-w-0 max-w-full max-xl:px-8 max-lg:px-6'}
|
||||
>
|
||||
<Skeleton variant='rectangular' width={'100%'} height={'100%'} />
|
||||
<Skeleton variant="rectangular" width={'100%'} height={'100%'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { Box, Skeleton } from '@mui/material';
|
||||
import './skeleton.scss';
|
||||
|
||||
export const DirectoryStructure = () => {
|
||||
return (
|
||||
<Box className={'w-full'}>
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
<div className="nested">
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
<div className="nested">
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
<div className="nested">
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
<div className="directory-item">
|
||||
<Skeleton variant="circular" width={20} height={20} />
|
||||
<Skeleton variant="text" className={'flex-1'} />
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
14
frontend/appflowy_web_app/src/components/_shared/skeleton/skeleton.scss
vendored
Normal file
14
frontend/appflowy_web_app/src/components/_shared/skeleton/skeleton.scss
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.directory-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
}
|
||||
|
||||
.directory-item .MuiSkeleton-root {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.nested {
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ function AsTemplate ({
|
|||
submitRef.current?.click();
|
||||
}} variant={'contained'} color={'primary'}
|
||||
>
|
||||
{t('template.asTemplate')}
|
||||
{t('button.save')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ $today-highlight-bg: transparent;
|
|||
border: 1px solid var(--line-divider);
|
||||
border-top: none;
|
||||
min-width: 1200px;
|
||||
@apply max-sm:w-[650vw];
|
||||
@apply max-sm:w-[600vw];
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ $today-highlight-bg: transparent;
|
|||
z-index: 50;
|
||||
min-width: 1200px;
|
||||
|
||||
@apply max-sm:w-[650vw];
|
||||
@apply max-sm:w-[600vw];
|
||||
|
||||
.rbc-header {
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import { Button } from '@mui/material';
|
|||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function NoDate({ emptyEvents }: { emptyEvents: CalendarEvent[] }) {
|
||||
function NoDate ({ emptyEvents }: { emptyEvents: CalendarEvent[] }) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const { t } = useTranslation();
|
||||
const content = useMemo(() => {
|
||||
return (
|
||||
<div className={'flex w-[260px] flex-col gap-3 p-2 text-xs font-medium'}>
|
||||
<div className={'flex w-[260px] flex-col gap-3 max-sm:gap-1 p-2 text-xs font-medium'}>
|
||||
{/*<div className={'text-text-caption'}>{t('calendar.settings.clickToOpen')}</div>*/}
|
||||
{emptyEvents.map((event) => {
|
||||
const rowId = event.id.split(':')[0];
|
||||
|
|
@ -34,7 +34,7 @@ function NoDate({ emptyEvents }: { emptyEvents: CalendarEvent[] }) {
|
|||
size={'small'}
|
||||
variant={'outlined'}
|
||||
disabled
|
||||
className={'rounded-md border-line-divider'}
|
||||
className={'rounded-md border-line-divider whitespace-nowrap overflow-hidden'}
|
||||
color={'inherit'}
|
||||
// onClick={() => setOpen(true)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { ReactComponent as DownArrow } from '$icons/16x/arrow_down.svg';
|
|||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function Toolbar({
|
||||
export function Toolbar ({
|
||||
onNavigate,
|
||||
date,
|
||||
emptyEvents,
|
||||
|
|
@ -24,12 +24,12 @@ export function Toolbar({
|
|||
return (
|
||||
<div className={'sticky left-0 flex items-center justify-between overflow-x-auto overflow-y-hidden'}>
|
||||
<div className={'whitespace-nowrap text-sm font-medium'}>{dateStr}</div>
|
||||
<div className={'flex items-center justify-end gap-2'}>
|
||||
<div className={'flex items-center justify-end gap-2 max-sm:gap-1'}>
|
||||
<IconButton size={'small'} onClick={() => onNavigate('PREV')}>
|
||||
<LeftArrow />
|
||||
</IconButton>
|
||||
<Button
|
||||
className={'h-6 font-normal'}
|
||||
className={'h-6 font-normal max-sm:min-w-fit'}
|
||||
size={'small'}
|
||||
variant={'text'}
|
||||
color={'inherit'}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { GetViewRowsMap, LoadView, LoadViewMeta, YDoc } from '@/application/collab.type';
|
||||
import DocumentSkeleton from '@/components/document/DocumentSkeleton';
|
||||
import DocumentSkeleton from '@/components/_shared/skeleton/DocumentSkeleton';
|
||||
import { Editor } from '@/components/editor';
|
||||
import React, { Suspense } from 'react';
|
||||
import ViewMetaPreview, { ViewMetaProps } from '@/components/view-meta/ViewMetaPreview';
|
||||
|
|
@ -24,10 +24,10 @@ export const Document = ({
|
|||
isTemplateThumb,
|
||||
}: DocumentProps) => {
|
||||
return (
|
||||
<div className={'mb-16 flex h-full w-full flex-col items-center justify-center'}>
|
||||
<div className={'mb-16 flex h-full w-full flex-col items-center min-h-[500px]'}>
|
||||
<ViewMetaPreview {...viewMeta} />
|
||||
<Suspense fallback={<DocumentSkeleton />}>
|
||||
<div className={'mx-16 w-[964px] min-w-0 max-w-full'}>
|
||||
<div className={'flex justify-center w-full'}>
|
||||
<Editor
|
||||
loadView={loadView}
|
||||
loadViewMeta={loadViewMeta}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ const EditorEditable = ({ editor }: { editor: ReactEditor }) => {
|
|||
|
||||
return decoration || [];
|
||||
}}
|
||||
className={'px-16 outline-none focus:outline-none max-md:px-4'}
|
||||
className={'outline-none w-[964px] min-w-0 max-w-full max-xl:px-8 max-lg:px-6 focus:outline-none'}
|
||||
renderLeaf={Leaf}
|
||||
renderElement={renderElement}
|
||||
readOnly={readOnly}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
export function getHeadingCssProperty(level: number) {
|
||||
export function getHeadingCssProperty (level: number) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return 'text-[1.75rem] pt-[10px] max-md:pt-[1.5vw] pb-[4px] max-md:pb-[1vw] font-bold max-sm:text-[6vw]';
|
||||
return 'text-[1.75rem] max-md:text-[24px] pt-[10px] max-md:pt-[1.5vw] pb-[4px] max-md:pb-[1vw] font-bold';
|
||||
case 2:
|
||||
return 'text-[1.55rem] pt-[8px] max-md:pt-[1vw] pb-[2px] max-md:pb-[0.5vw] font-bold max-sm:text-[5vw]';
|
||||
return 'text-[1.55rem] max-md:text-[22px] pt-[8px] max-md:pt-[1vw] pb-[2px] max-md:pb-[0.5vw] font-bold';
|
||||
case 3:
|
||||
return 'text-[1.35rem] pt-[4px] font-bold max-sm:text-[4vw]';
|
||||
return 'text-[1.35rem] max-md:text-[20px] pt-[4px] font-bold';
|
||||
case 4:
|
||||
return 'text-[1.25rem] pt-[4px] font-bold';
|
||||
return 'text-[1.25rem] max-md:text-[16px] pt-[4px] font-bold';
|
||||
case 5:
|
||||
return 'text-[1.15rem] pt-[4px] font-bold';
|
||||
case 6:
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import { EditorElementProps, TableCellNode, TableNode } from '@/components/editor/editor.type';
|
||||
import React, { forwardRef, memo, useMemo } from 'react';
|
||||
import { getScrollParent } from '@/components/global-comment/utils';
|
||||
import React, { forwardRef, memo, useEffect, useMemo, useRef, useCallback } from 'react';
|
||||
import { Grid } from '@atlaskit/primitives';
|
||||
import './table.scss';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { ReactEditor, useSlateStatic } from 'slate-react';
|
||||
|
||||
const Table = memo(
|
||||
forwardRef<HTMLDivElement, EditorElementProps<TableNode>>(({ node, children, className, ...attributes }, ref) => {
|
||||
const { rowsLen, colsLen, rowDefaultHeight, colsHeight } = node.data;
|
||||
const cells = node.children as TableCellNode[];
|
||||
const [width, setWidth] = React.useState(0);
|
||||
const offsetLeftRef = useRef(0);
|
||||
|
||||
const columnGroup = useMemo(() => {
|
||||
return Array.from({ length: colsLen }, (_, index) => {
|
||||
|
|
@ -37,18 +41,56 @@ const Table = memo(
|
|||
.join(' ');
|
||||
}, [rowGroup, rowDefaultHeight]);
|
||||
|
||||
const editor = useSlateStatic();
|
||||
|
||||
const calcTableWidth = useCallback((editorDom: HTMLElement, scrollContainer: HTMLElement) => {
|
||||
|
||||
const scrollRect = scrollContainer.getBoundingClientRect();
|
||||
|
||||
setWidth(scrollRect.width);
|
||||
offsetLeftRef.current = editorDom.getBoundingClientRect().left - scrollRect.left;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const editorDom = ReactEditor.toDOMNode(editor, editor);
|
||||
const scrollContainer = getScrollParent(editorDom) as HTMLElement;
|
||||
|
||||
if (!scrollContainer) return;
|
||||
calcTableWidth(editorDom, scrollContainer);
|
||||
const onResize = () => {
|
||||
calcTableWidth(editorDom, scrollContainer);
|
||||
};
|
||||
|
||||
const resizeObserver = new ResizeObserver(onResize);
|
||||
|
||||
resizeObserver.observe(scrollContainer);
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [calcTableWidth, editor]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
{...attributes}
|
||||
className={`table-block relative my-2 w-full overflow-hidden px-1 ${className || ''}`}
|
||||
style={{
|
||||
...attributes.style,
|
||||
width,
|
||||
maxWidth: width,
|
||||
flex: 'none',
|
||||
left: -offsetLeftRef.current,
|
||||
}}
|
||||
>
|
||||
<div className={'h-full w-full overflow-x-auto overflow-y-hidden'}>
|
||||
<div className={'h-full w-full overflow-x-auto overflow-y-hidden'} style={{
|
||||
paddingLeft: offsetLeftRef.current + 'px',
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
id={`table-${node.blockId}`}
|
||||
rowGap='space.0'
|
||||
autoFlow='column'
|
||||
columnGap='space.0'
|
||||
rowGap="space.0"
|
||||
autoFlow="column"
|
||||
columnGap="space.0"
|
||||
templateRows={templateRows}
|
||||
templateColumns={templateColumns}
|
||||
>
|
||||
|
|
@ -58,7 +100,7 @@ const Table = memo(
|
|||
</div>
|
||||
);
|
||||
}),
|
||||
(prevProps, nextProps) => isEqual(prevProps.node, nextProps.node)
|
||||
(prevProps, nextProps) => isEqual(prevProps.node, nextProps.node),
|
||||
);
|
||||
|
||||
export default Table;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
|
||||
[id^=table-] {
|
||||
width: fit-content;
|
||||
@apply border-t border-l border-line-border;
|
||||
@apply border-t border-l border-line-divider;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
@apply border-b border-r border-line-border;
|
||||
@apply border-b border-r border-line-divider;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import CircularProgress from '@mui/material/CircularProgress';
|
|||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function GlobalComment() {
|
||||
function GlobalComment () {
|
||||
const { t } = useTranslation();
|
||||
const { loading, comments } = useGlobalCommentContext();
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ function GlobalComment() {
|
|||
<div className={'mb-[480px] mt-16 flex h-fit w-full justify-center max-md:mb-[100px]'}>
|
||||
<div
|
||||
className={
|
||||
'flex w-[964px] min-w-0 max-w-full transform flex-col gap-2 px-16 transition-all duration-300 ease-in-out max-sm:px-4'
|
||||
'flex w-[964px] min-w-0 max-w-full max-xl:px-8 transform flex-col gap-2 transition-all duration-300 ease-in-out max-lg:px-6'
|
||||
}
|
||||
>
|
||||
<div className={'text-[24px]'}>{t('globalComment.comments')}</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export function AddCommentWrapper () {
|
|||
const { replyCommentId } = useGlobalCommentContext();
|
||||
const addCommentRef = useRef<HTMLDivElement>(null);
|
||||
const [showFixedAddComment, setShowFixedAddComment] = useState(false);
|
||||
const [offsetLeft, setOffsetLeft] = useState(0);
|
||||
const [focus, setFocus] = useState(false);
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
|
|
@ -22,12 +23,14 @@ export function AddCommentWrapper () {
|
|||
const element = addCommentRef.current;
|
||||
|
||||
if (!element) return;
|
||||
|
||||
const scrollContainer = getScrollParent(element);
|
||||
|
||||
if (!scrollContainer) return;
|
||||
|
||||
const handleScroll = () => {
|
||||
const isIntersecting = element.getBoundingClientRect().top < HEADER_HEIGHT;
|
||||
const rect = element.getBoundingClientRect();
|
||||
const isIntersecting = rect.top < HEADER_HEIGHT;
|
||||
|
||||
if (isIntersecting) {
|
||||
setShowFixedAddComment(true);
|
||||
|
|
@ -42,6 +45,20 @@ export function AddCommentWrapper () {
|
|||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const element = addCommentRef.current;
|
||||
|
||||
if (!element) return;
|
||||
const scrollContainer = getScrollParent(element);
|
||||
|
||||
if (!scrollContainer) return;
|
||||
if (showFixedAddComment) {
|
||||
setOffsetLeft(scrollContainer.getBoundingClientRect().left || 0);
|
||||
} else {
|
||||
setOffsetLeft(0);
|
||||
}
|
||||
}, [showFixedAddComment]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'my-2'} id="addComment" ref={addCommentRef}>
|
||||
|
|
@ -55,8 +72,12 @@ export function AddCommentWrapper () {
|
|||
</div>
|
||||
{showFixedAddComment && (
|
||||
<Portal container={document.body}>
|
||||
<div className={'fixed top-[48px] flex w-full justify-center'}>
|
||||
<div className={'w-[964px] min-w-0 max-w-full px-16 max-sm:px-4'}>
|
||||
<div style={{
|
||||
left: offsetLeft + 'px',
|
||||
width: `calc(100% - ${offsetLeft}px)`,
|
||||
}} className={'fixed top-[48px] flex w-full justify-center'}
|
||||
>
|
||||
<div className={'w-[964px] min-w-0 max-w-full max-xl:px-8 max-lg:px-6'}>
|
||||
<AddComment fixed content={content} setContent={setContent} focus={focus} setFocus={setFocus} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ function DatabaseView ({ viewMeta, ...props }: DatabaseProps) {
|
|||
minHeight: 'calc(100vh - 48px)',
|
||||
maxWidth: isTemplateThumb ? '964px' : undefined,
|
||||
}}
|
||||
className={'relative flex h-full w-full flex-col px-16 max-md:px-4'}
|
||||
className={'relative flex h-full w-full flex-col px-16 max-xl:px-8 max-lg:px-6'}
|
||||
>
|
||||
<DatabaseHeader {...viewMeta} />
|
||||
<Suspense fallback={<ComponentLoading />}>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import { AFConfigContext } from '@/components/app/app.hooks';
|
|||
import { GlobalCommentProvider } from '@/components/global-comment';
|
||||
import CollabView from '@/components/publish/CollabView';
|
||||
import { OutlineDrawer } from '@/components/publish/outline';
|
||||
import React, { Suspense, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { getPlatform } from '@/utils/platform';
|
||||
import React, { Suspense, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { PublishViewHeader } from '@/components/publish/header';
|
||||
import NotFound from '@/components/error/NotFound';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
|
@ -21,7 +22,6 @@ const drawerWidth = 268;
|
|||
export function PublishView ({ namespace, publishName }: PublishViewProps) {
|
||||
const [doc, setDoc] = useState<YDoc | undefined>();
|
||||
const [notFound, setNotFound] = useState<boolean>(false);
|
||||
|
||||
const service = useContext(AFConfigContext)?.service;
|
||||
const openPublishView = useCallback(async () => {
|
||||
let doc;
|
||||
|
|
@ -42,18 +42,47 @@ export function PublishView ({ namespace, publishName }: PublishViewProps) {
|
|||
void openPublishView();
|
||||
}, [openPublishView]);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(() => {
|
||||
return localStorage.getItem('publish_outline_open') === 'true';
|
||||
});
|
||||
|
||||
const [search] = useSearchParams();
|
||||
|
||||
const isTemplate = search.get('template') === 'true';
|
||||
const isTemplateThumb = isTemplate && search.get('thumbnail') === 'true';
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('publish_outline_open', open ? 'true' : 'false');
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTemplateThumb) return;
|
||||
document.documentElement.setAttribute('thumbnail', 'true');
|
||||
}, [isTemplateThumb]);
|
||||
|
||||
const drawerOpened = useMemo(() => open && !getPlatform().isMobile, [open]);
|
||||
const siderWidth = drawerOpened ? drawerWidth : 0;
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
const isMobile = getPlatform().isMobile;
|
||||
|
||||
if (!isMobile && window.innerWidth - siderWidth < 640) {
|
||||
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
onResize();
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
}, [siderWidth]);
|
||||
|
||||
if (notFound && !doc) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
|
@ -73,8 +102,8 @@ export function PublishView ({ namespace, publishName }: PublishViewProps) {
|
|||
overflowXHidden
|
||||
overflowYHidden={isTemplateThumb}
|
||||
style={{
|
||||
transform: open ? `translateX(${drawerWidth}px)` : 'none',
|
||||
width: open ? `calc(100% - ${drawerWidth}px)` : '100%',
|
||||
transform: drawerOpened ? `translateX(${drawerWidth}px)` : 'none',
|
||||
width: drawerOpened ? `calc(100% - ${drawerWidth}px)` : '100%',
|
||||
transition: 'width 0.2s ease-in-out, transform 0.2s ease-in-out',
|
||||
}}
|
||||
className={'appflowy-layout appflowy-scroll-container h-full'}
|
||||
|
|
@ -83,7 +112,11 @@ export function PublishView ({ namespace, publishName }: PublishViewProps) {
|
|||
onOpenDrawer={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
openDrawer={open}
|
||||
drawerWidth={drawerWidth}
|
||||
onCloseDrawer={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
openDrawer={drawerOpened}
|
||||
/>}
|
||||
|
||||
<CollabView doc={doc} />
|
||||
|
|
@ -92,8 +125,10 @@ export function PublishView ({ namespace, publishName }: PublishViewProps) {
|
|||
<GlobalCommentProvider />
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
</AFScroller>
|
||||
{open && <OutlineDrawer width={drawerWidth} open={open} onClose={() => setOpen(false)} />}
|
||||
{drawerOpened &&
|
||||
<OutlineDrawer width={drawerWidth} open={drawerOpened} onClose={() => setOpen(false)} />}
|
||||
</div>
|
||||
</PublishProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ function BreadcrumbItem ({ crumb, disableClick = false }: { crumb: Crumb; disabl
|
|||
return (
|
||||
<Tooltip title={name} placement={'bottom'} enterDelay={1000} enterNextDelay={1000}>
|
||||
<div
|
||||
className={`flex items-center gap-1 text-sm ${!disableClick ? 'cursor-pointer' : 'flex-1 overflow-hidden'}`}
|
||||
className={`flex items-center gap-1.5 text-sm ${!disableClick ? 'cursor-pointer' : 'flex-1 overflow-hidden'}`}
|
||||
onClick={async () => {
|
||||
if (disableClick) return;
|
||||
try {
|
||||
|
|
@ -54,10 +54,10 @@ function BreadcrumbItem ({ crumb, disableClick = false }: { crumb: Crumb; disabl
|
|||
>
|
||||
{extraObj && extraObj.is_space ? (
|
||||
<span
|
||||
className={'icon h-5 w-5'}
|
||||
className={'icon h-4 w-4'}
|
||||
style={{
|
||||
backgroundColor: extraObj.space_icon_color ? renderColor(extraObj.space_icon_color) : 'rgb(163, 74, 253)',
|
||||
borderRadius: '8px',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
>
|
||||
<SpaceIcon value={extraObj.space_icon || ''} char={extraObj.space_icon ? undefined : name.slice(0, 1)} />
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ import { ReactComponent as SunIcon } from '@/assets/sun.svg';
|
|||
import { ReactComponent as LoginIcon } from '@/assets/login.svg';
|
||||
import { ReactComponent as ReportIcon } from '@/assets/report.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as Logo } from '@/assets/logo.svg';
|
||||
import { ReactComponent as AppflowyLogo } from '@/assets/appflowy.svg';
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
function MoreActions () {
|
||||
|
|
@ -110,18 +107,6 @@ function MoreActions () {
|
|||
</button>
|
||||
))}
|
||||
|
||||
<div
|
||||
onClick={() => {
|
||||
window.open('https://appflowy.io', '_blank');
|
||||
}}
|
||||
className={
|
||||
'flex w-full cursor-pointer items-center justify-center py-2 text-sm text-text-title opacity-50'
|
||||
}
|
||||
>
|
||||
Powered by
|
||||
<Logo className={'ml-3 h-4 w-4'} />
|
||||
<AppflowyLogo className={'w-20'} />
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { usePublishContext } from '@/application/publish';
|
|||
import { useCurrentUser } from '@/components/app/app.hooks';
|
||||
import { openOrDownload } from '@/components/publish/header/utils';
|
||||
import { createHotkey, HOT_KEY_NAME } from '@/utils/hotkeys';
|
||||
import { getPlatform } from '@/utils/platform';
|
||||
import { Divider, IconButton, Tooltip } from '@mui/material';
|
||||
import { debounce } from 'lodash-es';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
|
|
@ -15,7 +16,14 @@ import { Duplicate } from './duplicate';
|
|||
|
||||
export const HEADER_HEIGHT = 48;
|
||||
|
||||
export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer: () => void; openDrawer: boolean }) {
|
||||
export function PublishViewHeader ({
|
||||
drawerWidth, onOpenDrawer, openDrawer, onCloseDrawer,
|
||||
}: {
|
||||
onOpenDrawer: () => void;
|
||||
drawerWidth: number;
|
||||
openDrawer: boolean;
|
||||
onCloseDrawer: () => void
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const viewMeta = usePublishContext()?.viewMeta;
|
||||
const crumbs = useMemo(() => {
|
||||
|
|
@ -28,7 +36,7 @@ export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer:
|
|||
const extra = ancestor?.extra ? JSON.parse(ancestor.extra) : {};
|
||||
|
||||
icon = extra.icon?.value || ancestor.icon?.value;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +50,9 @@ export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer:
|
|||
});
|
||||
}, [viewMeta]);
|
||||
const [openPopover, setOpenPopover] = React.useState(false);
|
||||
const isMobile = useMemo(() => {
|
||||
return getPlatform().isMobile;
|
||||
}, []);
|
||||
|
||||
const debounceClosePopover = useMemo(() => {
|
||||
return debounce(() => {
|
||||
|
|
@ -50,15 +61,20 @@ export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer:
|
|||
}, []);
|
||||
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
switch(true) {
|
||||
switch (true) {
|
||||
case createHotkey(HOT_KEY_NAME.TOGGLE_SIDEBAR)(e):
|
||||
e.preventDefault();
|
||||
// setOpen((prev) => !prev);
|
||||
if (openDrawer) {
|
||||
onCloseDrawer();
|
||||
} else {
|
||||
onOpenDrawer();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, []);
|
||||
}, [onCloseDrawer, onOpenDrawer, openDrawer]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
|
|
@ -69,13 +85,18 @@ export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer:
|
|||
|
||||
const handleOpenPopover = useCallback(() => {
|
||||
debounceClosePopover.cancel();
|
||||
if(openDrawer) {
|
||||
if (openDrawer) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpenPopover(true);
|
||||
}, [openDrawer, debounceClosePopover]);
|
||||
|
||||
const debounceOpenPopover = useMemo(() => {
|
||||
debounceClosePopover.cancel();
|
||||
return debounce(handleOpenPopover, 100);
|
||||
}, [handleOpenPopover, debounceClosePopover]);
|
||||
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
const isAppFlowyUser = currentUser?.email?.endsWith('@appflowy.io');
|
||||
|
|
@ -89,22 +110,31 @@ export function PublishViewHeader({ onOpenDrawer, openDrawer }: { onOpenDrawer:
|
|||
}}
|
||||
className={'appflowy-top-bar sticky top-0 z-10 flex px-5'}
|
||||
>
|
||||
<div className={'flex w-full items-center justify-between gap-2 overflow-hidden'}>
|
||||
{!openDrawer && openPopover && (
|
||||
<div className={'flex w-full items-center justify-between gap-4 overflow-hidden'}>
|
||||
{!openDrawer && (
|
||||
<OutlinePopover
|
||||
onMouseEnter={handleOpenPopover}
|
||||
onMouseLeave={debounceClosePopover}
|
||||
{...isMobile ? undefined : {
|
||||
onMouseEnter: handleOpenPopover,
|
||||
onMouseLeave: debounceClosePopover,
|
||||
}}
|
||||
open={openPopover}
|
||||
onClose={debounceClosePopover}
|
||||
drawerWidth={drawerWidth}
|
||||
>
|
||||
<IconButton
|
||||
className={'hidden'}
|
||||
onClick={() => {
|
||||
setOpenPopover(false);
|
||||
onOpenDrawer();
|
||||
{...isMobile ? {
|
||||
onTouchEnd: () => {
|
||||
setOpenPopover(prev => !prev);
|
||||
},
|
||||
} : {
|
||||
onMouseEnter: debounceOpenPopover,
|
||||
onMouseLeave: debounceClosePopover,
|
||||
onClick: () => {
|
||||
setOpenPopover(false);
|
||||
onOpenDrawer();
|
||||
},
|
||||
}}
|
||||
onMouseEnter={handleOpenPopover}
|
||||
onMouseLeave={debounceClosePopover}
|
||||
|
||||
>
|
||||
<SideOutlined className={'h-4 w-4'} />
|
||||
</IconButton>
|
||||
|
|
|
|||
|
|
@ -1,62 +1,29 @@
|
|||
import { PublishViewInfo, ViewLayout } from '@/application/collab.type';
|
||||
import { usePublishContext } from '@/application/publish';
|
||||
import emptyImageSrc from '@/assets/images/empty.png';
|
||||
import { DirectoryStructure } from '@/components/_shared/skeleton/OutlineSkeleton';
|
||||
import OutlineItem from '@/components/publish/outline/OutlineItem';
|
||||
import SearchInput from '@/components/publish/outline/SearchInput';
|
||||
import { filterViews } from '@/components/publish/outline/utils';
|
||||
import { CircularProgress } from '@mui/material';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React from 'react';
|
||||
|
||||
function Outline({ viewMeta, width }: { viewMeta?: PublishViewInfo; width: number }) {
|
||||
const hasChildren = Boolean(viewMeta?.child_views?.length);
|
||||
const { t } = useTranslation();
|
||||
const [children, setChildren] = React.useState<PublishViewInfo[]>([]);
|
||||
function Outline ({ width }: { width: number }) {
|
||||
const outline = usePublishContext()?.outline;
|
||||
|
||||
useEffect(() => {
|
||||
if (viewMeta) {
|
||||
setChildren(viewMeta.child_views || []);
|
||||
}
|
||||
}, [viewMeta]);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(val: string) => {
|
||||
if (!val) {
|
||||
return setChildren(viewMeta?.child_views || []);
|
||||
}
|
||||
|
||||
setChildren(filterViews(viewMeta?.child_views || [], val));
|
||||
},
|
||||
[viewMeta]
|
||||
);
|
||||
|
||||
if (!viewMeta) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
const isEmpty = outline && outline.children.length === 0;
|
||||
|
||||
return (
|
||||
<div className={'flex w-full flex-1 flex-col items-start justify-between gap-2'}>
|
||||
<div
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '44px',
|
||||
<div className={'flex w-full flex-1 flex-col gap-1 py-[10px] px-[10px]'}>
|
||||
{isEmpty && <img src={emptyImageSrc} alt={'No data found'} className={'mx-auto h-[200px]'} />}
|
||||
{!outline ? <div style={{
|
||||
width: width - 20,
|
||||
}}
|
||||
className={'z-10 flex items-center justify-center gap-3 bg-bg-body'}
|
||||
>
|
||||
<SearchInput onSearch={handleSearch} />
|
||||
</div>
|
||||
|
||||
{hasChildren ? (
|
||||
<div className={'flex w-full flex-1 flex-col'}>
|
||||
{children
|
||||
.filter((view) => view.layout === ViewLayout.Document)
|
||||
.map((view: PublishViewInfo) => (
|
||||
<OutlineItem width={width} key={view.view_id} view={view} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={'flex w-full flex-1 items-center justify-center text-text-caption'}>{t('noPagesInside')}</div>
|
||||
)}
|
||||
><DirectoryStructure /></div> :
|
||||
outline.children.map((view) =>
|
||||
<OutlineItem
|
||||
key={view.view_id}
|
||||
view={view}
|
||||
width={width - 20}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import { usePublishContext } from '@/application/publish';
|
||||
import { ReactComponent as AppflowyLogo } from '@/assets/appflowy.svg';
|
||||
import { ReactComponent as Logo } from '@/assets/logo.svg';
|
||||
import { ReactComponent as AppFlowyLogo } from '@/assets/appflowy.svg';
|
||||
import { ReactComponent as SideOutlined } from '@/assets/side_outlined.svg';
|
||||
import AppFlowyPower from '@/components/_shared/appflowy-power/AppFlowyPower';
|
||||
import Outline from '@/components/publish/outline/Outline';
|
||||
import { createHotKeyLabel, HOT_KEY_NAME } from '@/utils/hotkeys';
|
||||
import { Drawer, IconButton, Tooltip } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function OutlineDrawer({ open, width, onClose }: { open: boolean; width: number; onClose: () => void }) {
|
||||
export function OutlineDrawer ({ open, width, onClose }: { open: boolean; width: number; onClose: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const viewMeta = usePublishContext()?.viewMeta;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
|
|
@ -24,22 +22,29 @@ export function OutlineDrawer({ open, width, onClose }: { open: boolean; width:
|
|||
boxShadow: 'none',
|
||||
},
|
||||
}}
|
||||
variant='persistent'
|
||||
anchor='left'
|
||||
variant="persistent"
|
||||
anchor="left"
|
||||
open={open}
|
||||
tabIndex={0}
|
||||
autoFocus
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className={'flex h-full flex-col'}>
|
||||
<div className={'flex h-[48px] items-center justify-between p-4'}>
|
||||
<div className={'flex h-full relative min-h-full flex-col overflow-y-auto overflow-x-hidden appflowy-scroller'}>
|
||||
<div style={{
|
||||
backdropFilter: 'blur(4px)',
|
||||
}} className={'flex z-10 h-[48px] sticky top-0 items-center justify-between p-4'}
|
||||
>
|
||||
<div
|
||||
className={'flex cursor-pointer items-center gap-1 text-text-title'}
|
||||
onClick={() => {
|
||||
window.open('https://appflowy.io', '_blank');
|
||||
}}
|
||||
>
|
||||
<Logo className={'h-5 w-5'} />
|
||||
<AppflowyLogo className={'w-24'} />
|
||||
<AppFlowyLogo className={'w-[88px]'} />
|
||||
</div>
|
||||
<Tooltip
|
||||
title={
|
||||
|
|
@ -54,9 +59,10 @@ export function OutlineDrawer({ open, width, onClose }: { open: boolean; width:
|
|||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={'flex flex-1 flex-col overflow-y-auto px-4 pb-4'}>
|
||||
<Outline width={width} viewMeta={viewMeta} />
|
||||
<div className={'flex h-fit flex-1 flex-col'}>
|
||||
<Outline width={width} />
|
||||
</div>
|
||||
<AppFlowyPower width={width} />
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,24 @@
|
|||
import { PublishViewInfo, ViewLayout } from '@/application/collab.type';
|
||||
import { PublishContext } from '@/application/publish';
|
||||
import { PublishContext, usePublishContext } from '@/application/publish';
|
||||
import { View } from '@/application/types';
|
||||
import { notify } from '@/components/_shared/notify';
|
||||
import { ViewIcon } from '@/components/_shared/view-icon';
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import SpaceIcon from '@/components/publish/header/SpaceIcon';
|
||||
import { renderColor } from '@/utils/color';
|
||||
import { isFlagEmoji } from '@/utils/emoji';
|
||||
import React, { useCallback, useContext, useEffect } from 'react';
|
||||
import { ReactComponent as ChevronDownIcon } from '@/assets/chevron_down.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function OutlineItem({ view, level = 0, width }: { view: PublishViewInfo; width: number; level?: number }) {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
function OutlineItem ({ view, level = 0, width }: { view: View; width: number; level?: number }) {
|
||||
const [isExpanded, setIsExpanded] = React.useState(() => {
|
||||
return localStorage.getItem('publish_outline_expanded_' + view.view_id) === 'true';
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('publish_outline_expanded_' + view.view_id, isExpanded ? 'true' : 'false');
|
||||
}, [isExpanded, view.view_id]);
|
||||
|
||||
const selected = usePublishContext()?.viewMeta?.view_id === view.view_id;
|
||||
const getIcon = useCallback(() => {
|
||||
if (isExpanded) {
|
||||
return (
|
||||
|
|
@ -18,6 +29,7 @@ function OutlineItem({ view, level = 0, width }: { view: PublishViewInfo; width:
|
|||
onClick={() => {
|
||||
setIsExpanded(false);
|
||||
}}
|
||||
className={'opacity-50 hover:opacity-100'}
|
||||
>
|
||||
<ChevronDownIcon className={'h-4 w-4'} />
|
||||
</button>
|
||||
|
|
@ -29,6 +41,7 @@ function OutlineItem({ view, level = 0, width }: { view: PublishViewInfo; width:
|
|||
style={{
|
||||
paddingLeft: 1.125 * level + 'rem',
|
||||
}}
|
||||
className={'opacity-50 hover:opacity-100'}
|
||||
onClick={() => {
|
||||
setIsExpanded(true);
|
||||
}}
|
||||
|
|
@ -40,34 +53,53 @@ function OutlineItem({ view, level = 0, width }: { view: PublishViewInfo; width:
|
|||
const { t } = useTranslation();
|
||||
|
||||
const navigateToView = useContext(PublishContext)?.toView;
|
||||
const renderItem = (item: PublishViewInfo) => {
|
||||
const { icon, layout, name, view_id } = item;
|
||||
const renderItem = (item: View) => {
|
||||
const { icon, layout, name, view_id, extra } = item;
|
||||
|
||||
const isSpace = extra?.is_space;
|
||||
|
||||
return (
|
||||
<div className={'flex h-fit flex-col gap-2'}>
|
||||
<div className={'flex h-fit my-0.5 w-full flex-col gap-2'}>
|
||||
<div
|
||||
style={{
|
||||
width: width - 32,
|
||||
width,
|
||||
backgroundColor: selected ? 'var(--fill-list-hover)' : undefined,
|
||||
}}
|
||||
className={
|
||||
'flex items-center gap-0.5 rounded-[8px] p-1.5 text-sm hover:bg-content-blue-50 focus:bg-content-blue-50 focus:outline-none'
|
||||
'flex items-center w-full gap-0.5 rounded-[8px] py-1.5 px-0.5 text-sm hover:bg-content-blue-50 focus:bg-content-blue-50 focus:outline-none'
|
||||
}
|
||||
>
|
||||
{item.child_views?.length ? getIcon() : null}
|
||||
{item.children?.length ? getIcon() : null}
|
||||
<div
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigateToView?.(view_id);
|
||||
} catch (e) {
|
||||
notify.error(t('publish.hasNotBeenPublished'));
|
||||
notify.default(t('publish.hasNotBeenPublished'));
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
paddingLeft: item.child_views?.length ? 0 : 1.125 * (level + 1) + 'rem',
|
||||
paddingLeft: item.children?.length ? 0 : 1.125 * (level + 1) + 'rem',
|
||||
}}
|
||||
className={'flex flex-1 cursor-pointer items-center gap-1.5 overflow-hidden'}
|
||||
>
|
||||
<div className={'icon'}>{icon?.value || <ViewIcon layout={layout} size={'small'} />}</div>
|
||||
{isSpace && extra ?
|
||||
<span
|
||||
className={'icon h-4 w-4'}
|
||||
style={{
|
||||
backgroundColor: extra.space_icon_color ? renderColor(extra.space_icon_color) : 'rgb(163, 74, 253)',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
>
|
||||
<SpaceIcon
|
||||
value={extra.space_icon || ''}
|
||||
char={extra.space_icon ? undefined : name.slice(0, 1)}
|
||||
/></span> :
|
||||
<div className={`${icon && isFlagEmoji(icon.value) ? 'icon' : ''}`}>
|
||||
{icon?.value || <ViewIcon layout={layout} size={'small'} />}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className={'flex-1 truncate'}>{name}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -75,10 +107,10 @@ function OutlineItem({ view, level = 0, width }: { view: PublishViewInfo; width:
|
|||
);
|
||||
};
|
||||
|
||||
const children = view.child_views || [];
|
||||
const children = view.children || [];
|
||||
|
||||
return (
|
||||
<div className={'flex h-fit flex-col'}>
|
||||
<div className={'flex h-fit w-full flex-col'}>
|
||||
{renderItem(view)}
|
||||
<div
|
||||
className={'flex transform flex-col gap-2 transition-all'}
|
||||
|
|
@ -87,7 +119,6 @@ function OutlineItem({ view, level = 0, width }: { view: PublishViewInfo; width:
|
|||
}}
|
||||
>
|
||||
{children
|
||||
.filter((view) => view.layout === ViewLayout.Document)
|
||||
.map((item, index) => (
|
||||
<OutlineItem level={level + 1} width={width} key={index} view={item} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { usePublishContext } from '@/application/publish';
|
||||
import AppFlowyPower from '@/components/_shared/appflowy-power/AppFlowyPower';
|
||||
import Outline from '@/components/publish/outline/Outline';
|
||||
import { Divider, PopperPlacementType } from '@mui/material';
|
||||
import { PopperPlacementType } from '@mui/material';
|
||||
import React, { ReactElement, useMemo } from 'react';
|
||||
import { RichTooltip } from '@/components/_shared/popover';
|
||||
import { ReactComponent as Logo } from '@/assets/logo.svg';
|
||||
import { ReactComponent as AppflowyLogo } from '@/assets/appflowy.svg';
|
||||
|
||||
export function OutlinePopover({
|
||||
export function OutlinePopover ({
|
||||
children,
|
||||
open,
|
||||
onClose,
|
||||
placement,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
drawerWidth,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
|
|
@ -20,6 +20,7 @@ export function OutlinePopover({
|
|||
placement?: PopperPlacementType;
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
drawerWidth: number;
|
||||
}) {
|
||||
const viewMeta = usePublishContext()?.viewMeta;
|
||||
|
||||
|
|
@ -28,36 +29,21 @@ export function OutlinePopover({
|
|||
<div
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={'flex h-fit max-h-[500px] w-[268px] flex-col overflow-y-auto overflow-x-hidden p-2'}
|
||||
>
|
||||
<Outline width={268} viewMeta={viewMeta} />
|
||||
<div
|
||||
style={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
height: '44px',
|
||||
}}
|
||||
className={'flex flex-col items-center justify-center gap-3 bg-bg-body'}
|
||||
>
|
||||
{Boolean(viewMeta?.child_views?.length) && <Divider className={'w-full'} />}
|
||||
|
||||
<div
|
||||
onClick={() => {
|
||||
window.open('https://appflowy.io', '_blank');
|
||||
}}
|
||||
className={'flex w-full cursor-pointer items-center justify-center text-sm text-text-title opacity-50'}
|
||||
>
|
||||
<Logo className={'h-4 w-4'} />
|
||||
<AppflowyLogo className={'w-20'} />
|
||||
</div>
|
||||
</div>
|
||||
className={'flex h-fit max-h-[590px] flex-col overflow-y-auto overflow-x-hidden appflowy-scroller'}
|
||||
>
|
||||
<Outline width={drawerWidth} />
|
||||
|
||||
<AppFlowyPower divider={Boolean(viewMeta?.child_views?.length)} />
|
||||
</div>
|
||||
);
|
||||
}, [onMouseEnter, onMouseLeave, viewMeta]);
|
||||
}, [onMouseEnter, onMouseLeave, viewMeta, drawerWidth]);
|
||||
|
||||
return (
|
||||
<RichTooltip open={open} onClose={onClose} content={content} placement={placement}>
|
||||
<RichTooltip PaperProps={{
|
||||
className: 'rounded-[14px] border border-tint-purple bg-bg-body m-2 overflow-hidden',
|
||||
}} open={open} onClose={onClose} content={content} placement={placement}
|
||||
>
|
||||
{children}
|
||||
</RichTooltip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -63,16 +63,16 @@ export function ViewMetaPreview ({ icon, cover, name }: ViewMetaProps) {
|
|||
<div className={'flex w-full flex-col items-center'}>
|
||||
{cover && <ViewCover coverType={coverType} coverValue={coverValue} />}
|
||||
<div
|
||||
className={`relative mx-16 mb-6 mt-[52px] max-md:mt-[38px] w-[964px] min-w-0 max-w-full overflow-visible max-md:mx-4`}
|
||||
className={`relative mb-6 mt-[52px] max-md:mt-[38px] max-xl:px-8 w-[964px] min-w-0 max-w-full overflow-visible max-lg:px-6`}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'flex gap-4 overflow-hidden whitespace-pre-wrap break-words break-all px-16 text-[2.25rem] font-bold leading-[2em] max-md:px-4 max-sm:text-[7vw]'
|
||||
'flex gap-4 overflow-hidden whitespace-pre-wrap break-words break-all text-[2.25rem] font-bold max-md:text-[26px]'
|
||||
}
|
||||
>
|
||||
{icon?.value ? <div className={`view-icon ${isFlag ? 'icon' : ''}`}>{icon?.value}</div> : null}
|
||||
|
||||
<div className={'relative top-1.5'}>
|
||||
<div className={'relative'}>
|
||||
{name || <span className={'text-text-placeholder'}>{t('menuAppHeader.defaultNewPageName')}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Button, Typography } from '@mui/material';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { ReactComponent as Logo } from '@/assets/logo.svg';
|
||||
import { ReactComponent as AppflowyLogo } from '@/assets/appflowy.svg';
|
||||
|
||||
function AfterPaymentPage() {
|
||||
function AfterPaymentPage () {
|
||||
const openAppFlowy = useCallback(() => {
|
||||
window.open(`appflowy-flutter://payment-success/${window.location.search || ''}`, '_self');
|
||||
}, []);
|
||||
|
|
@ -14,9 +13,8 @@ function AfterPaymentPage() {
|
|||
return (
|
||||
<div className={'m-0 flex h-screen w-screen items-center justify-center bg-bg-body p-6'}>
|
||||
<div className={'flex max-w-[560px] flex-col items-center gap-1 text-center'}>
|
||||
<Typography variant='h3' className={'mb-[27px] flex items-center gap-4 text-text-title'} gutterBottom>
|
||||
<Typography variant="h3" className={'mb-[27px] flex items-center gap-4 text-text-title'} gutterBottom>
|
||||
<>
|
||||
<Logo className={'w-9'} />
|
||||
<AppflowyLogo className={'w-32'} />
|
||||
</>
|
||||
</Typography>
|
||||
|
|
@ -31,8 +29,8 @@ function AfterPaymentPage() {
|
|||
</div>
|
||||
<Button
|
||||
onClick={openAppFlowy}
|
||||
variant='contained'
|
||||
color='primary'
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={
|
||||
'mt-[32px] mb-[48px] h-[68px] rounded-[20px] px-[44px] py-[18px] text-[20px] font-medium leading-[120%] text-content-on-fill'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ body {
|
|||
|
||||
.view-icon {
|
||||
@apply flex w-fit cursor-pointer rounded-lg text-[1.5em];
|
||||
line-height: 1.5em;
|
||||
line-height: 1em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,4 +62,5 @@
|
|||
--gradient6: linear-gradient(180deg, #036FFA 0%, #00B8E5 100%);
|
||||
--gradient7: linear-gradient(38.2deg, #F0C6CF 0%, #DECCE2 40.4754%, #CAD3F9 100%);
|
||||
--header: rgba(0, 0, 0, 0.7);
|
||||
--footer: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
|
@ -65,4 +65,5 @@
|
|||
--gradient6: linear-gradient(180deg, #036FFA 0%, #00B8E5 100%);
|
||||
--gradient7: linear-gradient(38.2deg, #F0C6CF 0%, #DECCE2 40.4754%, #CAD3F9 100%);
|
||||
--header: rgba(255, 255, 255, 0.8);
|
||||
--footer: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
|
@ -2473,7 +2473,7 @@
|
|||
"commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?"
|
||||
},
|
||||
"template": {
|
||||
"asTemplate": "As template",
|
||||
"asTemplate": "Save as template",
|
||||
"name": "Template name",
|
||||
"description": "Template Description",
|
||||
"about": "Template About",
|
||||
|
|
|
|||
Loading…
Reference in a new issue