mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-23 19:49:56 +01:00
feat: Change default font size & font family in editor
This commit is contained in:
@@ -48,6 +48,8 @@ export type Settings = {
|
||||
keyboardShown?: boolean;
|
||||
doubleSpacedLines?: boolean;
|
||||
corsProxy: string;
|
||||
fontSize: string;
|
||||
fontFamily: string;
|
||||
};
|
||||
|
||||
export type EditorProps = {
|
||||
|
||||
@@ -136,6 +136,13 @@ export const useEditorEvents = (
|
||||
const doubleSpacedLines = useSettingStore(
|
||||
(state) => state.settings?.doubleSpacedLines
|
||||
);
|
||||
const defaultFontSize = useSettingStore(
|
||||
(state) => state.settings.defaultFontSize
|
||||
);
|
||||
const defaultFontFamily = useSettingStore(
|
||||
(state) => state.settings.defaultFontFamily
|
||||
);
|
||||
|
||||
const tools = useDragState((state) => state.data);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -164,7 +171,9 @@ export const useEditorEvents = (
|
||||
noHeader: noHeader,
|
||||
noToolbar: readonly || editorPropReadonly || noToolbar,
|
||||
doubleSpacedLines: doubleSpacedLines,
|
||||
corsProxy: corsProxy
|
||||
corsProxy: corsProxy,
|
||||
fontSize: defaultFontSize,
|
||||
fontFamily: defaultFontFamily
|
||||
});
|
||||
}, [
|
||||
fullscreen,
|
||||
@@ -179,7 +188,9 @@ export const useEditorEvents = (
|
||||
editorPropReadonly,
|
||||
noHeader,
|
||||
noToolbar,
|
||||
corsProxy
|
||||
corsProxy,
|
||||
defaultFontSize,
|
||||
defaultFontFamily
|
||||
]);
|
||||
|
||||
const onBackPress = useCallback(async () => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { Licenses } from "./licenses";
|
||||
import SoundPicker from "./sound-picker";
|
||||
import { Subscription } from "./subscription";
|
||||
import { TrashIntervalSelector } from "./trash-interval-selector";
|
||||
import { FontSelector } from "./font-selector";
|
||||
export const components: { [name: string]: ReactElement } = {
|
||||
colorpicker: <AccentColorPicker />,
|
||||
homeselector: <HomagePageSelector />,
|
||||
@@ -35,5 +36,6 @@ export const components: { [name: string]: ReactElement } = {
|
||||
"debug-logs": <DebugLogs />,
|
||||
"sound-picker": <SoundPicker />,
|
||||
licenses: <Licenses />,
|
||||
"trash-interval-selector": <TrashIntervalSelector />
|
||||
"trash-interval-selector": <TrashIntervalSelector />,
|
||||
"font-selector": <FontSelector />
|
||||
};
|
||||
|
||||
110
apps/mobile/app/screens/settings/font-selector.jsx
Normal file
110
apps/mobile/app/screens/settings/font-selector.jsx
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { PressableButton } from "../../components/ui/pressable";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font";
|
||||
|
||||
export const FontSelector = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const defaultFontFamily = useSettingStore(
|
||||
(state) => state.settings.defaultFontFamily
|
||||
);
|
||||
const menuRef = useRef();
|
||||
const [width, setWidth] = useState(0);
|
||||
|
||||
const onChange = (item) => {
|
||||
menuRef.current?.hide();
|
||||
SettingsService.set({
|
||||
defaultFontFamily: item
|
||||
});
|
||||
};
|
||||
return (
|
||||
<View
|
||||
onLayout={(event) => {
|
||||
setWidth(event.nativeEvent.layout.width);
|
||||
}}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
animationDuration={200}
|
||||
style={{
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.bg,
|
||||
width: width,
|
||||
marginTop: 60
|
||||
}}
|
||||
onRequestClose={() => {
|
||||
menuRef.current?.hide();
|
||||
}}
|
||||
anchor={
|
||||
<PressableButton
|
||||
onPress={async () => {
|
||||
menuRef.current?.show();
|
||||
}}
|
||||
type="grayBg"
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
>
|
||||
<Paragraph>{getFontById(defaultFontFamily).title}</Paragraph>
|
||||
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
|
||||
</PressableButton>
|
||||
}
|
||||
>
|
||||
{getFonts().map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
onPress={async () => {
|
||||
onChange(item.id);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor:
|
||||
defaultFontFamily === item.id ? colors.nav : "transparent",
|
||||
width: "100%",
|
||||
maxWidth: width
|
||||
}}
|
||||
textStyle={{
|
||||
fontSize: SIZE.md,
|
||||
color: defaultFontFamily === item.id ? colors.accent : colors.pri
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -37,6 +37,7 @@ import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { SIZE } from "../../utils/size";
|
||||
import { components } from "./components";
|
||||
import { RouteParams, SettingSection } from "./types";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
|
||||
const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -64,6 +65,12 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
backgroundColor: colors.errorBg
|
||||
}
|
||||
: {};
|
||||
|
||||
const updateInput = (value: any) => {
|
||||
inputRef?.current?.setNativeProps({
|
||||
text: value + ""
|
||||
});
|
||||
};
|
||||
return isHidden ? null : (
|
||||
<PressableButton
|
||||
disabled={item.type === "component"}
|
||||
@@ -190,6 +197,93 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
defaultValue={item.inputProperties?.defaultValue}
|
||||
/>
|
||||
)}
|
||||
|
||||
{item.type === "input-selector" && (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 12
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
name="minus"
|
||||
onPress={() => {
|
||||
const rawValue = SettingsService.get()[
|
||||
item.property as keyof SettingStore["settings"]
|
||||
] as string;
|
||||
if (rawValue) {
|
||||
const currentValue = parseInt(rawValue);
|
||||
if (currentValue <= 0) return;
|
||||
const nextValue = currentValue - 1;
|
||||
SettingsService.set({
|
||||
[item.property as string]: nextValue
|
||||
});
|
||||
updateInput(nextValue);
|
||||
}
|
||||
}}
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
<Input
|
||||
{...item.inputProperties}
|
||||
onSubmit={(e) => {
|
||||
if (e.nativeEvent.text) {
|
||||
SettingsService.set({
|
||||
[item.property as string]: e.nativeEvent.text
|
||||
});
|
||||
}
|
||||
item.inputProperties?.onSubmitEditing?.(e);
|
||||
}}
|
||||
onChangeText={(text) => {
|
||||
if (text) {
|
||||
if (item.minInputValue) {
|
||||
text =
|
||||
parseInt(text) < item.minInputValue
|
||||
? item.minInputValue + ""
|
||||
: text;
|
||||
}
|
||||
SettingsService.set({
|
||||
[item.property as string]: text
|
||||
});
|
||||
}
|
||||
item.inputProperties?.onSubmitEditing?.(text as any);
|
||||
}}
|
||||
keyboardType="decimal-pad"
|
||||
containerStyle={{
|
||||
width: 45
|
||||
}}
|
||||
wrapperStyle={{
|
||||
maxWidth: 45,
|
||||
marginBottom: 0,
|
||||
marginHorizontal: 6
|
||||
}}
|
||||
fwdRef={inputRef}
|
||||
onLayout={() => {
|
||||
if (item.property) {
|
||||
updateInput(SettingsService.get()[item.property]);
|
||||
}
|
||||
}}
|
||||
defaultValue={item.inputProperties?.defaultValue}
|
||||
/>
|
||||
<IconButton
|
||||
name="plus"
|
||||
onPress={() => {
|
||||
const rawValue = SettingsService.get()[
|
||||
item.property as keyof SettingStore["settings"]
|
||||
] as string;
|
||||
if (rawValue) {
|
||||
const currentValue = parseInt(rawValue);
|
||||
const nextValue = currentValue + 1;
|
||||
SettingsService.set({
|
||||
[item.property as string]: nextValue
|
||||
});
|
||||
updateInput(nextValue);
|
||||
}
|
||||
}}
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import notifee from "@notifee/react-native";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import { Linking, Platform } from "react-native";
|
||||
@@ -450,13 +451,13 @@ export const settingsGroups: SettingSection[] = [
|
||||
},
|
||||
{
|
||||
id: "customize",
|
||||
name: "Customize",
|
||||
name: "Customization",
|
||||
sections: [
|
||||
{
|
||||
id: "personalization",
|
||||
type: "screen",
|
||||
name: "Theme",
|
||||
description: "Change app look and feel",
|
||||
description: "Change app look and feel with color themes",
|
||||
icon: "shape",
|
||||
sections: [
|
||||
{
|
||||
@@ -514,7 +515,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
id: "behaviour",
|
||||
type: "screen",
|
||||
name: "Behaviour",
|
||||
description: "Change app homepage",
|
||||
description: "Change how the app behaves in different situations",
|
||||
sections: [
|
||||
{
|
||||
id: "default-home",
|
||||
@@ -532,6 +533,63 @@ export const settingsGroups: SettingSection[] = [
|
||||
component: "trash-interval-selector"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "editor",
|
||||
name: "Editor",
|
||||
type: "screen",
|
||||
icon: "note-edit-outline",
|
||||
description: "Customize the editor to fit your needs",
|
||||
sections: [
|
||||
{
|
||||
id: "configure-toolbar",
|
||||
type: "screen",
|
||||
name: "Configure toolbar",
|
||||
description: "Make the toolbar adaptable to your needs.",
|
||||
component: "configuretoolbar"
|
||||
},
|
||||
{
|
||||
id: "reset-toolbar",
|
||||
name: "Reset toolbar",
|
||||
description: "Reset toolbar configuration to default",
|
||||
modifer: () => {
|
||||
useDragState.getState().setPreset("default");
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "double-spaced-lines",
|
||||
name: "Use double spaced lines",
|
||||
description:
|
||||
"New lines will be double spaced (old ones won't be affected).",
|
||||
type: "switch",
|
||||
property: "doubleSpacedLines",
|
||||
icon: "format-line-spacing",
|
||||
onChange: () => {
|
||||
ToastEvent.show({
|
||||
heading: "Line spacing changed",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "default-font-size",
|
||||
name: "Default font size",
|
||||
description: "Set the default font size in editor",
|
||||
type: "input-selector",
|
||||
minInputValue: 8,
|
||||
icon: "format-size",
|
||||
property: "defaultFontSize"
|
||||
},
|
||||
{
|
||||
id: "default-font-family",
|
||||
name: "Default font family",
|
||||
description: "Set the default font family in editor",
|
||||
type: "component",
|
||||
icon: "format-font",
|
||||
property: "defaultFontFamily",
|
||||
component: "font-selector"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -928,42 +986,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "editor",
|
||||
name: "Editor",
|
||||
sections: [
|
||||
{
|
||||
id: "configure-toolbar",
|
||||
type: "screen",
|
||||
name: "Configure toolbar",
|
||||
description: "Make the toolbar adaptable to your needs.",
|
||||
component: "configuretoolbar"
|
||||
},
|
||||
{
|
||||
id: "reset-toolbar",
|
||||
name: "Reset toolbar",
|
||||
description: "Reset toolbar configuration to default",
|
||||
modifer: () => {
|
||||
useDragState.getState().setPreset("default");
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "double-spaced-lines",
|
||||
name: "Use double spaced lines",
|
||||
description:
|
||||
"New lines will be double spaced (old ones won't be affected).",
|
||||
type: "switch",
|
||||
property: "doubleSpacedLines",
|
||||
icon: "format-line-spacing",
|
||||
onChange: () => {
|
||||
ToastEvent.show({
|
||||
heading: "Line spacing changed",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "help-support",
|
||||
name: "Help and support",
|
||||
|
||||
@@ -22,7 +22,14 @@ import { Settings } from "../../stores/use-setting-store";
|
||||
|
||||
export type SettingSection = {
|
||||
id: string;
|
||||
type?: "screen" | "switch" | "component" | "danger" | "input";
|
||||
type?:
|
||||
| "screen"
|
||||
| "switch"
|
||||
| "component"
|
||||
| "danger"
|
||||
| "input"
|
||||
| "input-selector"
|
||||
| "dropdown-selector";
|
||||
name?: string | ((current?: unknown) => string);
|
||||
description?: string | ((current: unknown) => string);
|
||||
icon?: string;
|
||||
@@ -35,6 +42,8 @@ export type SettingSection = {
|
||||
hidden?: (current: unknown) => boolean;
|
||||
onChange?: (property: boolean) => void;
|
||||
inputProperties?: TextInput["props"];
|
||||
options?: any[];
|
||||
minInputValue?: number;
|
||||
};
|
||||
|
||||
export type SettingsGroup = {
|
||||
|
||||
@@ -69,6 +69,7 @@ export type RouteParams = {
|
||||
AppLock: AppLockRouteParams;
|
||||
Auth: AuthParams;
|
||||
Reminders: GenericRouteParam;
|
||||
SettingsGroup: GenericRouteParam;
|
||||
};
|
||||
|
||||
export type RouteName = keyof RouteParams;
|
||||
|
||||
@@ -73,6 +73,8 @@ export type Settings = {
|
||||
corsProxy: string;
|
||||
disableRealtimeSync?: boolean;
|
||||
notificationSound?: Sound & { platform: PlatformOSType };
|
||||
defaultFontSize: string;
|
||||
defaultFontFamily: string;
|
||||
};
|
||||
|
||||
type DimensionsType = {
|
||||
@@ -149,7 +151,9 @@ export const useSettingStore = create<SettingStore>((set) => ({
|
||||
defaultSnoozeTime: "5",
|
||||
corsProxy: "https://cors.notesnook.com",
|
||||
reminderNotificationMode: "urgent",
|
||||
notificationSound: undefined
|
||||
notificationSound: undefined,
|
||||
defaultFontFamily: "sans-serif",
|
||||
defaultFontSize: "16"
|
||||
},
|
||||
sheetKeyboardHandler: true,
|
||||
fullscreen: false,
|
||||
|
||||
@@ -27,7 +27,6 @@ import { db } from "../common/database";
|
||||
import { tabBarRef } from "./global-refs";
|
||||
|
||||
let prevTarget = null;
|
||||
let htmlToText;
|
||||
|
||||
export const TOOLTIP_POSITIONS = {
|
||||
LEFT: 1,
|
||||
@@ -171,3 +170,8 @@ export function showTooltip(event, text, position = 2) {
|
||||
clickToHide: true
|
||||
});
|
||||
}
|
||||
|
||||
export function toTitleCase(value) {
|
||||
if (!value) return;
|
||||
return value.slice(0, 1).toUpperCase() + value.slice(1);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,19 @@ import { Button, Flex } from "@theme-ui/components";
|
||||
import { useMenuTrigger } from "../../hooks/use-menu";
|
||||
import { ChevronDown } from "../icons";
|
||||
|
||||
export default function DropdownButton({ title, options }) {
|
||||
export default function DropdownButton(props) {
|
||||
const { openMenu } = useMenuTrigger();
|
||||
const { options, title, sx, buttonStyle, chevronStyle } = props;
|
||||
|
||||
if (!options || !options.length) return null;
|
||||
return (
|
||||
<Flex>
|
||||
<Flex sx={sx}>
|
||||
<Button
|
||||
sx={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
|
||||
sx={{
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
...buttonStyle
|
||||
}}
|
||||
onClick={options[0].onClick}
|
||||
>
|
||||
{options[0].title()}
|
||||
@@ -38,7 +43,8 @@ export default function DropdownButton({ title, options }) {
|
||||
px={1}
|
||||
sx={{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 0
|
||||
borderTopLeftRadius: 0,
|
||||
...chevronStyle
|
||||
}}
|
||||
onClick={() => openMenu(options.slice(1), { title })}
|
||||
>
|
||||
|
||||
@@ -24,17 +24,26 @@ import BaseStore from "../../stores";
|
||||
import { UseBoundStore } from "zustand";
|
||||
import shallow from "zustand/shallow";
|
||||
import type { ToolbarDefinition } from "@notesnook/editor";
|
||||
import Config from "../../utils/config";
|
||||
|
||||
type EditorConfig = { fontFamily: string; fontSize: number };
|
||||
type EditorSubState = {
|
||||
editor?: IEditor;
|
||||
canUndo?: boolean;
|
||||
canRedo?: boolean;
|
||||
searching?: boolean;
|
||||
toolbarConfig?: ToolbarDefinition;
|
||||
editorConfig: EditorConfig;
|
||||
statistics?: NoteStatistics;
|
||||
};
|
||||
|
||||
class EditorContext extends BaseStore {
|
||||
subState: EditorSubState = {};
|
||||
subState: EditorSubState = {
|
||||
editorConfig: Config.get("editorConfig", {
|
||||
fontFamily: "sans-serif",
|
||||
fontSize: 16
|
||||
})
|
||||
};
|
||||
|
||||
configure = (
|
||||
partial:
|
||||
@@ -116,3 +125,27 @@ export function useNoteStatistics(): NoteStatistics {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function useEditorConfig() {
|
||||
const editorConfig = useEditorContext((store) => store.subState.editorConfig);
|
||||
const configure = useEditorContext((store) => store.configure);
|
||||
const setEditorConfig = useCallback(
|
||||
(config: Partial<EditorConfig>) => {
|
||||
if (editorConfig)
|
||||
Config.set("editorConfig", {
|
||||
...editorConfig,
|
||||
...config
|
||||
});
|
||||
|
||||
configure({
|
||||
editorConfig: {
|
||||
...editorConfig,
|
||||
...config
|
||||
}
|
||||
});
|
||||
},
|
||||
[editorConfig, configure]
|
||||
);
|
||||
|
||||
return { editorConfig, setEditorConfig };
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@ import {
|
||||
usePermissionHandler,
|
||||
getHTMLFromFragment,
|
||||
Fragment,
|
||||
type DownloadOptions,
|
||||
getTotalWords,
|
||||
countWords,
|
||||
type DownloadOptions
|
||||
getFontById
|
||||
} from "@notesnook/editor";
|
||||
import { Box, Flex } from "@theme-ui/components";
|
||||
import { PropsWithChildren, useEffect, useRef, useState } from "react";
|
||||
@@ -45,7 +46,8 @@ import {
|
||||
useConfigureEditor,
|
||||
useSearch,
|
||||
useToolbarConfig,
|
||||
configureEditor
|
||||
configureEditor,
|
||||
useEditorConfig
|
||||
} from "./context";
|
||||
import { createPortal } from "react-dom";
|
||||
import { getCurrentPreset } from "../../common/toolbar-config";
|
||||
@@ -71,6 +73,8 @@ type TipTapProps = {
|
||||
theme: Theme;
|
||||
isMobile?: boolean;
|
||||
downloadOptions?: DownloadOptions;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
};
|
||||
|
||||
const SAVE_INTERVAL = process.env.REACT_APP_TEST ? 100 : 300;
|
||||
@@ -115,7 +119,9 @@ function TipTap(props: TipTapProps) {
|
||||
nonce,
|
||||
theme,
|
||||
isMobile,
|
||||
downloadOptions
|
||||
downloadOptions,
|
||||
fontSize,
|
||||
fontFamily
|
||||
} = props;
|
||||
|
||||
const isUserPremium = useIsUserPremium();
|
||||
@@ -301,7 +307,6 @@ function TipTap(props: TipTapProps) {
|
||||
}, [editor, editorContainer]);
|
||||
|
||||
if (!toolbarContainerId) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Portal containerId={toolbarContainerId}>
|
||||
@@ -310,16 +315,24 @@ function TipTap(props: TipTapProps) {
|
||||
theme={theme}
|
||||
location={isMobile ? "bottom" : "top"}
|
||||
tools={toolbarConfig}
|
||||
defaultFontFamily={fontFamily}
|
||||
defaultFontSize={fontSize}
|
||||
/>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TiptapWrapper(props: Omit<TipTapProps, "editorContainer" | "theme">) {
|
||||
function TiptapWrapper(
|
||||
props: Omit<
|
||||
TipTapProps,
|
||||
"editorContainer" | "theme" | "fontSize" | "fontFamily"
|
||||
>
|
||||
) {
|
||||
const theme = useTheme() as Theme;
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const editorContainerRef = useRef<HTMLDivElement>(null);
|
||||
const { editorConfig } = useEditorConfig();
|
||||
useEffect(() => {
|
||||
setIsReady(true);
|
||||
}, []);
|
||||
@@ -332,6 +345,8 @@ function TiptapWrapper(props: Omit<TipTapProps, "editorContainer" | "theme">) {
|
||||
{...props}
|
||||
editorContainer={editorContainerRef.current}
|
||||
theme={theme}
|
||||
fontFamily={editorConfig.fontFamily}
|
||||
fontSize={editorConfig.fontSize}
|
||||
/>
|
||||
) : null}
|
||||
<Box
|
||||
@@ -341,7 +356,9 @@ function TiptapWrapper(props: Omit<TipTapProps, "editorContainer" | "theme">) {
|
||||
flex: 1,
|
||||
cursor: "text",
|
||||
color: theme.colors.text, // TODO!
|
||||
paddingBottom: 150
|
||||
paddingBottom: 150,
|
||||
fontSize: editorConfig.fontSize,
|
||||
fontFamily: getFontById(editorConfig.fontFamily)?.font
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -23,6 +23,8 @@ import { useStore, store } from "../../stores/editor-store";
|
||||
import { debounceWithId } from "../../utils/debounce";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
import useTablet from "../../hooks/use-tablet";
|
||||
import { useEditorConfig } from "./context";
|
||||
import { getFontById } from "@notesnook/editor";
|
||||
|
||||
type TitleBoxProps = {
|
||||
readonly: boolean;
|
||||
@@ -35,6 +37,7 @@ function TitleBox(props: TitleBoxProps) {
|
||||
const id = useStore((store) => store.session.id);
|
||||
const isMobile = useMobile();
|
||||
const isTablet = useTablet();
|
||||
const { editorConfig } = useEditorConfig();
|
||||
|
||||
const MAX_FONT_SIZE = useMemo(() => {
|
||||
return isMobile || isTablet ? 1.625 : 2.625;
|
||||
@@ -66,7 +69,7 @@ function TitleBox(props: TitleBoxProps) {
|
||||
readOnly={readonly}
|
||||
sx={{
|
||||
p: 0,
|
||||
fontFamily: "heading",
|
||||
fontFamily: getFontById(editorConfig.fontFamily)?.font || "heading",
|
||||
fontSize: ["1.625em", "1.625em", "2.625em"],
|
||||
fontWeight: "heading",
|
||||
width: "100%"
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { Button, Flex, Input, Text } from "@theme-ui/components";
|
||||
import * as Icon from "../components/icons";
|
||||
import { useStore as useUserStore } from "../stores/user-store";
|
||||
import { useStore as useNoteStore } from "../stores/note-store";
|
||||
@@ -81,6 +81,8 @@ import { useTelemetry } from "../hooks/use-telemetry";
|
||||
import useSpellChecker from "../hooks/use-spell-checker";
|
||||
import useDesktopIntegration from "../hooks/use-desktop-integration";
|
||||
import { writeText } from "clipboard-polyfill";
|
||||
import { useEditorConfig } from "../components/editor/context";
|
||||
import { getFonts } from "@notesnook/editor";
|
||||
|
||||
function subscriptionStatusToString(user) {
|
||||
const status = user?.subscription?.type;
|
||||
@@ -219,6 +221,7 @@ function Settings() {
|
||||
"corsProxy",
|
||||
"https://cors.notesnook.com"
|
||||
);
|
||||
const { editorConfig, setEditorConfig } = useEditorConfig();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -622,6 +625,51 @@ function Settings() {
|
||||
/>
|
||||
{groups.editor && (
|
||||
<>
|
||||
<OptionsItem
|
||||
title={"Default font family"}
|
||||
tip={"Set default font family"}
|
||||
selectedOption={editorConfig.fontFamily}
|
||||
options={getFonts().map((font) => ({
|
||||
value: font.id,
|
||||
title: font.title
|
||||
}))}
|
||||
onSelectionChanged={(option) => {
|
||||
setEditorConfig({ fontFamily: option.value });
|
||||
}}
|
||||
/>
|
||||
<Flex
|
||||
py={2}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
borderBottom: "1px solid",
|
||||
borderBottomColor: "border",
|
||||
":hover": { borderBottomColor: "primary" },
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Tip
|
||||
text="Default font size"
|
||||
tip="Set default font size"
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
<Input
|
||||
type="number"
|
||||
min={8}
|
||||
sx={{ width: 70 }}
|
||||
defaultValue={editorConfig.fontSize}
|
||||
onChange={(e) => {
|
||||
setEditorConfig({ fontSize: e.currentTarget.valueAsNumber });
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{/* <Counter
|
||||
value={editorConfig.fontSize}
|
||||
onDecrease={() => {}}
|
||||
onIncrease={() => {}}
|
||||
title="Set default font size"
|
||||
onReset={() => setEditorConfig({ fontFamily: "16px" })}
|
||||
/> */}
|
||||
{/* <DefaultFont /> */}
|
||||
<Toggle
|
||||
title="Use double spaced lines"
|
||||
onTip="New lines will be double spaced (old ones won't be affected)."
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import {
|
||||
Editor,
|
||||
getFontById,
|
||||
PortalProvider,
|
||||
Toolbar,
|
||||
usePermissionHandler,
|
||||
@@ -227,6 +228,7 @@ const Tiptap = ({
|
||||
readonly={settings.readonly}
|
||||
controller={controllerRef}
|
||||
title={controller.title}
|
||||
fontFamily={settings.fontFamily}
|
||||
/>
|
||||
<StatusBar container={containerRef} />
|
||||
</>
|
||||
@@ -234,6 +236,8 @@ const Tiptap = ({
|
||||
|
||||
<ContentDiv
|
||||
padding={settings.doubleSpacedLines ? 0 : 6}
|
||||
fontSize={settings.fontSize}
|
||||
fontFamily={settings.fontFamily}
|
||||
ref={contentRef}
|
||||
/>
|
||||
|
||||
@@ -254,6 +258,8 @@ const Tiptap = ({
|
||||
editor={_editor}
|
||||
location="bottom"
|
||||
tools={[...settings.tools]}
|
||||
defaultFontFamily={settings.fontFamily}
|
||||
defaultFontSize={settings.fontSize}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -262,7 +268,10 @@ const Tiptap = ({
|
||||
};
|
||||
|
||||
const ContentDiv = memo(
|
||||
forwardRef<HTMLDivElement, { padding: number }>((props, ref) => {
|
||||
forwardRef<
|
||||
HTMLDivElement,
|
||||
{ padding: number; fontSize: number; fontFamily: string }
|
||||
>((props, ref) => {
|
||||
const theme = useEditorThemeStore((state) => state.colors);
|
||||
return (
|
||||
<div
|
||||
@@ -272,12 +281,18 @@ const ContentDiv = memo(
|
||||
paddingTop: props.padding,
|
||||
color: theme.pri,
|
||||
marginTop: -12,
|
||||
caretColor: theme.accent
|
||||
caretColor: theme.accent,
|
||||
fontSize: props.fontSize,
|
||||
fontFamily: getFontById(props.fontFamily)?.font
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
() => true
|
||||
(prev, next) => {
|
||||
if (prev.fontSize !== next.fontSize || prev.fontFamily !== next.fontFamily)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
const modifyToolbarTheme = (toolbarTheme: Theme) => {
|
||||
|
||||
@@ -17,20 +17,22 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { RefObject, useEffect, useRef } from "react";
|
||||
import { getFontById } from "@notesnook/editor";
|
||||
import React, { RefObject, useEffect, useRef } from "react";
|
||||
import { EditorController } from "../hooks/useEditorController";
|
||||
import styles from "./styles.module.css";
|
||||
function Title({
|
||||
controller,
|
||||
title,
|
||||
titlePlaceholder,
|
||||
readonly
|
||||
readonly,
|
||||
fontFamily
|
||||
}: {
|
||||
controller: RefObject<EditorController>;
|
||||
title: string;
|
||||
titlePlaceholder: string;
|
||||
readonly: boolean;
|
||||
fontFamily: string;
|
||||
}) {
|
||||
const titleRef = useRef<HTMLInputElement>(null);
|
||||
const emitUpdate = useRef(true);
|
||||
@@ -59,7 +61,7 @@ function Title({
|
||||
paddingRight: 12,
|
||||
paddingLeft: 12,
|
||||
fontWeight: 600,
|
||||
fontFamily: "Open Sans",
|
||||
fontFamily: getFontById(fontFamily)?.font || "Open Sans",
|
||||
backgroundColor: "transparent",
|
||||
color: "var(--nn_heading)",
|
||||
caretColor: "var(--nn_accent)",
|
||||
@@ -76,8 +78,13 @@ function Title({
|
||||
}
|
||||
|
||||
export default React.memo(Title, (prev, next) => {
|
||||
if (prev.title !== next.title) return false;
|
||||
if (prev.titlePlaceholder !== next.titlePlaceholder) return false;
|
||||
if (prev.readonly !== next.readonly) return false;
|
||||
if (
|
||||
prev.title !== next.title ||
|
||||
prev.titlePlaceholder !== next.titlePlaceholder ||
|
||||
prev.readonly !== next.readonly ||
|
||||
prev.fontFamily !== next.fontFamily
|
||||
)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -30,7 +30,9 @@ const initialState = {
|
||||
noToolbar: globalThis.noToolbar,
|
||||
noHeader: globalThis.noHeader,
|
||||
readonly: globalThis.readonly,
|
||||
doubleSpacedLines: true
|
||||
doubleSpacedLines: true,
|
||||
fontFamily: "sans-serif",
|
||||
fontSize: "16px"
|
||||
};
|
||||
|
||||
global.settingsController = {
|
||||
|
||||
@@ -39,6 +39,8 @@ export type Settings = {
|
||||
noHeader?: boolean;
|
||||
doubleSpacedLines?: boolean;
|
||||
corsProxy: string;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
};
|
||||
|
||||
/* eslint-disable no-var */
|
||||
|
||||
@@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import "@tiptap/extension-text-style";
|
||||
import { getFontConfig } from "@notesnook/theme/dist/theme/font";
|
||||
import { Extension } from "@tiptap/core";
|
||||
import { getFontById } from "../../utils/font";
|
||||
|
||||
export type FontFamilyOptions = {
|
||||
types: string[];
|
||||
@@ -39,12 +39,6 @@ declare module "@tiptap/core" {
|
||||
}
|
||||
}
|
||||
|
||||
const FONTS: Record<string, string> = {
|
||||
monospace: getFontConfig().fonts.monospace,
|
||||
"sans-serif": getFontConfig().fonts.body,
|
||||
serif: `Noto Serif, Times New Roman, serif`
|
||||
};
|
||||
|
||||
export const FontFamily = Extension.create<FontFamilyOptions>({
|
||||
name: "fontFamily",
|
||||
|
||||
@@ -70,7 +64,8 @@ export const FontFamily = Extension.create<FontFamilyOptions>({
|
||||
|
||||
const realFontFamily =
|
||||
attributes["data-font-family"] || attributes.fontFamily;
|
||||
const font = FONTS[realFontFamily] || attributes.fontFamily;
|
||||
const font =
|
||||
getFontById(realFontFamily)?.font || attributes.fontFamily;
|
||||
return {
|
||||
"data-font-family": realFontFamily,
|
||||
style: `font-family: ${font}`
|
||||
|
||||
@@ -22,7 +22,6 @@ import "@tiptap/extension-text-style";
|
||||
|
||||
type FontSizeOptions = {
|
||||
types: string[];
|
||||
defaultFontSize: number;
|
||||
};
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
@@ -44,8 +43,7 @@ export const FontSize = Extension.create<FontSizeOptions>({
|
||||
name: "fontSize",
|
||||
|
||||
defaultOptions: {
|
||||
types: ["textStyle"],
|
||||
defaultFontSize: 16
|
||||
types: ["textStyle"]
|
||||
},
|
||||
|
||||
addGlobalAttributes() {
|
||||
@@ -54,7 +52,6 @@ export const FontSize = Extension.create<FontSizeOptions>({
|
||||
types: this.options.types,
|
||||
attributes: {
|
||||
fontSize: {
|
||||
default: `${this.options.defaultFontSize}px`,
|
||||
parseHTML: (element) => element.style.fontSize,
|
||||
renderHTML: (attributes) => {
|
||||
if (!attributes.fontSize) {
|
||||
|
||||
@@ -273,6 +273,7 @@ export * from "./extensions/react";
|
||||
export * from "./toolbar";
|
||||
export * from "./types";
|
||||
export * from "./utils/word-counter";
|
||||
export * from "./utils/font";
|
||||
export {
|
||||
useTiptap,
|
||||
Toolbar,
|
||||
|
||||
@@ -39,6 +39,10 @@ interface ToolbarState {
|
||||
closePopup: (popupId: string) => void;
|
||||
closePopupGroup: (groupId: string, excluded: string[]) => void;
|
||||
closeAllPopups: () => void;
|
||||
fontFamily: string;
|
||||
setFontFamily: (fontFamily: string) => void;
|
||||
fontSize: number;
|
||||
setFontSize: (fontSize: number) => void;
|
||||
}
|
||||
|
||||
export const useToolbarStore = create<ToolbarState>((set, get) => ({
|
||||
@@ -92,6 +96,16 @@ export const useToolbarStore = create<ToolbarState>((set, get) => ({
|
||||
for (const key in state.openedPopups) {
|
||||
state.openedPopups[key] = undefined;
|
||||
}
|
||||
}),
|
||||
fontFamily: "sans-serif",
|
||||
setFontFamily: (fontFamily) =>
|
||||
set((state) => {
|
||||
state.fontFamily = fontFamily;
|
||||
}),
|
||||
fontSize: 16,
|
||||
setFontSize: (fontSize) =>
|
||||
set((state) => {
|
||||
state.fontSize = fontSize;
|
||||
})
|
||||
}));
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ type ToolbarProps = FlexProps & {
|
||||
editor: Editor | null;
|
||||
location: ToolbarLocation;
|
||||
tools?: ToolbarDefinition;
|
||||
defaultFontFamily: string;
|
||||
defaultFontSize: number;
|
||||
};
|
||||
|
||||
export function Toolbar(props: ToolbarProps) {
|
||||
@@ -49,10 +51,11 @@ export function Toolbar(props: ToolbarProps) {
|
||||
theme,
|
||||
location,
|
||||
tools = getDefaultPresets().default,
|
||||
defaultFontFamily,
|
||||
defaultFontSize,
|
||||
sx,
|
||||
...flexProps
|
||||
} = props;
|
||||
|
||||
const toolbarTools = useMemo(
|
||||
() => [...STATIC_TOOLBAR_GROUPS, ...tools],
|
||||
[tools]
|
||||
@@ -63,11 +66,23 @@ export function Toolbar(props: ToolbarProps) {
|
||||
const setToolbarLocation = useToolbarStore(
|
||||
(store) => store.setToolbarLocation
|
||||
);
|
||||
const setDefaultFontFamily = useToolbarStore((store) => store.setFontFamily);
|
||||
const setDefaultFontSize = useToolbarStore((store) => store.setFontSize);
|
||||
|
||||
useEffect(() => {
|
||||
setToolbarLocation(location);
|
||||
}, [location, setToolbarLocation]);
|
||||
|
||||
useEffect(() => {
|
||||
setDefaultFontFamily(defaultFontFamily);
|
||||
setDefaultFontSize(defaultFontSize);
|
||||
}, [
|
||||
defaultFontFamily,
|
||||
defaultFontSize,
|
||||
setDefaultFontFamily,
|
||||
setDefaultFontSize
|
||||
]);
|
||||
|
||||
if (!editor) return null;
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
|
||||
@@ -24,12 +24,16 @@ import { MenuItem } from "../../components/menu/types";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Counter } from "../components/counter";
|
||||
import { useRefValue } from "../../hooks/use-ref-value";
|
||||
import { useToolbarStore } from "../stores/toolbar-store";
|
||||
import { getFontById, getFontIds, getFonts } from "../../utils/font";
|
||||
|
||||
export function FontSize(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
const { fontSize: _fontSize } = editor.getAttributes("textStyle");
|
||||
const fontSize = _fontSize || "16px";
|
||||
const fontSizeAsNumber = useRefValue(parseInt(fontSize.replace("px", "")));
|
||||
const defaultFontSize = useToolbarStore((store) => store.fontSize);
|
||||
const { fontSize } = editor.getAttributes("textStyle");
|
||||
const fontSizeAsNumber = useRefValue(
|
||||
fontSize ? parseInt(fontSize.replace("px", "")) : defaultFontSize
|
||||
);
|
||||
|
||||
const decreaseFontSize = useCallback(() => {
|
||||
return Math.max(8, fontSizeAsNumber.current - 1);
|
||||
@@ -56,27 +60,26 @@ export function FontSize(props: ToolProps) {
|
||||
.setFontSize(`${increaseFontSize()}px`)
|
||||
.run();
|
||||
}}
|
||||
onReset={() => editor.current?.chain().focus().setFontSize("16px").run()}
|
||||
onReset={() =>
|
||||
editor.current
|
||||
?.chain()
|
||||
.focus()
|
||||
.setFontSize(`${defaultFontSize}px`)
|
||||
.run()
|
||||
}
|
||||
value={fontSize}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const fontFamilies = {
|
||||
"Sans-serif": "Open Sans",
|
||||
Serif: "serif",
|
||||
Monospace: "monospace"
|
||||
};
|
||||
export function FontFamily(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
|
||||
const defaultFontFamily = useToolbarStore((store) => store.fontFamily);
|
||||
const currentFontFamily =
|
||||
Object.entries(fontFamilies)
|
||||
.find(([_key, value]) =>
|
||||
editor.isActive("textStyle", { fontFamily: value })
|
||||
)
|
||||
?.map((a) => a)
|
||||
?.at(0) || "Sans-serif";
|
||||
getFontIds().find((id) =>
|
||||
editor.isActive("textStyle", { fontFamily: id })
|
||||
) || defaultFontFamily;
|
||||
|
||||
const items = useMemo(
|
||||
() => toMenuItems(editor, currentFontFamily),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -87,7 +90,7 @@ export function FontFamily(props: ToolProps) {
|
||||
<Dropdown
|
||||
id="fontFamily"
|
||||
group="font"
|
||||
selectedItem={currentFontFamily}
|
||||
selectedItem={getFontById(currentFontFamily)?.title || defaultFontFamily}
|
||||
items={items}
|
||||
menuWidth={130}
|
||||
/>
|
||||
@@ -96,16 +99,16 @@ export function FontFamily(props: ToolProps) {
|
||||
|
||||
function toMenuItems(editor: Editor, currentFontFamily: string): MenuItem[] {
|
||||
const menuItems: MenuItem[] = [];
|
||||
for (const key in fontFamilies) {
|
||||
const value = fontFamilies[key as keyof typeof fontFamilies];
|
||||
for (const font of getFonts()) {
|
||||
menuItems.push({
|
||||
key,
|
||||
key: font.id,
|
||||
type: "button",
|
||||
title: key,
|
||||
isChecked: key === currentFontFamily,
|
||||
onClick: () => editor.current?.chain().focus().setFontFamily(value).run(),
|
||||
title: font.title,
|
||||
isChecked: font.id === currentFontFamily,
|
||||
onClick: () =>
|
||||
editor.current?.chain().focus().setFontFamily(font.id).run(),
|
||||
styles: {
|
||||
fontFamily: value
|
||||
fontFamily: font.font
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
50
packages/editor/src/utils/font.ts
Normal file
50
packages/editor/src/utils/font.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getFontConfig } from "@notesnook/theme/dist/theme/font";
|
||||
|
||||
const FONTS = [
|
||||
{
|
||||
title: "Monospace",
|
||||
id: "monospace",
|
||||
font: getFontConfig().fonts.monospace
|
||||
},
|
||||
{
|
||||
title: "Sans-serif",
|
||||
id: "sans-serif",
|
||||
font: getFontConfig().fonts.body
|
||||
},
|
||||
{
|
||||
title: "Serif",
|
||||
id: "serif",
|
||||
font: `Noto Serif, Times New Roman, serif`
|
||||
}
|
||||
];
|
||||
|
||||
export function getFonts() {
|
||||
return FONTS;
|
||||
}
|
||||
|
||||
export function getFontById(id: string) {
|
||||
return FONTS.find((a) => a.id === id);
|
||||
}
|
||||
|
||||
export function getFontIds() {
|
||||
return FONTS.map((a) => a.id);
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
|
||||
|
||||
.ProseMirror span * {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.ProseMirror {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.ProseMirror p.is-editor-empty:first-child::before {
|
||||
color: var(--placeholder);
|
||||
content: attr(data-placeholder);
|
||||
@@ -87,6 +93,7 @@
|
||||
|
||||
.ProseMirror p {
|
||||
margin-bottom: 0px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.ProseMirror > p[data-spacing="double"] {
|
||||
|
||||
Reference in New Issue
Block a user