Skip to content

Commit 401dcf4

Browse files
fix(notes): use CRLF when copying notes on Windows (#894)
1 parent c22c87b commit 401dcf4

4 files changed

Lines changed: 47 additions & 0 deletions

File tree

src/renderer/components/notes/NotesEditor.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
useTheme,
1313
} from '@/composables'
1414
import { i18n, ipc } from '@/electron'
15+
import { isWindows } from '@/utils'
1516
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
1617
import { markdown, markdownLanguage } from '@codemirror/lang-markdown'
1718
import { indentUnit } from '@codemirror/language'
@@ -25,6 +26,7 @@ import {
2526
placeholder,
2627
} from '@codemirror/view'
2728
import { GFM, type MarkdownConfig } from '@lezer/markdown'
29+
import { createClipboardOutput } from './cm-extensions/clipboardOutput'
2830
import {
2931
clearInlineFormatting,
3032
getHeadingLevel,
@@ -224,6 +226,7 @@ function createEditorState(doc: string): EditorState {
224226
? presentationTheme
225227
: createNotesEditTheme(raw, notesSettings),
226228
EditorView.lineWrapping,
229+
createClipboardOutput(isWindows),
227230
history(),
228231
Prec.highest(
229232
keymap.of(editable ? createListIndent({ indent: notesIndentUnit }) : []),
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { EditorState } from '@codemirror/state'
2+
import { EditorView } from '@codemirror/view'
3+
import { describe, expect, it } from 'vitest'
4+
import { createClipboardOutput } from '../clipboardOutput'
5+
6+
function filterClipboardOutput(text: string, isWindows: boolean) {
7+
const state = EditorState.create({
8+
extensions: [createClipboardOutput(isWindows)],
9+
})
10+
11+
return state
12+
.facet(EditorView.clipboardOutputFilter)
13+
.reduce((output, filter) => filter(output, state), text)
14+
}
15+
16+
describe('clipboard output', () => {
17+
it('converts LF line endings to CRLF on Windows', () => {
18+
expect(filterClipboardOutput('first\nsecond\nthird', true)).toBe(
19+
'first\r\nsecond\r\nthird',
20+
)
21+
})
22+
23+
it('does not duplicate CR in existing CRLF line endings on Windows', () => {
24+
expect(filterClipboardOutput('first\r\nsecond', true)).toBe(
25+
'first\r\nsecond',
26+
)
27+
})
28+
29+
it('leaves line endings unchanged on macOS and Linux', () => {
30+
expect(filterClipboardOutput('first\nsecond', false)).toBe('first\nsecond')
31+
})
32+
33+
it('leaves text without line endings unchanged', () => {
34+
expect(filterClipboardOutput('single line', true)).toBe('single line')
35+
})
36+
})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { EditorView } from '@codemirror/view'
2+
3+
export function createClipboardOutput(isWindows: boolean) {
4+
return EditorView.clipboardOutputFilter.of(text =>
5+
isWindows ? text.replace(/\r?\n/g, '\r\n') : text,
6+
)
7+
}

src/renderer/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { type ClassValue, clsx } from 'clsx'
22
import { twMerge } from 'tailwind-merge'
33

44
export const isMac = navigator.userAgent.toLowerCase().includes('mac')
5+
export const isWindows = navigator.userAgent.toLowerCase().includes('windows')
56

67
export function cn(...inputs: ClassValue[]) {
78
return twMerge(clsx(inputs))

0 commit comments

Comments
 (0)