diff --git a/apps/mobile/app/screens/editor/tiptap/types.ts b/apps/mobile/app/screens/editor/tiptap/types.ts index aab228f71..a9e3d3026 100644 --- a/apps/mobile/app/screens/editor/tiptap/types.ts +++ b/apps/mobile/app/screens/editor/tiptap/types.ts @@ -48,6 +48,8 @@ export type Settings = { keyboardShown?: boolean; doubleSpacedLines?: boolean; corsProxy: string; + fontSize: string; + fontFamily: string; }; export type EditorProps = { diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts b/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts index b290bab48..959528946 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts @@ -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 () => { diff --git a/apps/mobile/app/screens/settings/components.tsx b/apps/mobile/app/screens/settings/components.tsx index da09823b2..3109e6c92 100644 --- a/apps/mobile/app/screens/settings/components.tsx +++ b/apps/mobile/app/screens/settings/components.tsx @@ -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: , homeselector: , @@ -35,5 +36,6 @@ export const components: { [name: string]: ReactElement } = { "debug-logs": , "sound-picker": , licenses: , - "trash-interval-selector": + "trash-interval-selector": , + "font-selector": }; diff --git a/apps/mobile/app/screens/settings/font-selector.jsx b/apps/mobile/app/screens/settings/font-selector.jsx new file mode 100644 index 000000000..e25697e07 --- /dev/null +++ b/apps/mobile/app/screens/settings/font-selector.jsx @@ -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 . +*/ + +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 ( + { + setWidth(event.nativeEvent.layout.width); + }} + style={{ + width: "100%" + }} + > + { + menuRef.current?.hide(); + }} + anchor={ + { + menuRef.current?.show(); + }} + type="grayBg" + customStyle={{ + flexDirection: "row", + alignItems: "center", + marginTop: 10, + width: "100%", + justifyContent: "space-between", + padding: 12 + }} + > + {getFontById(defaultFontFamily).title} + + + } + > + {getFonts().map((item) => ( + { + 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} + + ))} + + + ); +}; diff --git a/apps/mobile/app/screens/settings/section-item.tsx b/apps/mobile/app/screens/settings/section-item.tsx index 53ac29af8..aab39a1c0 100644 --- a/apps/mobile/app/screens/settings/section-item.tsx +++ b/apps/mobile/app/screens/settings/section-item.tsx @@ -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 : ( { defaultValue={item.inputProperties?.defaultValue} /> )} + + {item.type === "input-selector" && ( + + { + 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} + /> + { + 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} + /> + { + 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} + /> + + )} diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx index badfbc490..9080d3dc5 100644 --- a/apps/mobile/app/screens/settings/settings-data.tsx +++ b/apps/mobile/app/screens/settings/settings-data.tsx @@ -18,6 +18,7 @@ along with this program. If not, see . */ 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", diff --git a/apps/mobile/app/screens/settings/types.ts b/apps/mobile/app/screens/settings/types.ts index 70c1720fd..80e95db90 100644 --- a/apps/mobile/app/screens/settings/types.ts +++ b/apps/mobile/app/screens/settings/types.ts @@ -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 = { diff --git a/apps/mobile/app/stores/use-navigation-store.ts b/apps/mobile/app/stores/use-navigation-store.ts index 37fdcefc4..b87aaa4b0 100644 --- a/apps/mobile/app/stores/use-navigation-store.ts +++ b/apps/mobile/app/stores/use-navigation-store.ts @@ -69,6 +69,7 @@ export type RouteParams = { AppLock: AppLockRouteParams; Auth: AuthParams; Reminders: GenericRouteParam; + SettingsGroup: GenericRouteParam; }; export type RouteName = keyof RouteParams; diff --git a/apps/mobile/app/stores/use-setting-store.ts b/apps/mobile/app/stores/use-setting-store.ts index f98721fbf..704f3b681 100644 --- a/apps/mobile/app/stores/use-setting-store.ts +++ b/apps/mobile/app/stores/use-setting-store.ts @@ -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((set) => ({ defaultSnoozeTime: "5", corsProxy: "https://cors.notesnook.com", reminderNotificationMode: "urgent", - notificationSound: undefined + notificationSound: undefined, + defaultFontFamily: "sans-serif", + defaultFontSize: "16" }, sheetKeyboardHandler: true, fullscreen: false, diff --git a/apps/mobile/app/utils/index.js b/apps/mobile/app/utils/index.js index 1fd892d4b..517c397e7 100755 --- a/apps/mobile/app/utils/index.js +++ b/apps/mobile/app/utils/index.js @@ -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); +} diff --git a/apps/web/src/components/dropdown-button/index.js b/apps/web/src/components/dropdown-button/index.js index a88d91048..8fe76e1f3 100644 --- a/apps/web/src/components/dropdown-button/index.js +++ b/apps/web/src/components/dropdown-button/index.js @@ -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 ( - +