diff --git a/ui/src/components/Comment/index.scss b/ui/src/components/Comment/index.scss index 7264c66c4..592863f9c 100644 --- a/ui/src/components/Comment/index.scss +++ b/ui/src/components/Comment/index.scss @@ -23,6 +23,8 @@ .comments-wrap { .comment-item { + border-bottom: 1px solid var(--an-comment-item-border-bottom); + &:hover { @include media-breakpoint-up(md) { .control-area { @@ -30,7 +32,6 @@ } } } - border-bottom: 1px solid var(--an-comment-item-border-bottom); } .fmt { display: inline; diff --git a/ui/src/components/Editor/utils/codemirror/commands.test.cjs b/ui/src/components/Editor/utils/codemirror/commands.test.cjs new file mode 100644 index 000000000..294f38515 --- /dev/null +++ b/ui/src/components/Editor/utils/codemirror/commands.test.cjs @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const test = require('node:test'); +const ts = require('typescript'); + +const previousTypeScriptLoader = require.extensions['.ts']; +require.extensions['.ts'] = (module, filename) => { + const source = fs.readFileSync(filename, 'utf8'); + const output = ts.transpileModule(source, { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2020, + esModuleInterop: true, + }, + fileName: filename, + }).outputText; + module._compile(output, filename); +}; + +const { EditorState } = require('@codemirror/state'); +let createCodeMirrorAdapter; +try { + ({ createCodeMirrorAdapter } = require('./adapter.ts')); +} finally { + if (previousTypeScriptLoader) { + require.extensions['.ts'] = previousTypeScriptLoader; + } else { + delete require.extensions['.ts']; + } +} + +function createEditorView(doc = '') { + let state = EditorState.create({ doc }); + + return { + contentDOM: { + addEventListener() {}, + focus() {}, + removeEventListener() {}, + }, + get state() { + return state; + }, + dispatch(spec) { + state = state.update(spec).state; + }, + }; +} + +for (const [name, command, expected] of [ + ['unordered', 'insertUnorderedList', '- '], + ['ordered', 'insertOrderedList', '1. '], +]) { + test(`${name} list toolbar command inserts a marker in an empty editor`, () => { + const view = createEditorView(); + const editor = createCodeMirrorAdapter(view); + + editor[command](); + + assert.equal(view.state.doc.toString(), expected); + assert.equal(view.state.selection.main.head, expected.length); + }); +} diff --git a/ui/src/components/Editor/utils/codemirror/commands.ts b/ui/src/components/Editor/utils/codemirror/commands.ts index 546382d46..153e530fb 100644 --- a/ui/src/components/Editor/utils/codemirror/commands.ts +++ b/ui/src/components/Editor/utils/codemirror/commands.ts @@ -17,7 +17,7 @@ * under the License. */ -import { EditorSelection } from '@codemirror/state'; +import { EditorSelection, Line } from '@codemirror/state'; import { Editor, Level } from '../../types'; @@ -33,6 +33,22 @@ import { Editor, Level } from '../../types'; * @returns Object containing all command methods */ export function createCommandMethods(editor: Editor) { + const insertListMarkerOnBlankLine = (line: Line, marker: string) => { + if (line.text.trim() !== '') { + return false; + } + + editor.dispatch({ + changes: { + from: line.from, + to: line.to, + insert: marker, + }, + selection: EditorSelection.cursor(line.from + marker.length), + }); + return true; + }; + // Create methods object that allows self-reference const methods = { wrapText: (before: string, after = before, defaultText) => { @@ -128,8 +144,10 @@ export function createCommandMethods(editor: Editor) { }, insertOrderedList: () => { - const cursor = editor.getCursor(); - const line = editor.state.doc.line(cursor.line); + const line = editor.state.doc.lineAt(editor.state.selection.main.head); + if (insertListMarkerOnBlankLine(line, '1. ')) { + return; + } const lineText = line.text.trim(); if (/^\d+\.\s/.test(lineText)) { return; @@ -143,8 +161,10 @@ export function createCommandMethods(editor: Editor) { }, insertUnorderedList: () => { - const cursor = editor.getCursor(); - const line = editor.state.doc.line(cursor.line); + const line = editor.state.doc.lineAt(editor.state.selection.main.head); + if (insertListMarkerOnBlankLine(line, '- ')) { + return; + } const lineText = line.text.trim(); if (/^[-*+]\s/.test(lineText)) { return;