feat(v4.0.0): 快速插入支持选中位置插入
- 先选中部分文本,再双击可在选中位置前/后插入 - 支持替换选中内容 - 自动识别选中文本并在原内容中定位
This commit is contained in:
@@ -1084,6 +1084,14 @@ INDEX_TEMPLATE = '''
|
||||
.markdown-content pre { font-size: 0.85em; overflow-x: auto; }
|
||||
.markdown-content ul, .markdown-content ol { margin-bottom: 0.5em; }
|
||||
.markdown-content a { color: #0d6efd; }
|
||||
/* 快速插入区域样式 */
|
||||
.quick-insert-area:hover {
|
||||
background: #f0f8ff;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
.quick-insert-area:active {
|
||||
background: #e8f4ff;
|
||||
}
|
||||
.star-btn { font-size: 11px; }
|
||||
.status-pending { color: #ffc107; }
|
||||
.status-in_progress { color: #17a2b8; }
|
||||
@@ -1789,6 +1797,55 @@ INDEX_TEMPLATE = '''
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速插入模态框 -->
|
||||
<div class="modal fade" id="quickInsertModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-plus-circle"></i> 快速插入内容</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="quickInsertItemId">
|
||||
<input type="hidden" id="quickInsertField">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">插入位置</label>
|
||||
<select id="quickInsertPosition" class="form-select">
|
||||
<option value="start">开头</option>
|
||||
<option value="end" selected>末尾</option>
|
||||
<option value="selection_before">选中内容之前</option>
|
||||
<option value="selection_after">选中内容之后</option>
|
||||
<option value="selection_replace">替换选中内容</option>
|
||||
</select>
|
||||
<input type="hidden" id="quickInsertSelection">
|
||||
<div id="quickInsertSelectionInfo" class="form-text" style="display:none;">
|
||||
选中内容: <code id="quickInsertSelectionPreview"></code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">要插入的内容</label>
|
||||
<textarea id="quickInsertContent" class="form-control" rows="5" placeholder="输入要插入的内容..."></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">插入格式</label>
|
||||
<select id="quickInsertFormat" class="form-select">
|
||||
<option value="raw">原文本</option>
|
||||
<option value="newline">独立一行</option>
|
||||
<option value="bullet">列表项 (- item)</option>
|
||||
<option value="heading4">标题 ####</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" onclick="executeQuickInsert()">
|
||||
<i class="bi bi-check"></i> 插入并保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 移动到文件夹模态框 -->
|
||||
<div class="modal fade" id="moveToFolderModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
@@ -2691,7 +2748,19 @@ async function showDetail(id) {
|
||||
}
|
||||
|
||||
if (item.content) {
|
||||
html += `<div class="mb-3"><strong>内容:</strong><br><div class="border rounded p-3 bg-light markdown-content">${renderMarkdown(item.content)}</div></div>`;
|
||||
html += `<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<strong>内容:</strong>
|
||||
<small class="text-muted"><i class="bi bi-hand-index"></i> 可先选中部分文本,再双击在此处插入</small>
|
||||
</div>
|
||||
<div class="border rounded p-3 bg-light markdown-content quick-insert-area"
|
||||
data-field="content"
|
||||
data-item-id="${item.id}"
|
||||
ondblclick="showQuickInsert(${item.id}, 'content')"
|
||||
style="cursor: pointer;">
|
||||
${renderMarkdown(item.content)}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (item.source) {
|
||||
@@ -2711,7 +2780,19 @@ async function showDetail(id) {
|
||||
}
|
||||
|
||||
if (item.note) {
|
||||
html += `<div class="mb-3"><strong>详情/备注:</strong><br><div class="border rounded p-3 bg-light markdown-content">${renderMarkdown(item.note)}</div></div>`;
|
||||
html += `<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<strong>详情/备注:</strong>
|
||||
<small class="text-muted"><i class="bi bi-hand-index"></i> 可先选中部分文本,再双击在此处插入</small>
|
||||
</div>
|
||||
<div class="border rounded p-3 bg-light markdown-content quick-insert-area"
|
||||
data-field="note"
|
||||
data-item-id="${item.id}"
|
||||
ondblclick="showQuickInsert(${item.id}, 'note')"
|
||||
style="cursor: pointer;">
|
||||
${renderMarkdown(item.note)}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += `<div class="text-muted small"><strong>创建时间:</strong> ${formatDate(item.created_at)}<br><strong>更新时间:</strong> ${formatDate(item.updated_at)}</div>`;
|
||||
@@ -3546,6 +3627,137 @@ async function emptyTrash() {
|
||||
|
||||
// ============ Folder 文件夹管理 ============
|
||||
|
||||
// ============ 快速插入功能 ============
|
||||
|
||||
// 保存当前详情数据,用于快速插入
|
||||
let currentDetailItem = null;
|
||||
|
||||
// 显示快速插入模态框
|
||||
function showQuickInsert(itemId, field) {
|
||||
document.getElementById('quickInsertItemId').value = itemId;
|
||||
document.getElementById('quickInsertField').value = field;
|
||||
document.getElementById('quickInsertContent').value = '';
|
||||
document.getElementById('quickInsertPosition').value = 'end';
|
||||
document.getElementById('quickInsertFormat').value = 'newline';
|
||||
|
||||
// 获取当前选中的文本
|
||||
const selection = window.getSelection();
|
||||
const selectedText = selection.toString().trim();
|
||||
|
||||
if (selectedText) {
|
||||
document.getElementById('quickInsertSelection').value = selectedText;
|
||||
document.getElementById('quickInsertSelectionInfo').style.display = 'block';
|
||||
document.getElementById('quickInsertSelectionPreview').textContent = selectedText.substring(0, 50) + (selectedText.length > 50 ? '...' : '');
|
||||
document.getElementById('quickInsertPosition').value = 'selection_after'; // 默认在选中内容之后插入
|
||||
} else {
|
||||
document.getElementById('quickInsertSelection').value = '';
|
||||
document.getElementById('quickInsertSelectionInfo').style.display = 'none';
|
||||
}
|
||||
|
||||
new bootstrap.Modal(document.getElementById('quickInsertModal')).show();
|
||||
}
|
||||
|
||||
// 执行快速插入
|
||||
async function executeQuickInsert() {
|
||||
const itemId = document.getElementById('quickInsertItemId').value;
|
||||
const field = document.getElementById('quickInsertField').value;
|
||||
const insertContent = document.getElementById('quickInsertContent').value.trim();
|
||||
const position = document.getElementById('quickInsertPosition').value;
|
||||
const format = document.getElementById('quickInsertFormat').value;
|
||||
const selectedText = document.getElementById('quickInsertSelection').value;
|
||||
|
||||
if (!insertContent) {
|
||||
alert('请输入要插入的内容');
|
||||
return;
|
||||
}
|
||||
|
||||
// 离线检查
|
||||
if (!checkOnlineBeforeAction('快速插入')) return;
|
||||
|
||||
// 获取当前数据
|
||||
const res = await fetch(`${API_BASE}/items/${itemId}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (!data.success) {
|
||||
alert('获取数据失败');
|
||||
return;
|
||||
}
|
||||
|
||||
const item = data.data;
|
||||
let originalContent = item[field] || '';
|
||||
|
||||
// 根据格式处理插入内容
|
||||
let formattedContent = insertContent;
|
||||
switch (format) {
|
||||
case 'newline':
|
||||
formattedContent = '\\n' + insertContent;
|
||||
break;
|
||||
case 'bullet':
|
||||
formattedContent = '\\n- ' + insertContent;
|
||||
break;
|
||||
case 'heading4':
|
||||
formattedContent = '\\n#### ' + insertContent;
|
||||
break;
|
||||
default:
|
||||
formattedContent = insertContent;
|
||||
}
|
||||
|
||||
// 根据位置插入
|
||||
let newContent;
|
||||
|
||||
if (position === 'start') {
|
||||
newContent = formattedContent + originalContent;
|
||||
} else if (position === 'end') {
|
||||
newContent = originalContent + formattedContent;
|
||||
} else if (position.startsWith('selection_') && selectedText) {
|
||||
// 在选中内容位置插入
|
||||
const selectionIndex = originalContent.indexOf(selectedText);
|
||||
if (selectionIndex === -1) {
|
||||
alert('未在原内容中找到选中的文本,可能是因为Markdown渲染后的文本与原文本不完全一致。\\n请尝试手动编辑。');
|
||||
return;
|
||||
}
|
||||
|
||||
const before = originalContent.substring(0, selectionIndex);
|
||||
const selected = originalContent.substring(selectionIndex, selectionIndex + selectedText.length);
|
||||
const after = originalContent.substring(selectionIndex + selectedText.length);
|
||||
|
||||
if (position === 'selection_before') {
|
||||
newContent = before + formattedContent + selected + after;
|
||||
} else if (position === 'selection_after') {
|
||||
newContent = before + selected + formattedContent + after;
|
||||
} else if (position === 'selection_replace') {
|
||||
newContent = before + formattedContent + after;
|
||||
}
|
||||
} else {
|
||||
// 没有选中内容但选择了选中位置选项,默认末尾
|
||||
newContent = originalContent + formattedContent;
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
const updateData = {};
|
||||
updateData[field] = newContent;
|
||||
|
||||
const updateRes = await fetch(`${API_BASE}/items/${itemId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
const updateResult = await updateRes.json();
|
||||
|
||||
if (updateResult.success) {
|
||||
bootstrap.Modal.getInstance(document.getElementById('quickInsertModal')).hide();
|
||||
|
||||
// 刷新详情显示
|
||||
showDetail(itemId);
|
||||
|
||||
// 刷新列表
|
||||
refreshData();
|
||||
} else {
|
||||
alert('保存失败: ' + updateResult.error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFolders() {
|
||||
const res = await fetch(`${API_BASE}/folders`);
|
||||
const data = await res.json();
|
||||
|
||||
Reference in New Issue
Block a user