From 538e9e9b4e5455cd5d2fa1b3b1a8382257a49452 Mon Sep 17 00:00:00 2001 From: Arun Kumar Date: Fri, 10 Mar 2023 17:31:35 +0530 Subject: [PATCH 1/6] Desktop: Fixes #7506: Linux notebook display bug (#7897) --- packages/app-desktop/gui/Sidebar/Sidebar.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/app-desktop/gui/Sidebar/Sidebar.tsx b/packages/app-desktop/gui/Sidebar/Sidebar.tsx index 8ca179ad3..48810352b 100644 --- a/packages/app-desktop/gui/Sidebar/Sidebar.tsx +++ b/packages/app-desktop/gui/Sidebar/Sidebar.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; import { useEffect, useRef, useCallback, useMemo } from 'react'; +import styled from 'styled-components'; +import shim from '@joplin/lib/shim'; import { StyledRoot, StyledAddButton, StyledShareIcon, StyledHeader, StyledHeaderIcon, StyledAllNotesIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles'; import { ButtonLevel } from '../Button/Button'; import CommandService from '@joplin/lib/services/CommandService'; @@ -38,6 +40,15 @@ const { clipboard } = require('electron'); const logger = Logger.create('Sidebar'); +const StyledFoldersHolder = styled.div` + // linux bug: https://github.com/laurent22/joplin/issues/7506#issuecomment-1447101057 + & a.list-item { + ${shim.isLinux() && { + opacity: 1, + }} + } +`; + interface Props { themeId: number; dispatch: Function; @@ -705,13 +716,13 @@ const SidebarComponent = (props: Props) => { const folderItems = [renderAllNotesItem(theme, allNotesSelected)].concat(result.items); folderItemsOrder_.current = result.order; items.push( -
{folderItems} -
+ ); } From 9106fb82f3939a3018a9f6f90b571aa290ae2774 Mon Sep 17 00:00:00 2001 From: pedr Date: Fri, 10 Mar 2023 09:53:48 -0300 Subject: [PATCH 2/6] Chore: Desktop: Resolves #7879: Paste as Text shortcut pasting content twice (#7885) --- .../NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx index 5de384af8..943c2f238 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx @@ -77,16 +77,6 @@ function stripMarkup(markupLanguage: number, markup: string, options: any = null return markupToHtml_.stripMarkup(markupLanguage, markup, options); } -function createSyntheticClipboardEventWithoutHTML(): ClipboardEvent { - const clipboardData = new DataTransfer(); - for (const format of clipboard.availableFormats()) { - if (format !== 'text/html') { - clipboardData.setData(format, clipboard.read(format)); - } - } - return new ClipboardEvent('paste', { clipboardData }); -} - interface TinyMceCommand { name: string; value?: any; @@ -1076,24 +1066,24 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => { } } - function onKeyDown(event: any) { + async function onKeyDown(event: any) { // It seems "paste as text" is handled automatically on Windows and Linux, // so we need to run the below code only on macOS. If we were to run this // on Windows/Linux, we would have this double-paste issue: // https://github.com/laurent22/joplin/issues/4243 - // Handle "paste as text". Note that when pressing CtrlOrCmd+Shift+V it's going - // to trigger the "keydown" event but not the "paste" event, so it's ok to process - // it here and we don't need to do anything special in onPaste - if (!shim.isWindows() && !shim.isLinux()) { - if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') { - pasteAsPlainText(); - } + // While "paste as text" functionality is handled by Windows and Linux, if we + // want to allow the user to customize the shortcut we need to prevent when it + // has the default value so it doesn't paste the content twice + // (one by the system and the other by our code) + if ((event.metaKey || event.ctrlKey) && event.shiftKey && event.code === 'KeyV') { + event.preventDefault(); + pasteAsPlainText(null); } } - async function onPasteAsText() { - await onPaste(createSyntheticClipboardEventWithoutHTML()); + function onPasteAsText() { + pasteAsPlainText(null); } editor.on(TinyMceEditorEvents.KeyUp, onKeyUp); From b561460307540bd67671c6b56bc55a71a61f4cba Mon Sep 17 00:00:00 2001 From: Julien <32807437+julien-me@users.noreply.github.com> Date: Sun, 12 Mar 2023 23:26:15 +0800 Subject: [PATCH 3/6] Desktop: Resolves #7848: Made note list controls responsive (#7884) --- .../gui/NoteListControls/NoteListControls.tsx | 188 ++++++++++++++---- .../gui/NoteListWrapper/NoteListWrapper.tsx | 2 +- 2 files changed, 151 insertions(+), 39 deletions(-) diff --git a/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx b/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx index ef53e2e61..ec375d94f 100644 --- a/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx +++ b/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx @@ -1,6 +1,6 @@ import { AppState } from '../../app.reducer'; import * as React from 'react'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useMemo, useState } from 'react'; import SearchBar from '../SearchBar/SearchBar'; import Button, { ButtonLevel, ButtonSize, buttonSizePx } from '../Button/Button'; import CommandService from '@joplin/lib/services/CommandService'; @@ -11,6 +11,13 @@ import { _ } from '@joplin/lib/locale'; const { connect } = require('react-redux'); const styled = require('styled-components').default; +enum BaseBreakpoint { + Sm = 160, + Md = 190, + Lg = 40, + Xl = 474, +} + interface Props { showNewNoteButtons: boolean; sortOrderButtonsVisible: boolean; @@ -18,6 +25,14 @@ interface Props { sortOrderReverse: boolean; notesParentType: string; height: number; + width: number; +} + +interface Breakpoints { + Sm: number; + Md: number; + Lg: number; + Xl: number; } const StyledRoot = styled.div` @@ -34,7 +49,7 @@ const StyledButton = styled(Button)` width: auto; height: 26px; min-height: 26px; - flex: 1 0 auto; + max-width: none; .fa, .fas { font-size: 11px; @@ -54,7 +69,13 @@ const StyledPairButtonR = styled(Button)` width: auto; `; -const RowContainer = styled.div` +const TopRow = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +`; + +const BottomRow = styled.div` display: flex; flex-direction: row; flex: 1 1 auto; @@ -68,7 +89,100 @@ const SortOrderButtonsContainer = styled.div` `; function NoteListControls(props: Props) { + const [dynamicBreakpoints, setDynamicBreakpoints] = useState({ Sm: BaseBreakpoint.Sm, Md: BaseBreakpoint.Md, Lg: BaseBreakpoint.Lg, Xl: BaseBreakpoint.Xl }); + const searchBarRef = useRef(null); + const newNoteRef = useRef(null); + const newTodoRef = useRef(null); + const noteControlsRef = useRef(null); + const searchAndSortRef = useRef(null); + + const getTextWidth = (text: string): number => { + const canvas = document.createElement('canvas'); + if (!canvas) throw new Error('Failed to create canvas element'); + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Failed to get context'); + const fontWeight = getComputedStyle(newNoteRef.current).getPropertyValue('font-weight'); + const fontSize = getComputedStyle(newNoteRef.current).getPropertyValue('font-size'); + const fontFamily = getComputedStyle(newNoteRef.current).getPropertyValue('font-family'); + ctx.font = `${fontWeight} ${fontSize} ${fontFamily}`; + + return ctx.measureText(text).width; + }; + + // Initialize language-specific breakpoints + useEffect(() => { + // Use the longest string to calculate the amount of extra width needed + const smAdditional = getTextWidth(_('note')) > getTextWidth(_('to-do')) ? getTextWidth(_('note')) : getTextWidth(_('to-do')); + const mdAdditional = getTextWidth(_('New note')) > getTextWidth(_('New to-do')) ? getTextWidth(_('New note')) : getTextWidth(_('New to-do')); + + const Sm = BaseBreakpoint.Sm + smAdditional * 2; + const Md = BaseBreakpoint.Md + mdAdditional * 2; + const Lg = BaseBreakpoint.Lg + Md; + const Xl = BaseBreakpoint.Xl; + + setDynamicBreakpoints({ Sm, Md, Lg, Xl }); + }, []); + + const breakpoint = useMemo(() => { + // Find largest breakpoint that width is less than + const index = Object.values(dynamicBreakpoints).findIndex(x => props.width < x); + + return index === -1 ? dynamicBreakpoints.Xl : Object.values(dynamicBreakpoints)[index]; + }, [props.width, dynamicBreakpoints]); + + const noteButtonText = useMemo(() => { + if (breakpoint === dynamicBreakpoints.Sm) { + return ''; + } else if (breakpoint === dynamicBreakpoints.Md) { + return _('note'); + } else { + return _('New note'); + } + }, [breakpoint, dynamicBreakpoints]); + + const todoButtonText = useMemo(() => { + if (breakpoint === dynamicBreakpoints.Sm) { + return ''; + } else if (breakpoint === dynamicBreakpoints.Md) { + return _('to-do'); + } else { + return _('New to-do'); + } + }, [breakpoint, dynamicBreakpoints]); + + const noteIcon = useMemo(() => { + if (breakpoint === dynamicBreakpoints.Sm) { + return 'icon-note'; + } else { + return 'fas fa-plus'; + } + }, [breakpoint, dynamicBreakpoints]); + + const todoIcon = useMemo(() => { + if (breakpoint === dynamicBreakpoints.Sm) { + return 'far fa-check-square'; + } else { + return 'fas fa-plus'; + } + }, [breakpoint, dynamicBreakpoints]); + + const showTooltip = useMemo(() => { + if (breakpoint === dynamicBreakpoints.Sm) { + return true; + } else { + return false; + } + }, [breakpoint, dynamicBreakpoints.Sm]); + + useEffect(() => { + if (breakpoint === dynamicBreakpoints.Xl) { + noteControlsRef.current.style.flexDirection = 'row'; + searchAndSortRef.current.style.flex = '2 1 auto'; + } else { + noteControlsRef.current.style.flexDirection = 'column'; + } + }, [breakpoint, dynamicBreakpoints]); useEffect(() => { CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef)); @@ -127,57 +241,55 @@ function NoteListControls(props: Props) { if (!props.showNewNoteButtons) return null; return ( - - + - - + ); } return ( - + {renderNewNoteButtons()} - + - - {showsSortOrderButtons() && - - } - {showsSortOrderButtons() && - - } - - + {showsSortOrderButtons() && + + + + + } + ); } diff --git a/packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx b/packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx index a0f4a6ca3..5595cdb4f 100644 --- a/packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx +++ b/packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx @@ -33,7 +33,7 @@ export default function NoteListWrapper(props: Props) { return ( - + ); From e07e248fea1d128b0253bcb947d73f65afa68ab3 Mon Sep 17 00:00:00 2001 From: Julien <32807437+julien-me@users.noreply.github.com> Date: Fri, 3 Mar 2023 19:28:12 +0800 Subject: [PATCH 4/6] Desktop: Always show new note buttons (Regression) (#7850) --- packages/app-desktop/gui/NoteListControls/NoteListControls.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx b/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx index ec375d94f..46897c5c1 100644 --- a/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx +++ b/packages/app-desktop/gui/NoteListControls/NoteListControls.tsx @@ -296,7 +296,8 @@ function NoteListControls(props: Props) { const mapStateToProps = (state: AppState) => { return { - showNewNoteButtons: state.focusedField !== 'globalSearch', + // TODO: showNewNoteButtons and the logic associated is not needed anymore. + showNewNoteButtons: true, sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'], sortOrderField: state.settings['notes.sortOrder.field'], sortOrderReverse: state.settings['notes.sortOrder.reverse'], From d6d4897e1cd31ca47a3b642ea94e295714dfe591 Mon Sep 17 00:00:00 2001 From: pedr Date: Sun, 12 Mar 2023 12:33:16 -0300 Subject: [PATCH 5/6] Desktop: Resolves #7880: Paste as Text only working on hotkeys on Windows (#7886) From c96468149a5001d01e3e2e01446de981d1620bc1 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Sun, 12 Mar 2023 15:37:38 +0000 Subject: [PATCH 6/6] Desktop release v2.10.9 --- packages/app-desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index 3bce5aa16..7e60fa95e 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "2.10.8", + "version": "2.10.9", "description": "Joplin for Desktop", "main": "main.js", "private": true,