feat: Change default font size & font family in editor

This commit is contained in:
Ammar Ahmed
2023-04-17 23:45:28 +05:00
committed by GitHub
27 changed files with 580 additions and 106 deletions

View File

@@ -48,6 +48,8 @@ export type Settings = {
keyboardShown?: boolean;
doubleSpacedLines?: boolean;
corsProxy: string;
fontSize: string;
fontFamily: string;
};
export type EditorProps = {

View File

@@ -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 () => {

View File

@@ -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 />
};

View 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>
);
};

View File

@@ -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>

View File

@@ -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",

View File

@@ -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 = {

View File

@@ -69,6 +69,7 @@ export type RouteParams = {
AppLock: AppLockRouteParams;
Auth: AuthParams;
Reminders: GenericRouteParam;
SettingsGroup: GenericRouteParam;
};
export type RouteName = keyof RouteParams;

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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 })}
>

View File

@@ -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 };
}

View File

@@ -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>

View File

@@ -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%"

View File

@@ -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)."

View File

@@ -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) => {

View File

@@ -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;
});

View File

@@ -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 = {

View File

@@ -39,6 +39,8 @@ export type Settings = {
noHeader?: boolean;
doubleSpacedLines?: boolean;
corsProxy: string;
fontSize: number;
fontFamily: string;
};
/* eslint-disable no-var */

View File

@@ -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}`

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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;
})
}));

View File

@@ -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}>

View File

@@ -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
}
});
}

View 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);
}

View File

@@ -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"] {