fix: modified bullted icon style
@ -72,6 +72,12 @@ http {
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location /covers/ {
|
||||
root /usr/share/nginx/html;
|
||||
expires 30d;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
location = /404.html {
|
||||
root /usr/share/nginx/html;
|
||||
|
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_1.png
Normal file
After Width: | Height: | Size: 2.2 MiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_2.png
Normal file
After Width: | Height: | Size: 731 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_3.png
Normal file
After Width: | Height: | Size: 465 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_4.png
Normal file
After Width: | Height: | Size: 526 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_5.png
Normal file
After Width: | Height: | Size: 293 KiB |
BIN
frontend/appflowy_web_app/public/covers/m_cover_image_6.png
Normal file
After Width: | Height: | Size: 765 KiB |
Before Width: | Height: | Size: 1.1 MiB |
@ -56,12 +56,13 @@ const fetchMetaData = async (url) => {
|
||||
}
|
||||
};
|
||||
|
||||
const BASE_URL = process.env.AF_BASE_URL || 'https://beta.appflowy.cloud';
|
||||
const createServer = async (req) => {
|
||||
const timer = logRequestTimer(req);
|
||||
const reqUrl = new URL(req.url);
|
||||
|
||||
logger.info(`Request URL: ${reqUrl.pathname}`);
|
||||
const hostname = req.headers.get('host');
|
||||
|
||||
logger.info(`Request URL: ${hostname}${reqUrl.pathname}`);
|
||||
|
||||
const [
|
||||
namespace,
|
||||
@ -85,7 +86,11 @@ const createServer = async (req) => {
|
||||
let metaData;
|
||||
|
||||
try {
|
||||
metaData = await fetchMetaData(`${BASE_URL}/api/workspace/published/${namespace}/${publishName}`);
|
||||
const isBeta = hostname.startsWith('beta');
|
||||
const isTest = hostname.startsWith('test');
|
||||
const defaultUrl = 'https://beta.appflowy.cloud';
|
||||
const baseUrl = isBeta ? 'https://beta.appflowy.cloud' : isTest ? 'https://test.appflowy.cloud' : defaultUrl;
|
||||
metaData = await fetchMetaData(`${baseUrl}/api/workspace/published/${namespace}/${publishName}`);
|
||||
} catch (error) {
|
||||
logger.error(`Error fetching meta data: ${error}`);
|
||||
}
|
||||
@ -105,8 +110,13 @@ const createServer = async (req) => {
|
||||
|
||||
try {
|
||||
const cover = metaData.view.extra ? JSON.parse(metaData.view.extra)?.cover : null;
|
||||
if (cover && ['unsplash', 'custom'].includes(cover.type)) {
|
||||
image = cover.value;
|
||||
if (cover) {
|
||||
if (['unsplash', 'custom'].includes(cover.type)) {
|
||||
image = cover.value;
|
||||
} else if (cover.type === 'built_in') {
|
||||
image = `/covers/m_cover_image_${cover.value}.png`;
|
||||
}
|
||||
|
||||
}
|
||||
} catch (_) {
|
||||
// Do nothing
|
||||
@ -152,7 +162,6 @@ const start = () => {
|
||||
},
|
||||
});
|
||||
logger.info(`Server is running on port 3000`);
|
||||
logger.info(`Base API URL: ${process.env.AF_BASE_URL}`);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
|
@ -61,6 +61,7 @@ export class AFClientService implements AFService {
|
||||
const name = `${namespace}_${publishName}`;
|
||||
|
||||
const isLoaded = this.publishViewLoaded.has(name);
|
||||
|
||||
const doc = await getPublishView(
|
||||
async () => {
|
||||
try {
|
||||
|
@ -7,7 +7,7 @@ describe('convert yjs data to slate content', () => {
|
||||
it('should return undefined if root block is not exist', () => {
|
||||
const doc = new Y.Doc();
|
||||
|
||||
expect(() => yDocToSlateContent(doc)).toThrowError();
|
||||
expect(() => yDocToSlateContent(doc)).toBeUndefined();
|
||||
|
||||
const doc2 = withTestingYDoc('1');
|
||||
const { blocks, childrenMap, textMap, pageId } = getTestingDocData(doc2);
|
||||
|
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="2.75" fill="#454545"/>
|
||||
</svg>
|
After Width: | Height: | Size: 151 B |
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="2.75" stroke="#454545"/>
|
||||
</svg>
|
After Width: | Height: | Size: 153 B |
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="5" height="5" transform="translate(6.5 6.5)" fill="#454545"/>
|
||||
</svg>
|
After Width: | Height: | Size: 178 B |
@ -4,23 +4,25 @@ import React, { createContext, useEffect, useState } from 'react';
|
||||
import { AFService, AFServiceConfig } from '@/application/services/services.type';
|
||||
import { getService } from '@/application/services';
|
||||
|
||||
const hostName = window.location.hostname;
|
||||
const isProd = !hostName.includes('localhost');
|
||||
const isBeta = isProd && hostName.includes('beta');
|
||||
const isTest = isProd && hostName.includes('test');
|
||||
const baseAPIHost = isProd
|
||||
? isBeta
|
||||
? 'beta.appflowy.cloud'
|
||||
: isTest
|
||||
? 'test.appflowy.cloud'
|
||||
: 'beta.appflowy.cloud'
|
||||
: 'test.appflowy.cloud';
|
||||
const baseURL = `https://${baseAPIHost}`;
|
||||
const gotrueURL = `${baseURL}/gotrue`;
|
||||
|
||||
const defaultConfig: AFServiceConfig = {
|
||||
cloudConfig: {
|
||||
baseURL: import.meta.env.AF_BASE_URL
|
||||
? import.meta.env.AF_BASE_URL
|
||||
: import.meta.env.DEV
|
||||
? 'https://test.appflowy.cloud'
|
||||
: 'https://beta.appflowy.cloud',
|
||||
gotrueURL: import.meta.env.AF_GOTRUE_URL
|
||||
? import.meta.env.AF_GOTRUE_URL
|
||||
: import.meta.env.DEV
|
||||
? 'https://test.appflowy.cloud/gotrue'
|
||||
: 'https://beta.appflowy.cloud/gotrue',
|
||||
wsURL: import.meta.env.AF_WS_URL
|
||||
? import.meta.env.AF_WS_URL
|
||||
: import.meta.env.DEV
|
||||
? 'wss://test.appflowy.cloud/ws/v1'
|
||||
: 'wss://beta.appflowy.cloud/ws/v1',
|
||||
baseURL,
|
||||
gotrueURL,
|
||||
wsURL: `wss://${baseAPIHost}/ws/v1`,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { useDecorate } from '@/components/editor/components/blocks/code/useDecorate';
|
||||
import { Leaf } from '@/components/editor/components/leaf';
|
||||
import { useEditorContext } from '@/components/editor/EditorContext';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { Suspense, useCallback } from 'react';
|
||||
import { NodeEntry } from 'slate';
|
||||
import { Editable, ReactEditor, RenderElementProps } from 'slate-react';
|
||||
import { Element } from './components/element';
|
||||
import { Skeleton } from '@mui/material';
|
||||
|
||||
const EditorEditable = ({ editor }: { editor: ReactEditor }) => {
|
||||
const { readOnly } = useEditorContext();
|
||||
@ -17,7 +18,14 @@ const EditorEditable = ({ editor }: { editor: ReactEditor }) => {
|
||||
[codeDecorate]
|
||||
);
|
||||
|
||||
const renderElement = useCallback((props: RenderElementProps) => <Element {...props} />, []);
|
||||
const renderElement = useCallback(
|
||||
(props: RenderElementProps) => (
|
||||
<Suspense fallback={<Skeleton width={'100%'} height={24} />}>
|
||||
<Element {...props} />
|
||||
</Suspense>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -28,11 +28,11 @@ export function BulletedListIcon({ block, className }: { block: BulletedListNode
|
||||
const dataLetter = useMemo(() => {
|
||||
switch (letter) {
|
||||
case Letter.Disc:
|
||||
return '•';
|
||||
return 'disc';
|
||||
case Letter.Circle:
|
||||
return '◦';
|
||||
return 'circle';
|
||||
case Letter.Square:
|
||||
return '▪';
|
||||
return 'square';
|
||||
}
|
||||
}, [letter]);
|
||||
|
||||
@ -41,9 +41,8 @@ export function BulletedListIcon({ block, className }: { block: BulletedListNode
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
data-letter={dataLetter}
|
||||
contentEditable={false}
|
||||
className={`${className} bulleted-icon flex min-w-[24px] justify-center pr-1 font-medium`}
|
||||
className={`${className} bulleted-icon ${dataLetter} flex min-w-[24px] justify-center pr-1 font-medium`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ export const LinkPreview = memo(
|
||||
title: string;
|
||||
description: string;
|
||||
} | null>(null);
|
||||
const [notFound, setNotFound] = useState<boolean>(false);
|
||||
const url = node.data.url;
|
||||
|
||||
useEffect(() => {
|
||||
@ -17,14 +18,19 @@ export const LinkPreview = memo(
|
||||
setData(null);
|
||||
void (async () => {
|
||||
try {
|
||||
setNotFound(false);
|
||||
const response = await axios.get(`https://api.microlink.io/?url=${url}`);
|
||||
|
||||
if (response.data.statusCode !== 200) return;
|
||||
if (response.data.statusCode !== 200) {
|
||||
setNotFound(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = response.data.data;
|
||||
|
||||
setData(data);
|
||||
} catch (error) {
|
||||
// don't do anything
|
||||
} catch (_) {
|
||||
setNotFound(true);
|
||||
}
|
||||
})();
|
||||
}, [url]);
|
||||
@ -37,17 +43,22 @@ export const LinkPreview = memo(
|
||||
ref={ref}
|
||||
className={`link-preview-block relative w-full cursor-pointer py-1`}
|
||||
>
|
||||
<div>
|
||||
{data ? (
|
||||
<div
|
||||
className={
|
||||
'container-bg flex w-full cursor-pointer select-none items-center gap-4 overflow-hidden rounded border border-line-divider bg-fill-list-active p-3'
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'container-bg flex w-full cursor-pointer select-none items-center gap-4 overflow-hidden rounded border border-line-divider bg-fill-list-active p-3'
|
||||
}
|
||||
>
|
||||
{notFound ? (
|
||||
<div className={'flex w-full items-center justify-center'}>
|
||||
<div className={'text-text-title'}>Could not load preview</div>
|
||||
<div className={'text-sm text-text-caption'}>{url}</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src={data.image.url}
|
||||
alt={data.title}
|
||||
className={'container h-full w-[25%] rounded bg-cover bg-center'}
|
||||
src={data?.image.url}
|
||||
alt={data?.title}
|
||||
className={'container h-full min-h-[48px] w-[25%] rounded bg-cover bg-center'}
|
||||
/>
|
||||
<div className={'flex flex-col justify-center gap-2 overflow-hidden'}>
|
||||
<div
|
||||
@ -55,22 +66,18 @@ export const LinkPreview = memo(
|
||||
'max-h-[48px] overflow-hidden whitespace-pre-wrap break-words text-base font-bold text-text-title'
|
||||
}
|
||||
>
|
||||
{data.title}
|
||||
{data?.title}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
'max-h-[64px] overflow-hidden truncate whitespace-pre-wrap break-words text-sm text-text-title'
|
||||
}
|
||||
>
|
||||
{data.description}
|
||||
{data?.description}
|
||||
</div>
|
||||
<div className={'truncate whitespace-nowrap text-xs text-text-caption'}>{url}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<a href={node.data.url} className={'text-content-blue-400 underline'} target={'blank'}>
|
||||
{node.data.url}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div ref={ref} className={'absolute left-0 top-0 h-full w-full caret-transparent'}>
|
||||
|
@ -16,7 +16,6 @@ import { Quote } from '@/components/editor/components/blocks/quote';
|
||||
import { TableBlock, TableCellBlock } from '@/components/editor/components/blocks/table';
|
||||
import { Text } from '@/components/editor/components/blocks/text';
|
||||
import { ElementFallbackRender } from '@/components/error/ElementFallbackRender';
|
||||
import { Skeleton } from '@mui/material';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { TodoList } from 'src/components/editor/components/blocks/todo-list';
|
||||
import { ToggleList } from 'src/components/editor/components/blocks/toggle-list';
|
||||
@ -25,7 +24,7 @@ import { Formula } from '@/components/editor/components/leaf/formula';
|
||||
import { Mention } from '@/components/editor/components/leaf/mention';
|
||||
import { EditorElementProps, TextNode } from '@/components/editor/editor.type';
|
||||
import { renderColor } from '@/utils/color';
|
||||
import React, { FC, memo, Suspense, useMemo } from 'react';
|
||||
import React, { FC, memo, useMemo } from 'react';
|
||||
import { RenderElementProps } from 'slate-react';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
|
||||
@ -126,15 +125,13 @@ export const Element = memo(
|
||||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Skeleton width={'100%'} height={24} />}>
|
||||
<ErrorBoundary fallbackRender={ElementFallbackRender}>
|
||||
<div {...attributes} data-block-type={node.type} className={className}>
|
||||
<Component style={style} className={`flex w-full flex-col`} node={node}>
|
||||
{children}
|
||||
</Component>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</Suspense>
|
||||
<ErrorBoundary fallbackRender={ElementFallbackRender}>
|
||||
<div {...attributes} data-block-type={node.type} className={className}>
|
||||
<Component style={style} className={`flex w-full flex-col`} node={node}>
|
||||
{children}
|
||||
</Component>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => isEqual(prevProps.element, nextProps.element)
|
||||
|
@ -197,10 +197,22 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
|
||||
}
|
||||
|
||||
.bulleted-icon {
|
||||
&:after {
|
||||
content: attr(data-letter);
|
||||
font-weight: 500;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px 16px;
|
||||
background-position: center;
|
||||
|
||||
&.disc {
|
||||
background-image: url('../../assets/bulleted_list_icon_1.svg');
|
||||
}
|
||||
|
||||
&.circle {
|
||||
background-image: url('../../assets/bulleted_list_icon_2.svg');
|
||||
}
|
||||
|
||||
&.square {
|
||||
background-image: url('../../assets/bulleted_list_icon_3.svg');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.numbered-icon {
|
||||
|
@ -52,12 +52,12 @@ body {
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-family: 'Apple Color Emoji', 'Noto Color Emoji', 'Segoe UI Emoji', 'Twemoji Mozilla', sans-serif;
|
||||
font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji Mozilla', sans-serif;
|
||||
}
|
||||
|
||||
.view-icon {
|
||||
@apply flex w-fit leading-[1.5em] cursor-pointer rounded-lg py-2 text-[1.5em];
|
||||
font-family: 'Apple Color Emoji', 'Noto Color Emoji', 'Segoe UI Emoji', 'Twemoji Mozilla', sans-serif;
|
||||
font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji Mozilla', sans-serif;
|
||||
line-height: 1em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|