From fbc628e3311fc6f2ff7859d915bd12d584f1c63c Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 5 Feb 2024 23:45:22 -0500 Subject: [PATCH] feat: Add copy button to code selection, closes #6499 --- app/editor/menus/formatting.tsx | 11 ++++++++++ shared/editor/nodes/CodeFence.ts | 36 +++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/editor/menus/formatting.tsx b/app/editor/menus/formatting.tsx index 4e52e12d7..d207ccc86 100644 --- a/app/editor/menus/formatting.tsx +++ b/app/editor/menus/formatting.tsx @@ -15,6 +15,7 @@ import { ItalicIcon, OutdentIcon, IndentIcon, + CopyIcon, } from "outline-icons"; import { EditorState } from "prosemirror-state"; import { isInTable } from "prosemirror-tables"; @@ -171,5 +172,15 @@ export default function formattingMenuItems( label: isCodeBlock ? dictionary.comment : undefined, active: isMarkActive(schema.marks.comment), }, + { + name: "separator", + visible: isCode, + }, + { + name: "copyToClipboard", + icon: , + tooltip: dictionary.copy, + visible: isCode, + }, ]; } diff --git a/shared/editor/nodes/CodeFence.ts b/shared/editor/nodes/CodeFence.ts index 412ec914b..b65f073b2 100644 --- a/shared/editor/nodes/CodeFence.ts +++ b/shared/editor/nodes/CodeFence.ts @@ -8,7 +8,7 @@ import { Schema, Node as ProsemirrorNode, } from "prosemirror-model"; -import { Command, Plugin, PluginKey } from "prosemirror-state"; +import { Command, Plugin, PluginKey, TextSelection } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; import refractor from "refractor/core"; import bash from "refractor/lang/bash"; @@ -74,6 +74,7 @@ import Prism from "../extensions/Prism"; import { isCode } from "../lib/isCode"; import { MarkdownSerializerState } from "../lib/markdown/serializer"; import { findParentNode } from "../queries/findParentNode"; +import getMarkRange from "../queries/getMarkRange"; import isInCode from "../queries/isInCode"; import Node from "./Node"; @@ -193,16 +194,37 @@ export default class CodeFence extends Node { ...attrs, }); }, - copyToClipboard: (): Command => (state) => { + copyToClipboard: (): Command => (state, dispatch) => { const codeBlock = findParentNode(isCode)(state.selection); - if (!codeBlock) { - return false; + if (codeBlock) { + copy(codeBlock.node.textContent); + toast.message(this.options.dictionary.codeCopied); + return true; } - copy(codeBlock.node.textContent); - toast.message(this.options.dictionary.codeCopied); - return true; + const { doc, tr } = state; + const range = + getMarkRange( + doc.resolve(state.selection.from), + this.editor.schema.marks.code_inline + ) || + getMarkRange( + doc.resolve(state.selection.to), + this.editor.schema.marks.code_inline + ); + + if (range) { + const $end = doc.resolve(range.to); + tr.setSelection(new TextSelection($end, $end)); + dispatch?.(tr); + + copy(tr.doc.textBetween(state.selection.from, state.selection.to)); + toast.message(this.options.dictionary.codeCopied); + return true; + } + + return false; }, }; }