Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/cloud/components/Editor/EditorToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
mdiPageNextOutline,
mdiMathIntegralBox,
mdiCodeBrackets,
mdiFormatTextWrappingWrap,
} from '@mdi/js'
import { Position } from 'codemirror'
import EditorToolButton from './EditorToolButton'
Expand All @@ -26,6 +27,7 @@ import {
} from '../../lib/utils/events'
import { useI18n } from '../../lib/hooks/useI18n'
import { lngKeys } from '../../lib/i18n/types'
import { formatMarkdown } from '../../lib/editor/formatMarkdown'

interface EditorToolbarProps {
editorRef?: React.MutableRefObject<CodeMirror.Editor | null>
Expand Down Expand Up @@ -205,6 +207,47 @@ const EditorToolbar = ({ editorRef }: EditorToolbarProps) => {
}
}, [applyItalicStyle])

const formatEditorContent = useCallback(() => {
if (editorRef == null || editorRef.current == null) {
return
}

const editor = editorRef.current
const hasSelection = editor.somethingSelected()
const from = hasSelection ? editor.getCursor('from') : { line: 0, ch: 0 }
const lastLine = editor.lineCount() - 1
const to = hasSelection
? editor.getCursor('to')
: { line: lastLine, ch: (editor.getLine(lastLine) || '').length }
const source = editor.getRange(from, to, '\n')

if (source.trim() === '') {
return
}

const fromOffset = editor.indexFromPos(from)
const cursorOffset = Math.max(
0,
editor.indexFromPos(editor.getCursor()) - fromOffset
)

try {
const result = formatMarkdown(source, cursorOffset)

if (result.formatted === source) {
return
}

editor.operation(() => {
editor.replaceRange(result.formatted, from, to, '+format')
editor.setCursor(editor.posFromIndex(fromOffset + result.cursorOffset))
})
editor.focus()
} catch (error) {
console.warn('Unable to format markdown content', error)
}
}, [editorRef])

return (
<StyledEditorToolList>
<EditorHeaderToolDropdown
Expand Down Expand Up @@ -243,6 +286,12 @@ const EditorToolbar = ({ editorRef }: EditorToolbarProps) => {
style={{ marginRight: 20 }}
onClick={() => onFormatCallback('taskList')}
/>
<EditorToolButton
path={mdiFormatTextWrappingWrap}
tooltip={translate(lngKeys.EditorToolbarTooltipFormat)}
style={{ marginRight: 20 }}
onClick={formatEditorContent}
/>
<EditorToolButton
path={mdiFormatBold}
tooltip={translate(lngKeys.EditorToolbarTooltipBold)}
Expand Down
39 changes: 39 additions & 0 deletions src/cloud/lib/editor/formatMarkdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import prettier from 'prettier/standalone'
import parserMarkdown from 'prettier/parser-markdown'

export type MarkdownFormatResult = {
formatted: string
cursorOffset: number
}

export function formatMarkdown(
source: string,
cursorOffset: number
): MarkdownFormatResult {
const result = prettier.formatWithCursor(source, {
parser: 'markdown',
plugins: [parserMarkdown],
proseWrap: 'always',
printWidth: 80,
cursorOffset,
})

return keepOriginalFinalNewline(source, result.formatted, result.cursorOffset)
}

function keepOriginalFinalNewline(
source: string,
formatted: string,
cursorOffset: number
): MarkdownFormatResult {
if (/\r?\n$/.test(source) || !/\r?\n$/.test(formatted)) {
return { formatted, cursorOffset }
}

const stripped = formatted.replace(/\r?\n$/, '')

return {
formatted: stripped,
cursorOffset: Math.min(cursorOffset, stripped.length),
}
}
1 change: 1 addition & 0 deletions src/cloud/lib/i18n/enUS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ const enTranslation: TranslationSource = {
[lngKeys.EditorToolbarTooltipList]: 'Add a bulleted list',
[lngKeys.EditorToolbarTooltipNumberedList]: 'Add a numbered list',
[lngKeys.EditorToolbarTooltipTaskList]: 'Add a task list',
[lngKeys.EditorToolbarTooltipFormat]: 'Format document',
[lngKeys.EditorToolbarTooltipBold]: 'Add bold text',
[lngKeys.EditorToolbarTooltipItalic]: 'Add italic text',
[lngKeys.EditorToolbarTooltipCode]: 'Insert code',
Expand Down
1 change: 1 addition & 0 deletions src/cloud/lib/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ const frTranslation: TranslationSource = {
[lngKeys.EditorToolbarTooltipList]: 'Insérér une liste à puces',
[lngKeys.EditorToolbarTooltipNumberedList]: 'Insérér une liste numérique',
[lngKeys.EditorToolbarTooltipTaskList]: 'Insérer une liste de tâches',
[lngKeys.EditorToolbarTooltipFormat]: 'Formater le document',
[lngKeys.EditorToolbarTooltipBold]: 'Ajouter un texte en gras',
[lngKeys.EditorToolbarTooltipItalic]: 'Ajouter un texte en italique',
[lngKeys.EditorToolbarTooltipCode]: 'Insérer du code',
Expand Down
1 change: 1 addition & 0 deletions src/cloud/lib/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ const jpTranslation: TranslationSource = {
[lngKeys.EditorToolbarTooltipList]: '箇条書き',
[lngKeys.EditorToolbarTooltipNumberedList]: '番号リスト',
[lngKeys.EditorToolbarTooltipTaskList]: 'タスクリスト',
[lngKeys.EditorToolbarTooltipFormat]: '文書を整形',
[lngKeys.EditorToolbarTooltipBold]: '太文字',
[lngKeys.EditorToolbarTooltipItalic]: 'イタリック',
[lngKeys.EditorToolbarTooltipCode]: 'コード',
Expand Down
1 change: 1 addition & 0 deletions src/cloud/lib/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ export enum lngKeys {
EditorToolbarTooltipList = 'editor.toolbar.tooltips.list',
EditorToolbarTooltipNumberedList = 'editor.toolbar.tooltips.numberedlist',
EditorToolbarTooltipTaskList = 'editor.toolbar.tooltips.tasklist',
EditorToolbarTooltipFormat = 'editor.toolbar.tooltips.format',
EditorToolbarTooltipBold = 'editor.toolbar.tooltips.bold',
EditorToolbarTooltipItalic = 'editor.toolbar.tooltips.italic',
EditorToolbarTooltipCode = 'editor.toolbar.tooltips.code',
Expand Down
1 change: 1 addition & 0 deletions src/cloud/lib/i18n/zhCN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ const zhTranslation: TranslationSource = {
[lngKeys.EditorToolbarTooltipList]: '添加项目符号列表',
[lngKeys.EditorToolbarTooltipNumberedList]: '添加编号列表',
[lngKeys.EditorToolbarTooltipTaskList]: '添加任务列表',
[lngKeys.EditorToolbarTooltipFormat]: '格式化文档',
[lngKeys.EditorToolbarTooltipBold]: '添加粗体文本',
[lngKeys.EditorToolbarTooltipItalic]: '添加斜体文本',
[lngKeys.EditorToolbarTooltipCode]: '插入代码',
Expand Down
12 changes: 12 additions & 0 deletions typings/prettier.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
declare module 'prettier/standalone' {
import prettier from 'prettier'

export default prettier
}

declare module 'prettier/parser-markdown' {
import { Plugin } from 'prettier'

const parserMarkdown: Plugin
export default parserMarkdown
}