@@ -36,6 +37,27 @@ const emit = defineEmits(['copy', 'paste', 'clear'])
{{ selectedCount }}
+
+
+
{{ item.time }}
diff --git a/plugins/clipboard/src/utils/textMask.js b/plugins/clipboard/src/utils/textMask.js
new file mode 100644
index 000000000..a48cbc389
--- /dev/null
+++ b/plugins/clipboard/src/utils/textMask.js
@@ -0,0 +1,34 @@
+const MASK_CHARACTER = '•'
+const EDGE_VISIBLE_COUNT = 3
+
+const getVisibleEdgeCount = (visibleCharacterCount) => {
+ if (visibleCharacterCount <= 2) return 0
+ if (visibleCharacterCount <= EDGE_VISIBLE_COUNT * 2) return 1
+ return EDGE_VISIBLE_COUNT
+}
+
+/**
+ * Mask the middle of a string while retaining a recognizable prefix and suffix.
+ * Whitespace and line breaks keep their original positions.
+ * The original clipboard content is never modified.
+ */
+export const maskTextContent = (content) => {
+ const characters = Array.from(String(content ?? ''))
+ const visibleCharacterCount = characters.reduce(
+ (count, character) => count + (/\S/u.test(character) ? 1 : 0),
+ 0
+ )
+ const visibleEdgeCount = getVisibleEdgeCount(visibleCharacterCount)
+ let visibleIndex = 0
+
+ return characters.map(character => {
+ if (/\s/u.test(character)) return character
+
+ const shouldRemainVisible = visibleEdgeCount > 0 && (
+ visibleIndex < visibleEdgeCount ||
+ visibleIndex >= visibleCharacterCount - visibleEdgeCount
+ )
+ visibleIndex++
+ return shouldRemainVisible ? character : MASK_CHARACTER
+ }).join('')
+}
diff --git a/plugins/clipboard/tests/textMask.test.js b/plugins/clipboard/tests/textMask.test.js
new file mode 100644
index 000000000..13214852f
--- /dev/null
+++ b/plugins/clipboard/tests/textMask.test.js
@@ -0,0 +1,27 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { maskTextContent } from '../src/utils/textMask.js'
+
+test('keeps three visible characters at both ends of longer content', () => {
+ assert.equal(maskTextContent('P@ssw0rd-178452'), `P@s${'•'.repeat(9)}452`)
+})
+
+test('keeps one visible character at both ends of short content', () => {
+ assert.equal(maskTextContent('178452'), '1••••2')
+ assert.equal(maskTextContent('国际化'), '国•化')
+})
+
+test('preserves whitespace layout while counting only visible characters', () => {
+ assert.equal(maskTextContent('abc def\nxyz'), 'abc •••\nxyz')
+})
+
+test('masks very short content completely and handles Unicode code points', () => {
+ assert.equal(maskTextContent('🔐🙂'), '••')
+ assert.equal(maskTextContent('A'), '•')
+})
+
+test('handles empty values without exposing content', () => {
+ assert.equal(maskTextContent(''), '')
+ assert.equal(maskTextContent(null), '')
+ assert.equal(maskTextContent(undefined), '')
+})