-
{getPageIcon(page)}
- {page.name || t('menuAppHeader.defaultNewPageName')}
+
+
{getPageIcon(page)}
+ {page.name.trim() || t('menuAppHeader.defaultNewPageName')}
);
}
@@ -54,7 +54,7 @@ function Breadcrumb() {
>
{getPageIcon(page)}
- {page.name || t('document.title.placeholder')}
+ {page.name.trim() || t('menuAppHeader.defaultNewPageName')}
);
})}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPageTitle.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPageTitle.tsx
index 448fdc441a..948aedcae2 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPageTitle.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPageTitle.tsx
@@ -76,7 +76,7 @@ function NestedPageTitle({
{pageIcon}
- {page?.name || t('menuAppHeader.defaultNewPageName')}
+ {page?.name.trim() || t('menuAppHeader.defaultNewPageName')}
e.stopPropagation()} className={'min:w-14 flex items-center justify-end px-2'}>
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.hooks.ts
index e98a846da0..b6748614b8 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.hooks.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.hooks.ts
@@ -39,6 +39,9 @@ export function useLoadTrash() {
export function useTrashActions() {
const [restoreAllDialogOpen, setRestoreAllDialogOpen] = useState(false);
const [deleteAllDialogOpen, setDeleteAllDialogOpen] = useState(false);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+
+ const [deleteId, setDeleteId] = useState('');
const onClickRestoreAll = () => {
setRestoreAllDialogOpen(true);
@@ -51,9 +54,18 @@ export function useTrashActions() {
const closeDialog = () => {
setRestoreAllDialogOpen(false);
setDeleteAllDialogOpen(false);
+ setDeleteDialogOpen(false);
+ };
+
+ const onClickDelete = (id: string) => {
+ setDeleteId(id);
+ setDeleteDialogOpen(true);
};
return {
+ onClickDelete,
+ deleteDialogOpen,
+ deleteId,
onPutback: putback,
onDelete: deleteTrashItem,
onDeleteAll: deleteAll,
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.tsx
index 40f51d1fbf..f10848dc9b 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.tsx
@@ -20,6 +20,9 @@ function Trash() {
onRestoreAll,
onDeleteAll,
closeDialog,
+ deleteDialogOpen,
+ deleteId,
+ onClickDelete,
} = useTrashActions();
const [hoverId, setHoverId] = useState('');
@@ -50,7 +53,7 @@ function Trash() {
item={item}
key={item.id}
onPutback={onPutback}
- onDelete={onDelete}
+ onDelete={onClickDelete}
hoverId={hoverId}
setHoverId={setHoverId}
/>
@@ -62,6 +65,7 @@ function Trash() {
subtitle={t('trash.confirmRestoreAll.caption')}
onOk={onRestoreAll}
onClose={closeDialog}
+ okText={t('trash.restoreAll')}
/>
+ onDelete([deleteId])}
+ onClose={closeDialog}
+ />
);
}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/trash/TrashItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/trash/TrashItem.tsx
index 9d4bb15628..d266005612 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/components/trash/TrashItem.tsx
+++ b/frontend/appflowy_tauri/src/appflowy_app/components/trash/TrashItem.tsx
@@ -17,7 +17,7 @@ function TrashItem({
item: Trash;
hoverId: string;
onPutback: (id: string) => void;
- onDelete: (ids: string[]) => void;
+ onDelete: (id: string) => void;
}) {
const { t } = useTranslation();
@@ -35,7 +35,9 @@ function TrashItem({
}}
>
-
{item.name || t('document.title.placeholder')}
+
+ {item.name.trim() || t('menuAppHeader.defaultNewPageName')}
+
{dayjs.unix(item.modifiedTime).format('MM/DD/YYYY hh:mm A')}
{dayjs.unix(item.createTime).format('MM/DD/YYYY hh:mm A')}
- onDelete([item.id])}>
+ onDelete(item.id)}>
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/hotkeys.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/hotkeys.ts
index fab7f0612f..9e58429e8d 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/hotkeys.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/hotkeys.ts
@@ -22,40 +22,62 @@ export enum HOT_KEY_NAME {
UNDERLINE = 'underline',
STRIKETHROUGH = 'strikethrough',
CODE = 'code',
+ TOGGLE_TODO = 'toggle-todo',
+ TOGGLE_COLLAPSE = 'toggle-collapse',
}
const defaultHotKeys = {
- [HOT_KEY_NAME.ALIGN_LEFT]: 'control+shift+l',
- [HOT_KEY_NAME.ALIGN_CENTER]: 'control+shift+e',
- [HOT_KEY_NAME.ALIGN_RIGHT]: 'control+shift+r',
- [HOT_KEY_NAME.BOLD]: 'mod+b',
- [HOT_KEY_NAME.ITALIC]: 'mod+i',
- [HOT_KEY_NAME.UNDERLINE]: 'mod+u',
- [HOT_KEY_NAME.STRIKETHROUGH]: 'mod+shift+s',
- [HOT_KEY_NAME.CODE]: 'mod+shift+c',
+ [HOT_KEY_NAME.ALIGN_LEFT]: ['control+shift+l'],
+ [HOT_KEY_NAME.ALIGN_CENTER]: ['control+shift+e'],
+ [HOT_KEY_NAME.ALIGN_RIGHT]: ['control+shift+r'],
+ [HOT_KEY_NAME.BOLD]: ['mod+b'],
+ [HOT_KEY_NAME.ITALIC]: ['mod+i'],
+ [HOT_KEY_NAME.UNDERLINE]: ['mod+u'],
+ [HOT_KEY_NAME.STRIKETHROUGH]: ['mod+shift+s', 'mod+shift+x'],
+ [HOT_KEY_NAME.CODE]: ['mod+e'],
+ [HOT_KEY_NAME.TOGGLE_TODO]: ['mod+enter'],
+ [HOT_KEY_NAME.TOGGLE_COLLAPSE]: ['mod+enter'],
};
const replaceModifier = (hotkey: string) => {
return hotkey.replace('mod', getModifier()).replace('control', 'ctrl');
};
-export const createHotkey = (hotkeyName: HOT_KEY_NAME, customHotKeys?: Record) => {
+/**
+ * Create a hotkey checker.
+ * @example trigger strike through when user press "Cmd + Shift + S" or "Cmd + Shift + X"
+ * @param hotkeyName
+ * @param customHotKeys
+ */
+export const createHotkey = (hotkeyName: HOT_KEY_NAME, customHotKeys?: Record) => {
const keys = customHotKeys || defaultHotKeys;
- const hotkey = keys[hotkeyName];
+ const hotkeys = keys[hotkeyName];
return (event: KeyboardEvent) => {
- return isHotkey(hotkey, event);
+ return hotkeys.some((hotkey) => {
+ return isHotkey(hotkey, event);
+ });
};
};
-export const createHotKeyLabel = (hotkeyName: HOT_KEY_NAME, customHotKeys?: Record) => {
+/**
+ * Create a hotkey label.
+ * eg. "Ctrl + B / ⌘ + B"
+ * @param hotkeyName
+ * @param customHotKeys
+ */
+export const createHotKeyLabel = (hotkeyName: HOT_KEY_NAME, customHotKeys?: Record) => {
const keys = customHotKeys || defaultHotKeys;
- const hotkey = replaceModifier(keys[hotkeyName]);
+ const hotkeys = keys[hotkeyName].map((key) => replaceModifier(key));
- return hotkey
- .split('+')
- .map((key) => {
- return key === ' ' ? 'Space' : key.charAt(0).toUpperCase() + key.slice(1);
- })
- .join(' + ');
+ return hotkeys
+ .map((hotkey) =>
+ hotkey
+ .split('+')
+ .map((key) => {
+ return key === ' ' ? 'Space' : key.charAt(0).toUpperCase() + key.slice(1);
+ })
+ .join(' + ')
+ )
+ .join(' / ');
};
diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/open_url.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/open_url.ts
index 3fd9933a45..d854be5211 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/utils/open_url.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/utils/open_url.ts
@@ -1,9 +1,14 @@
import { open as openWindow } from '@tauri-apps/api/shell';
-export const pattern = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\S*)*\/?(\?[=&\w.%-]*)?(#[\w.\-!~*'()]*)?$/;
+const urlPattern = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\S*)*\/?(\?[=&\w.%-]*)?(#[\w.\-!~*'()]*)?$/;
+const ipPattern = /^(https?:\/\/)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d{1,5})?$/;
+
+export function isUrl(str: string) {
+ return urlPattern.test(str) || ipPattern.test(str);
+}
export function openUrl(str: string) {
- if (pattern.test(str)) {
+ if (isUrl(str)) {
const linkPrefix = ['http://', 'https://', 'file://', 'ftp://', 'ftps://', 'mailto:'];
if (linkPrefix.some((prefix) => str.startsWith(prefix))) {
diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json
index cd703d4a56..8807a3a057 100644
--- a/frontend/resources/translations/en.json
+++ b/frontend/resources/translations/en.json
@@ -144,7 +144,8 @@
"emptyDescription": "You don't have any deleted file",
"isDeleted": "is deleted",
"isRestored": "is restored"
- }
+ },
+ "confirmDeleteTitle": "Are you sure you want to delete this page permanently?"
},
"deletePagePrompt": {
"text": "This page is in Trash",
diff --git a/frontend/rust-lib/flowy-document/src/parser/constant.rs b/frontend/rust-lib/flowy-document/src/parser/constant.rs
index 27e817114d..20edb93871 100644
--- a/frontend/rust-lib/flowy-document/src/parser/constant.rs
+++ b/frontend/rust-lib/flowy-document/src/parser/constant.rs
@@ -107,7 +107,6 @@ pub const TEXT_DECORATION: &str = "text-decoration";
pub const BACKGROUND_COLOR: &str = "background-color";
pub const TRANSPARENT: &str = "transparent";
-pub const DEFAULT_FONT_COLOR: &str = "rgb(0, 0, 0)";
pub const COLOR: &str = "color";
pub const LINE_THROUGH: &str = "line-through";
diff --git a/frontend/rust-lib/flowy-document/src/parser/external/utils.rs b/frontend/rust-lib/flowy-document/src/parser/external/utils.rs
index 257f81e772..1e31792f2f 100644
--- a/frontend/rust-lib/flowy-document/src/parser/external/utils.rs
+++ b/frontend/rust-lib/flowy-document/src/parser/external/utils.rs
@@ -428,10 +428,6 @@ fn get_attributes_with_style(style: &str) -> HashMap {
attributes.insert(BG_COLOR.to_string(), Value::String(value.to_string()));
},
COLOR => {
- if value.eq(DEFAULT_FONT_COLOR) {
- continue;
- }
-
attributes.insert(FONT_COLOR.to_string(), Value::String(value.to_string()));
},
_ => {},
diff --git a/frontend/rust-lib/flowy-document/tests/assets/json/simple.json b/frontend/rust-lib/flowy-document/tests/assets/json/simple.json
index 9a27d97913..2ab6a3275e 100644
--- a/frontend/rust-lib/flowy-document/tests/assets/json/simple.json
+++ b/frontend/rust-lib/flowy-document/tests/assets/json/simple.json
@@ -2,7 +2,10 @@
"type": "page",
"data": {
"delta": [{
- "insert": "This is a paragraph"
+ "insert": "This is a paragraph",
+ "attributes": {
+ "font_color": "rgb(0, 0, 0)"
+ }
}]
},
"children": []