diff --git a/frontend/appflowy_web_app/nginx.conf b/frontend/appflowy_web_app/nginx.conf index 9a1db35d27..e0a658c310 100644 --- a/frontend/appflowy_web_app/nginx.conf +++ b/frontend/appflowy_web_app/nginx.conf @@ -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; diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_1.png b/frontend/appflowy_web_app/public/covers/m_cover_image_1.png new file mode 100644 index 0000000000..fb72022287 Binary files /dev/null and b/frontend/appflowy_web_app/public/covers/m_cover_image_1.png differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_2.png b/frontend/appflowy_web_app/public/covers/m_cover_image_2.png new file mode 100644 index 0000000000..9ecf02d253 Binary files /dev/null and b/frontend/appflowy_web_app/public/covers/m_cover_image_2.png differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_3.png b/frontend/appflowy_web_app/public/covers/m_cover_image_3.png new file mode 100644 index 0000000000..97072b04f4 Binary files /dev/null and b/frontend/appflowy_web_app/public/covers/m_cover_image_3.png differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_4.png b/frontend/appflowy_web_app/public/covers/m_cover_image_4.png new file mode 100644 index 0000000000..00d26a0500 Binary files /dev/null and b/frontend/appflowy_web_app/public/covers/m_cover_image_4.png differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_5.png b/frontend/appflowy_web_app/public/covers/m_cover_image_5.png new file mode 100644 index 0000000000..3ecc9546c1 Binary files /dev/null and b/frontend/appflowy_web_app/public/covers/m_cover_image_5.png differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_6.png b/frontend/appflowy_web_app/public/covers/m_cover_image_6.png new file mode 100644 index 0000000000..0abd2700e8 Binary files /dev/null and b/frontend/appflowy_web_app/public/covers/m_cover_image_6.png differ diff --git a/frontend/appflowy_web_app/public/launch_splash.jpg b/frontend/appflowy_web_app/public/launch_splash.jpg deleted file mode 100644 index 7e3bb9cee6..0000000000 Binary files a/frontend/appflowy_web_app/public/launch_splash.jpg and /dev/null differ diff --git a/frontend/appflowy_web_app/server.cjs b/frontend/appflowy_web_app/server.cjs index faf7230223..450f161038 100644 --- a/frontend/appflowy_web_app/server.cjs +++ b/frontend/appflowy_web_app/server.cjs @@ -56,13 +56,14 @@ 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, publishName, @@ -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); diff --git a/frontend/appflowy_web_app/src/application/services/js-services/index.ts b/frontend/appflowy_web_app/src/application/services/js-services/index.ts index e2939aa79a..41a2821ddf 100644 --- a/frontend/appflowy_web_app/src/application/services/js-services/index.ts +++ b/frontend/appflowy_web_app/src/application/services/js-services/index.ts @@ -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 { diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts b/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts index 0e473517d8..4584206862 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/__tests__/convert.test.ts @@ -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); diff --git a/frontend/appflowy_web_app/src/assets/bulleted_list_icon_1.svg b/frontend/appflowy_web_app/src/assets/bulleted_list_icon_1.svg new file mode 100644 index 0000000000..ce98736a7b --- /dev/null +++ b/frontend/appflowy_web_app/src/assets/bulleted_list_icon_1.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/appflowy_web_app/src/assets/bulleted_list_icon_2.svg b/frontend/appflowy_web_app/src/assets/bulleted_list_icon_2.svg new file mode 100644 index 0000000000..df8c868b53 --- /dev/null +++ b/frontend/appflowy_web_app/src/assets/bulleted_list_icon_2.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/appflowy_web_app/src/assets/bulleted_list_icon_3.svg b/frontend/appflowy_web_app/src/assets/bulleted_list_icon_3.svg new file mode 100644 index 0000000000..c1d880916c --- /dev/null +++ b/frontend/appflowy_web_app/src/assets/bulleted_list_icon_3.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/appflowy_web_app/src/components/app/AppConfig.tsx b/frontend/appflowy_web_app/src/components/app/AppConfig.tsx index b0649b3404..16f70dba1b 100644 --- a/frontend/appflowy_web_app/src/components/app/AppConfig.tsx +++ b/frontend/appflowy_web_app/src/components/app/AppConfig.tsx @@ -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`, }, }; diff --git a/frontend/appflowy_web_app/src/components/editor/Editable.tsx b/frontend/appflowy_web_app/src/components/editor/Editable.tsx index 3c46314f38..f7e728c483 100644 --- a/frontend/appflowy_web_app/src/components/editor/Editable.tsx +++ b/frontend/appflowy_web_app/src/components/editor/Editable.tsx @@ -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) => , []); + const renderElement = useCallback( + (props: RenderElementProps) => ( + }> + + + ), + [] + ); return ( <> diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/bulleted-list/BulletedListIcon.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/bulleted-list/BulletedListIcon.tsx index 62e06b6ba9..2494f3de99 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/bulleted-list/BulletedListIcon.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/bulleted-list/BulletedListIcon.tsx @@ -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`} /> ); } diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx index 84b6883256..91f36fb02c 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx @@ -9,6 +9,7 @@ export const LinkPreview = memo( title: string; description: string; } | null>(null); + const [notFound, setNotFound] = useState(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`} > -
- {data ? ( -
+
+ {notFound ? ( +
+
Could not load preview
+
{url}
+
+ ) : ( + <> {data.title}
- {data.title} + {data?.title}
- {data.description} + {data?.description}
{url}
-
- ) : ( - - {node.data.url} - + )}
diff --git a/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx b/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx index 1041490dbe..79cfd15d79 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/element/Element.tsx @@ -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 ( - }> - -
- - {children} - -
-
-
+ +
+ + {children} + +
+
); }, (prevProps, nextProps) => isEqual(prevProps.element, nextProps.element) diff --git a/frontend/appflowy_web_app/src/components/editor/editor.scss b/frontend/appflowy_web_app/src/components/editor/editor.scss index 69924d1f36..c892aec847 100644 --- a/frontend/appflowy_web_app/src/components/editor/editor.scss +++ b/frontend/appflowy_web_app/src/components/editor/editor.scss @@ -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 { diff --git a/frontend/appflowy_web_app/src/styles/app.scss b/frontend/appflowy_web_app/src/styles/app.scss index f69936e2e6..74acc671cc 100644 --- a/frontend/appflowy_web_app/src/styles/app.scss +++ b/frontend/appflowy_web_app/src/styles/app.scss @@ -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; }