自动将您的消息草稿保存到 localStorage,刷新页面时自动恢复。再也不会丢失输入内容。
// ==UserScript==
// @name DeepSeek Input Restore
// @namespace https://kyoya.is-a.dev
// @version 1.2.0
// @description Automatically saves your message draft to localStorage and restores it when you refresh the page. Never lose your input again.
// @description:tr Mesaj taslağınızı otomatik olarak localStorage'a kaydeder ve sayfayı yenilediğinizde geri yükler. Bir daha yazdıklarınızı kaybetmeyin.
// @description:zh-CN 自动将您的消息草稿保存到 localStorage,刷新页面时自动恢复。再也不会丢失输入内容。
// @author kyoyacchi
// @license MIT
// @icon https://deepseek.com/favicon.ico
// @match https://chat.deepseek.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'ds_input_draft';
// ─── Robust textarea finder ─────────────────────────────────────────────────
// The name="search" attribute is language-independent and stays fixed in
// DeepSeek's internal markup. We never rely on the placeholder as primary.
function findTextarea() {
// 1. Priority: name attribute (language-independent, most reliable)
const byName = document.querySelector('textarea[name="search"]');
if (byName) return byName;
// 2. Fallback: the single visible textarea on the page
const candidates = Array.from(document.querySelectorAll('textarea'))
.filter(el => el.offsetParent !== null);
if (candidates.length === 1) return candidates[0];
// 3. Last resort: placeholder (try both EN/TR)
const byPlaceholder = candidates.find(el => {
const ph = (el.getAttribute('placeholder') || '').toLowerCase();
return ph.includes('deepseek') || ph.includes('message') || ph.includes('mesaj');
});
if (byPlaceholder) return byPlaceholder;
return candidates[candidates.length - 1] || null;
}
function findSendButton() {
// Design system class, language-independent
return document.querySelector('div[role="button"].ds-button--primary.ds-button--circle');
}
// ─── Helpers ────────────────────────────────────────────────────────────────
function save(value) {
if (value) {
localStorage.setItem(STORAGE_KEY, value);
} else {
localStorage.removeItem(STORAGE_KEY);
}
}
function restore(textarea) {
const saved = localStorage.getItem(STORAGE_KEY);
if (!saved || !textarea) return;
const nativeSetter = Object.getOwnPropertyDescriptor(
HTMLTextAreaElement.prototype,
'value'
).set;
nativeSetter.call(textarea, saved);
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true }));
textarea.focus();
}
// ─── Listener setup ─────────────────────────────────────────────────────────
function attachListeners(textarea) {
// Save on every keystroke
textarea.addEventListener('input', () => save(textarea.value));
// Clear draft when Enter is pressed (message sent)
textarea.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
localStorage.removeItem(STORAGE_KEY);
}
});
}
// Clear draft when the send button is clicked
document.addEventListener('click', (e) => {
const btn = e.target.closest('div[role="button"].ds-button--primary.ds-button--circle');
if (btn) localStorage.removeItem(STORAGE_KEY);
}, true);
// ─── Wait for textarea ───────────────────────────────────────────────────────
// DeepSeek is a SPA so the textarea might not exist on document-idle.
// We observe the DOM until it appears, then attach listeners and restore.
// We also re-check on every new chat navigation (textarea can be re-mounted).
let knownTextarea = null;
function init() {
const textarea = findTextarea();
if (textarea && textarea !== knownTextarea) {
knownTextarea = textarea;
attachListeners(textarea);
// Small delay so React finishes hydrating before we write the value
setTimeout(() => restore(textarea), 400);
}
}
const observer = new MutationObserver(init);
observer.observe(document.body, { childList: true, subtree: true });
// Also try immediately in case it's already rendered
init();
})();