mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Merge branch 'release-2.10' into dev
This commit is contained in:
commit
fd7b345efa
@ -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);
|
||||
|
@ -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<Breakpoints>({ 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,33 +241,33 @@ function NoteListControls(props: Props) {
|
||||
if (!props.showNewNoteButtons) return null;
|
||||
|
||||
return (
|
||||
<RowContainer>
|
||||
<StyledButton
|
||||
<TopRow className="new-note-todo-buttons">
|
||||
<StyledButton ref={newNoteRef}
|
||||
className="new-note-button"
|
||||
tooltip={CommandService.instance().label('newNote')}
|
||||
iconName="fas fa-plus"
|
||||
title={_('%s', 'New note')}
|
||||
tooltip={ showTooltip ? CommandService.instance().label('newNote') : '' }
|
||||
iconName={noteIcon}
|
||||
title={_('%s', noteButtonText)}
|
||||
level={ButtonLevel.Primary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewNoteButtonClick}
|
||||
/>
|
||||
<StyledButton
|
||||
<StyledButton ref={newTodoRef}
|
||||
className="new-todo-button"
|
||||
tooltip={CommandService.instance().label('newTodo')}
|
||||
iconName="fas fa-plus"
|
||||
title={_('%s', 'New to-do')}
|
||||
tooltip={ showTooltip ? CommandService.instance().label('newTodo') : '' }
|
||||
iconName={todoIcon}
|
||||
title={_('%s', todoButtonText)}
|
||||
level={ButtonLevel.Secondary}
|
||||
size={ButtonSize.Small}
|
||||
onClick={onNewTodoButtonClick}
|
||||
/>
|
||||
</RowContainer>
|
||||
</TopRow>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<StyledRoot ref={noteControlsRef}>
|
||||
{renderNewNoteButtons()}
|
||||
<RowContainer>
|
||||
<BottomRow ref={searchAndSortRef} className="search-and-sort">
|
||||
<SearchBar inputRef={searchBarRef}/>
|
||||
{showsSortOrderButtons() &&
|
||||
<SortOrderButtonsContainer>
|
||||
@ -175,7 +289,7 @@ function NoteListControls(props: Props) {
|
||||
/>
|
||||
</SortOrderButtonsContainer>
|
||||
}
|
||||
</RowContainer>
|
||||
</BottomRow>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export default function NoteListWrapper(props: Props) {
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<NoteListControls height={controlHeight}/>
|
||||
<NoteListControls height={controlHeight} width={noteListSize.width}/>
|
||||
<NoteList resizableLayoutEventEmitter={props.resizableLayoutEventEmitter} size={noteListSize} visible={props.visible}/>
|
||||
</StyledRoot>
|
||||
);
|
||||
|
@ -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(
|
||||
<div
|
||||
<StyledFoldersHolder
|
||||
className={`folders ${props.folderHeaderIsExpanded ? 'expanded' : ''}`}
|
||||
key="folder_items"
|
||||
style={foldersStyle}
|
||||
>
|
||||
{folderItems}
|
||||
</div>
|
||||
</StyledFoldersHolder>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user