You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-04-21 19:45:16 +02:00
Compare commits
5 Commits
server-v3.6.1
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| a2bcb69303 | |||
| 8b46ac32fd | |||
| 0554db62b5 | |||
| 02e5769a65 | |||
| 5e0829dc0d |
@@ -1123,9 +1123,11 @@ packages/editor/CodeMirror/utils/formatting/computeSelectionFormatting.js
|
||||
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
|
||||
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
|
||||
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
|
||||
packages/editor/CodeMirror/utils/formatting/markdownFormatPatterns.js
|
||||
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
|
||||
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineMultilineSelectionFormat.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
|
||||
|
||||
@@ -1096,9 +1096,11 @@ packages/editor/CodeMirror/utils/formatting/computeSelectionFormatting.js
|
||||
packages/editor/CodeMirror/utils/formatting/findInlineMatch.test.js
|
||||
packages/editor/CodeMirror/utils/formatting/findInlineMatch.js
|
||||
packages/editor/CodeMirror/utils/formatting/isIndentationEquivalent.js
|
||||
packages/editor/CodeMirror/utils/formatting/markdownFormatPatterns.js
|
||||
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.test.js
|
||||
packages/editor/CodeMirror/utils/formatting/tabsToSpaces.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineFormatGlobally.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineMultilineSelectionFormat.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineRegionSurrounded.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleInlineSelectionFormat.js
|
||||
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
|
||||
|
||||
@@ -43,6 +43,12 @@ export interface FolderPickerOptions {
|
||||
mustSelect?: boolean;
|
||||
}
|
||||
|
||||
export enum ViewToggleButtonMode {
|
||||
Hidden = 'hidden',
|
||||
ShowViewer = 'show-viewer',
|
||||
ShowEditor = 'show-editor',
|
||||
}
|
||||
|
||||
interface ScreenHeaderProps {
|
||||
selectedNoteIds: string[];
|
||||
selectedFolderId: string;
|
||||
@@ -70,9 +76,8 @@ interface ScreenHeaderProps {
|
||||
showContextMenuButton?: boolean;
|
||||
showPluginEditorButton?: boolean;
|
||||
showBackButton?: boolean;
|
||||
showViewToggleButton?: boolean;
|
||||
viewToggleButtonMode?: ViewToggleButtonMode;
|
||||
onViewTogglePress?: OnPressCallback;
|
||||
viewToggleIconName?: string;
|
||||
|
||||
saveButtonDisabled?: boolean;
|
||||
showSaveButton?: boolean;
|
||||
@@ -376,10 +381,12 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
};
|
||||
|
||||
const renderViewToggleButton = () => {
|
||||
if (!this.props.showViewToggleButton || !this.props.onViewTogglePress || !this.props.viewToggleIconName) return null;
|
||||
const mode = this.props.viewToggleButtonMode ?? ViewToggleButtonMode.Hidden;
|
||||
if (mode === ViewToggleButtonMode.Hidden || !this.props.onViewTogglePress) return null;
|
||||
|
||||
return renderTopButton({
|
||||
iconName: this.props.viewToggleIconName,
|
||||
description: _('Toggle view/edit'),
|
||||
iconName: mode === ViewToggleButtonMode.ShowViewer ? 'ionicon book' : 'ionicon pencil',
|
||||
description: mode === ViewToggleButtonMode.ShowViewer ? _('Stop editing') : _('Edit'),
|
||||
onPress: this.props.onViewTogglePress,
|
||||
visible: true,
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ const expectToBeEditing = async (editing: boolean) => {
|
||||
};
|
||||
|
||||
const openEditor = async () => {
|
||||
const editToggle = await screen.findByLabelText('Toggle view/edit');
|
||||
const editToggle = await screen.findByLabelText('Edit');
|
||||
|
||||
fireEvent.press(editToggle);
|
||||
await expectToBeEditing(true);
|
||||
@@ -383,10 +383,10 @@ describe('screens/Note', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should show toggle button', async () => {
|
||||
it('should show edit button', async () => {
|
||||
const { unmount } = await setupNoteWithPanes(['viewer']);
|
||||
const toggleButton = await screen.findByLabelText('Toggle view/edit');
|
||||
expect(toggleButton).toBeVisible();
|
||||
const editButton = await screen.findByLabelText('Edit');
|
||||
expect(editButton).toBeVisible();
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -398,7 +398,7 @@ describe('screens/Note', () => {
|
||||
const expectedEditing = !initialEditing;
|
||||
const { unmount } = await setupNoteWithPanes(panes);
|
||||
await expectToBeEditing(initialEditing);
|
||||
const toggleButton = await screen.findByLabelText('Toggle view/edit');
|
||||
const toggleButton = await screen.findByLabelText(/Edit|Stop editing/);
|
||||
fireEvent.press(toggleButton);
|
||||
await expectToBeEditing(expectedEditing);
|
||||
unmount();
|
||||
|
||||
@@ -21,7 +21,7 @@ import NavService, { OnNavigateCallback as OnNavigateCallback } from '@joplin/li
|
||||
import { ModelType } from '@joplin/lib/BaseModel';
|
||||
import { fileExtension, safeFileExtension } from '@joplin/lib/path-utils';
|
||||
import * as mimeUtils from '@joplin/lib/mime-utils';
|
||||
import ScreenHeader, { MenuOptionType } from '../../ScreenHeader';
|
||||
import ScreenHeader, { MenuOptionType, ViewToggleButtonMode } from '../../ScreenHeader';
|
||||
import NoteTagsDialog from '../NoteTagsDialog';
|
||||
import time from '@joplin/lib/time';
|
||||
import Checkbox from '../../Checkbox';
|
||||
@@ -1800,6 +1800,12 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
||||
this.setState({ titleContainerWidth: width });
|
||||
}
|
||||
}}
|
||||
|
||||
// Making this focusable works around a tab ordering bug on Android
|
||||
// See https://github.com/laurent22/joplin/issues/14548
|
||||
accessible={Platform.OS === 'android'}
|
||||
// Since the group is focusable, it also needs a label (otherwise TalkBack reads "unlabelled"):
|
||||
aria-label={_('Title')}
|
||||
>
|
||||
<TextWrapCalculator
|
||||
textCompStyle={this.styles().titleTextInput}
|
||||
@@ -1855,6 +1861,11 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
||||
|
||||
const { editorPlugin: activeEditorPlugin } = getActivePluginEditorView(this.props.plugins, this.props.windowId);
|
||||
|
||||
let viewEditToggleMode = this.state.mode === 'edit' ? ViewToggleButtonMode.ShowViewer : ViewToggleButtonMode.ShowEditor;
|
||||
if (!this.state.note || this.state.note.deleted_time > 0 || editorView) {
|
||||
viewEditToggleMode = ViewToggleButtonMode.Hidden;
|
||||
}
|
||||
|
||||
const header = <ScreenHeader
|
||||
folderPickerOptions={this.folderPickerOptions()}
|
||||
menuOptions={this.menuOptions()}
|
||||
@@ -1869,8 +1880,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
||||
undoButtonDisabled={!this.state.undoRedoButtonState.canUndo && this.state.undoRedoButtonState.canRedo}
|
||||
onUndoButtonPress={this.screenHeader_undoButtonPress}
|
||||
onRedoButtonPress={this.screenHeader_redoButtonPress}
|
||||
showViewToggleButton={!!this.state.note && !this.state.note.deleted_time && !editorView}
|
||||
viewToggleIconName={this.state.mode === 'edit' ? 'ionicon book' : 'ionicon pencil'}
|
||||
viewToggleButtonMode={viewEditToggleMode}
|
||||
onViewTogglePress={this.toggleVisiblePanes}
|
||||
title={getDisplayParentTitle(this.state.note, this.state.folder)}
|
||||
/>;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||
import { CommandRuntimeProps } from '../types';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { AccessibilityInfo } from 'react-native';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
// For compatibility with the desktop app, this command is called "toggleVisiblePanes".
|
||||
@@ -19,6 +21,8 @@ export const runtime = (props: CommandRuntimeProps): CommandRuntime => {
|
||||
const currentMode = props.getMode();
|
||||
const newMode = currentMode === 'edit' ? 'view' : 'edit';
|
||||
props.setMode(newMode);
|
||||
|
||||
AccessibilityInfo.announceForAccessibility(newMode === 'view' ? _('Viewing') : _('Editing'));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -117,10 +117,6 @@ const useStyles = (themeId: number) => {
|
||||
sidebarIcon: sidebarIconStyle,
|
||||
folderButton: folderButtonStyle,
|
||||
folderButtonText: folderButtonTextStyle,
|
||||
conflictFolderButtonText: {
|
||||
...folderButtonTextStyle,
|
||||
color: theme.colorError,
|
||||
},
|
||||
folderButtonSelected: {
|
||||
...folderButtonStyle,
|
||||
backgroundColor: theme.selectedColor,
|
||||
@@ -197,6 +193,12 @@ const FolderItem: React.FC<FolderItemProps> = props => {
|
||||
paddingRight: 10,
|
||||
backgroundColor: props.selected ? theme.selectedColor : undefined,
|
||||
},
|
||||
conflictFolderButtonText: {
|
||||
color: theme.colorError,
|
||||
},
|
||||
conflictFolderButtonSelectedText: {
|
||||
color: theme.colorErrorSelected,
|
||||
},
|
||||
});
|
||||
}, [props.selected, props.depth, props.themeId]);
|
||||
const baseStyles = props.styles;
|
||||
@@ -268,7 +270,18 @@ const FolderItem: React.FC<FolderItemProps> = props => {
|
||||
// depth is specified with an accessibilityLabel:
|
||||
const folderDepthDescription = props.depth > 0 ? _('(level %d)', props.depth) : '';
|
||||
const accessibilityLabel = `${folderTitle} ${folderDepthDescription}`.trim();
|
||||
const folderButtonTextStyle = props.folder.id === Folder.conflictFolderId() ? baseStyles.conflictFolderButtonText : baseStyles.folderButtonText;
|
||||
const isConflictFolder = props.folder.id === Folder.conflictFolderId();
|
||||
const textStyle = useMemo(() => {
|
||||
const result: TextStyle[] = [baseStyles.folderButtonText];
|
||||
if (isConflictFolder) {
|
||||
result.push(styles.conflictFolderButtonText);
|
||||
if (props.selected) {
|
||||
result.push(styles.conflictFolderButtonSelectedText);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [styles, props.selected, isConflictFolder, baseStyles.folderButtonText]);
|
||||
|
||||
return (
|
||||
<View key={props.folder.id} style={styles.buttonWrapper}>
|
||||
<TouchableRipple
|
||||
@@ -284,7 +297,7 @@ const FolderItem: React.FC<FolderItemProps> = props => {
|
||||
{renderFolderIcon(props.folder.id, folderIcon)}
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={folderButtonTextStyle}
|
||||
style={textStyle}
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
{folderTitle}
|
||||
|
||||
@@ -38,6 +38,49 @@ describe('markdownCommands', () => {
|
||||
expect(editor.state.doc.toString()).toBe('Testing...');
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: 'bolding bullet lists line by line',
|
||||
initialDocText: '- one\n- two',
|
||||
syntaxNodes: ['BulletList'],
|
||||
toggleCommand: toggleBolded,
|
||||
expectedAfterFirstToggle: '- **one**\n- **two**',
|
||||
},
|
||||
{
|
||||
name: 'bolding bullet lists (alternate format, with indentation) line by line',
|
||||
initialDocText: '+ one\n\t+ two',
|
||||
syntaxNodes: ['BulletList'],
|
||||
toggleCommand: toggleBolded,
|
||||
expectedAfterFirstToggle: '+ **one**\n\t+ **two**',
|
||||
},
|
||||
{
|
||||
name: 'italicizing ordered lists line by line',
|
||||
initialDocText: '1. one\n2. two',
|
||||
syntaxNodes: ['OrderedList'],
|
||||
toggleCommand: toggleItalicized,
|
||||
expectedAfterFirstToggle: '1. *one*\n2. *two*',
|
||||
},
|
||||
{
|
||||
name: 'bolding checklist content while preserving markers',
|
||||
initialDocText: '- [ ] one\n- [x] two',
|
||||
syntaxNodes: ['BulletList'],
|
||||
toggleCommand: toggleBolded,
|
||||
expectedAfterFirstToggle: '- [ ] **one**\n- [x] **two**',
|
||||
},
|
||||
])('should support $name', async ({ initialDocText, syntaxNodes, toggleCommand, expectedAfterFirstToggle }) => {
|
||||
const editor = await createTestEditor(
|
||||
initialDocText,
|
||||
EditorSelection.range(0, initialDocText.length),
|
||||
syntaxNodes,
|
||||
);
|
||||
|
||||
toggleCommand(editor);
|
||||
expect(editor.state.doc.toString()).toBe(expectedAfterFirstToggle);
|
||||
|
||||
toggleCommand(editor);
|
||||
expect(editor.state.doc.toString()).toBe(initialDocText);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['trailing', 'ABC ', '**ABC** '],
|
||||
['leading', ' ABC', ' **ABC**'],
|
||||
@@ -56,6 +99,42 @@ describe('markdownCommands', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should wrap fenced code block multiline selections as a whole region', async () => {
|
||||
const initialDocText = '```\none\ntwo\n```';
|
||||
const editor = await createTestEditor(
|
||||
initialDocText,
|
||||
EditorSelection.range(0, initialDocText.length),
|
||||
['FencedCode'],
|
||||
);
|
||||
|
||||
toggleBolded(editor);
|
||||
expect(editor.state.doc.toString()).toBe('**```\none\ntwo\n```**');
|
||||
});
|
||||
|
||||
it('should apply bold to blockquote list content without wrapping markers', async () => {
|
||||
const initialDocText = '> - one\n> - two';
|
||||
const editor = await createTestEditor(
|
||||
initialDocText,
|
||||
EditorSelection.range(0, initialDocText.length),
|
||||
['Blockquote', 'BulletList'],
|
||||
);
|
||||
|
||||
toggleBolded(editor);
|
||||
expect(editor.state.doc.toString()).toBe('> - **one**\n> - **two**');
|
||||
});
|
||||
|
||||
it('should preserve blank lines when bolding multiline list selections', async () => {
|
||||
const initialDocText = '- one\n\n- two';
|
||||
const editor = await createTestEditor(
|
||||
initialDocText,
|
||||
EditorSelection.range(0, initialDocText.length),
|
||||
['BulletList'],
|
||||
);
|
||||
|
||||
toggleBolded(editor);
|
||||
expect(editor.state.doc.toString()).toBe('- **one**\n\n- **two**');
|
||||
});
|
||||
|
||||
it('for a cursor, bolding, then italicizing, should produce a bold-italic region', async () => {
|
||||
const initialDocText = '';
|
||||
const editor = await createTestEditor(
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Shared regex patterns and constants for Markdown formatting utilities.
|
||||
// Provides centralized definitions of blockquote, list, and other formatting patterns
|
||||
// to prevent duplication and ensure consistency across formatting modules.
|
||||
//
|
||||
// DESIGN ASSUMPTIONS:
|
||||
// - Blockquote lines follow format "> content" (no leading whitespace before the marker)
|
||||
// - blockquoteDetectRegex is used to check if a line starts with blockquote marker
|
||||
// - blockquotePrefixRegex is used to extract complete blockquote prefixes including nesting
|
||||
|
||||
// Blockquote extraction pattern: matches zero or more leading blockquote markers (> )
|
||||
// Supports nested blockquotes and captures leading whitespace.
|
||||
// PRIMARY USE: toggleInlineMultilineSelectionFormat - extract full prefix to preserve markers
|
||||
// Example: " > > text" extracts " > > "
|
||||
export const blockquotePrefixRegex = /^(\s*(?:>\s*)+)/;
|
||||
|
||||
// Blockquote detection pattern: checks if a line starts with a blockquote marker
|
||||
// Detects: starts with "> " (blockquote marker + space)
|
||||
// PRIMARY USE: toggleRegionFormatGlobally - check if line is in blockquote
|
||||
// LIMITATION: Does NOT support leading whitespace before ">" marker
|
||||
// Example: "> text" matches, " > text" does NOT match
|
||||
export const blockquoteDetectRegex = /^>\s/;
|
||||
|
||||
// Length of a single blockquote marker with space ("> ").
|
||||
// Used for offset calculations when removing blockquote markers from formatted line content.
|
||||
// ASSUMPTION: Simple "> " structure without leading whitespace
|
||||
export const singleBlockquoteMarkerLength = '> '.length;
|
||||
|
||||
// List prefix pattern: matches list markers at the start of a line
|
||||
// Supports:
|
||||
// - Bullet lists: "- " or "* "
|
||||
// - Checklist items: "- [ ]", "- [x]", "- [X]"
|
||||
// - Ordered lists: "1. ", "2. ", etc.
|
||||
// Captures the entire prefix including leading whitespace.
|
||||
// PRIMARY USE: toggleInlineMultilineSelectionFormat - extract list prefix to preserve markers
|
||||
// Example: " - [ ] item" extracts " - [ ] "
|
||||
export const listPrefixRegex = /^(\s*(?:[-*+]\s\[[ xX]\]\s|[-*+]\s|\d+[.)]\s))/;
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Text, EditorSelection, EditorState, SelectionRange, ChangeSet } from '@codemirror/state';
|
||||
import { RegionSpec } from './RegionSpec';
|
||||
import { SelectionUpdate } from './types';
|
||||
import toggleInlineRegionSurrounded from './toggleInlineRegionSurrounded';
|
||||
import intersectsSyntaxNode from '../isInSyntaxNode';
|
||||
import { blockquotePrefixRegex, listPrefixRegex } from './markdownFormatPatterns';
|
||||
|
||||
const toggleWholeTextRegion = (content: string, spec: RegionSpec) => {
|
||||
if (!content.trim()) return content;
|
||||
|
||||
let doc = Text.of(content.split('\n'));
|
||||
const update = toggleInlineRegionSurrounded(doc, EditorSelection.range(0, content.length), spec);
|
||||
|
||||
if (update.changes) {
|
||||
const change = ChangeSet.of(update.changes, doc.length);
|
||||
doc = change.apply(doc);
|
||||
}
|
||||
return doc.toString();
|
||||
};
|
||||
|
||||
const toggleListLineContent = (lineText: string, spec: RegionSpec) => {
|
||||
const blockquotePrefix = lineText.match(blockquotePrefixRegex)?.[1] ?? '';
|
||||
const remainingText = lineText.slice(blockquotePrefix.length);
|
||||
const listPrefix = remainingText.match(listPrefixRegex)?.[1];
|
||||
if (!listPrefix) return toggleWholeTextRegion(lineText, spec);
|
||||
|
||||
const content = remainingText.slice(listPrefix.length);
|
||||
if (!content.trim()) return lineText;
|
||||
|
||||
return blockquotePrefix + listPrefix + toggleWholeTextRegion(content, spec);
|
||||
};
|
||||
|
||||
export const shouldUseMultilineInlineSelectionFormatting = (
|
||||
state: EditorState,
|
||||
sel: SelectionRange,
|
||||
spec: RegionSpec,
|
||||
) => {
|
||||
if (sel.empty) return false;
|
||||
if (spec.nodeName !== 'StrongEmphasis' && spec.nodeName !== 'Emphasis') return false;
|
||||
if (intersectsSyntaxNode(state, sel, 'FencedCode') || intersectsSyntaxNode(state, sel, 'CodeBlock')) return false;
|
||||
|
||||
const doc = state.doc;
|
||||
const startLine = doc.lineAt(sel.from);
|
||||
const endLine = doc.lineAt(sel.to);
|
||||
if (startLine.number === endLine.number) return false;
|
||||
|
||||
// Keep behavior predictable by applying this strategy only to full-line ranges.
|
||||
return sel.from === startLine.from && sel.to === endLine.to;
|
||||
};
|
||||
|
||||
const toggleInlineMultilineSelectionFormat = (
|
||||
state: EditorState,
|
||||
sel: SelectionRange,
|
||||
spec: RegionSpec,
|
||||
): SelectionUpdate => {
|
||||
const doc = state.doc;
|
||||
const selectedText = doc.sliceString(sel.from, sel.to);
|
||||
const transformedText = selectedText
|
||||
.split('\n')
|
||||
.map(line => toggleListLineContent(line, spec))
|
||||
.join('\n');
|
||||
|
||||
return {
|
||||
changes: [{ from: sel.from, to: sel.to, insert: transformedText }],
|
||||
range: EditorSelection.range(sel.from, sel.from + transformedText.length),
|
||||
};
|
||||
};
|
||||
|
||||
export default toggleInlineMultilineSelectionFormat;
|
||||
@@ -4,12 +4,16 @@ import { SelectionUpdate } from './types';
|
||||
import findInlineMatch, { MatchSide } from './findInlineMatch';
|
||||
import growSelectionToNode from '../growSelectionToNode';
|
||||
import toggleInlineRegionSurrounded from './toggleInlineRegionSurrounded';
|
||||
import toggleInlineMultilineSelectionFormat, { shouldUseMultilineInlineSelectionFormatting } from './toggleInlineMultilineSelectionFormat';
|
||||
|
||||
// Returns updated selections: For all selections in the given `EditorState`, toggles
|
||||
// whether each is contained in an inline region of type [spec].
|
||||
const toggleInlineSelectionFormat = (
|
||||
state: EditorState, spec: RegionSpec, sel: SelectionRange,
|
||||
): SelectionUpdate => {
|
||||
if (shouldUseMultilineInlineSelectionFormatting(state, sel, spec)) {
|
||||
return toggleInlineMultilineSelectionFormat(state, sel, spec);
|
||||
}
|
||||
const endMatchLen = findInlineMatch(state.doc, spec, sel, MatchSide.End);
|
||||
|
||||
// If at the end of the region, move the
|
||||
|
||||
@@ -3,9 +3,7 @@ import { RegionSpec } from './RegionSpec';
|
||||
import findInlineMatch, { MatchSide } from './findInlineMatch';
|
||||
import growSelectionToNode from '../growSelectionToNode';
|
||||
import toggleInlineSelectionFormat from './toggleInlineSelectionFormat';
|
||||
|
||||
const blockQuoteStartLen = '> '.length;
|
||||
const blockQuoteRegex = /^>\s/;
|
||||
import { blockquoteDetectRegex, singleBlockquoteMarkerLength } from './markdownFormatPatterns';
|
||||
|
||||
// Toggle formatting for all selections. For example,
|
||||
// toggling a code RegionSpec repeatedly should create:
|
||||
@@ -35,7 +33,7 @@ const toggleRegionFormatGlobally = (
|
||||
// Don't treat '> ' as part of the line's content if we're in a blockquote.
|
||||
let contentLength = line.text.length;
|
||||
if (inBlockQuote && preserveBlockQuotes) {
|
||||
contentLength -= blockQuoteStartLen;
|
||||
contentLength -= singleBlockquoteMarkerLength;
|
||||
}
|
||||
|
||||
// If it matches the entire line, remove the newline character.
|
||||
@@ -46,7 +44,7 @@ const toggleRegionFormatGlobally = (
|
||||
|
||||
// Take into account the extra '> ' characters, if necessary
|
||||
if (inBlockQuote && preserveBlockQuotes) {
|
||||
stopIdx += blockQuoteStartLen;
|
||||
stopIdx += singleBlockquoteMarkerLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +66,7 @@ const toggleRegionFormatGlobally = (
|
||||
|
||||
if (startMatchLen >= 0 && stopMatchLen >= 0) {
|
||||
const fromLine = doc.lineAt(sel.from);
|
||||
const inBlockQuote = fromLine.text.match(blockQuoteRegex);
|
||||
const inBlockQuote = fromLine.text.match(blockquoteDetectRegex);
|
||||
|
||||
let lineStartStr = '\n';
|
||||
if (inBlockQuote && preserveBlockQuotes) {
|
||||
@@ -131,7 +129,7 @@ const toggleRegionFormatGlobally = (
|
||||
for (let i = fromLine.number; i <= toLine.number; i++) {
|
||||
const line = doc.line(i);
|
||||
|
||||
if (!line.text.match(blockQuoteRegex)) {
|
||||
if (!line.text.match(blockquoteDetectRegex)) {
|
||||
inBlockQuote = false;
|
||||
break;
|
||||
}
|
||||
@@ -139,8 +137,8 @@ const toggleRegionFormatGlobally = (
|
||||
|
||||
// Ignore block quote characters if in a block quote.
|
||||
if (inBlockQuote && preserveBlockQuotes) {
|
||||
fromLineText = fromLineText.substring(blockQuoteStartLen);
|
||||
toLineText = toLineText.substring(blockQuoteStartLen);
|
||||
fromLineText = fromLineText.substring(singleBlockquoteMarkerLength);
|
||||
toLineText = toLineText.substring(singleBlockquoteMarkerLength);
|
||||
}
|
||||
|
||||
// Otherwise, we're toggling the block version
|
||||
|
||||
@@ -226,6 +226,10 @@ export default class InteropService_Importer_OneNote extends InteropService_Impo
|
||||
(dom: Document, currentFolder: string) => this.convertExternalLinksToInternalLinks_(dom, currentFolder, idMap),
|
||||
(dom: Document, _currentFolder: string) => Promise.resolve(this.simplifyHtml_(dom)),
|
||||
];
|
||||
// Workaround: HTML read directly from the filesystem can cause parseFromString to hang.
|
||||
// Force creation of a new string.
|
||||
// See https://github.com/laurent22/joplin/issues/15132
|
||||
html = `${html} `.substring(0, html.length);
|
||||
const dom = this.domParser.parseFromString(html, 'text/html');
|
||||
|
||||
let changed = false;
|
||||
|
||||
@@ -11,6 +11,7 @@ const input: Theme = {
|
||||
oddBackgroundColor: '#eeeeee',
|
||||
color: '#32373F', // For regular text
|
||||
colorError: 'red',
|
||||
colorErrorSelected: '#d00000',
|
||||
colorCorrect: 'green',
|
||||
colorWarn: 'rgb(228,86,0)',
|
||||
colorWarnUrl: '#155BDA',
|
||||
@@ -87,6 +88,7 @@ const expected = `
|
||||
--joplin-color-correct: green;
|
||||
--joplin-color-error: red;
|
||||
--joplin-color-error2: #ff6c6c;
|
||||
--joplin-color-error-selected: #d00000;
|
||||
--joplin-color-faded: #7C8B9E;
|
||||
--joplin-color-warn: rgb(228,86,0);
|
||||
--joplin-color-warn2: #ffcb81;
|
||||
|
||||
@@ -21,6 +21,7 @@ const theme: Theme = {
|
||||
dividerColor: '#555555',
|
||||
selectedColor: '#616161',
|
||||
urlColor: 'rgb(166,166,255)',
|
||||
colorErrorSelected: '#FFD7D7',
|
||||
|
||||
// Color scheme "2" is used for the sidebar. It's white text over
|
||||
// dark blue background.
|
||||
|
||||
@@ -18,6 +18,7 @@ const theme: Theme = {
|
||||
dividerColor: '#dddddd',
|
||||
selectedColor: '#e5e5e5',
|
||||
urlColor: '#155BDA',
|
||||
colorErrorSelected: '#d00000',
|
||||
|
||||
// Color scheme "2" is used for the sidebar. It's white text over
|
||||
// dark blue background.
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface Theme {
|
||||
oddBackgroundColor: string;
|
||||
color: string; // For regular text
|
||||
colorError: string;
|
||||
colorErrorSelected: string; // On a selectedColor background
|
||||
colorCorrect: string;
|
||||
colorWarn: string;
|
||||
colorWarnUrl: string; // For URL displayed over a warningBackgroundColor
|
||||
|
||||
Reference in New Issue
Block a user