Modal for editing items.

This commit is contained in:
Chris McCormick 2026-01-03 21:46:31 +08:00
parent c3e2f7e16a
commit 2ac5282248

View file

@ -75,7 +75,24 @@
font-family: Helvetica, Arial, sans-serif; font-family: Helvetica, Arial, sans-serif;
} }
input::placeholder { textarea {
appearance: none;
box-shadow: none;
box-sizing: inherit;
padding: .5rem .5rem;
width: 100%;
font-size: 1rem;
color: var(--fg);
background-color: var(--bg);
border: .1rem solid var(--fg);
border-radius: var(--radius);
font-family: monospace;
resize: vertical;
min-height: 150px;
}
input::placeholder,
textarea::placeholder {
color: var(--fg); color: var(--fg);
font-style: italic; font-style: italic;
opacity: 1; opacity: 1;
@ -247,7 +264,7 @@
padding: 0.75em; padding: 0.75em;
border-radius: var(--radius); border-radius: var(--radius);
border: .1rem solid var(--shade); border: .1rem solid var(--shade);
cursor: grab; cursor: pointer;
user-select: none; user-select: none;
word-wrap: break-word; word-wrap: break-word;
position: relative; position: relative;
@ -261,10 +278,6 @@
border-color: var(--fg); border-color: var(--fg);
} }
.card:active {
cursor: grabbing;
}
.card.completed { .card.completed {
opacity: 0.6; opacity: 0.6;
} }
@ -272,6 +285,7 @@
.card.dragging { .card.dragging {
opacity: 0.5; opacity: 0.5;
border: .1rem dashed var(--fg); border: .1rem dashed var(--fg);
cursor: grabbing;
} }
.card-text { .card-text {
@ -842,9 +856,15 @@ chmod 755 kbtd
<div id="card-detail-modal" class="modal"> <div id="card-detail-modal" class="modal">
<div class="modal-content"> <div class="modal-content">
<h3 id="card-detail-title">Card Details</h3> <div class="modal-section">
<div class="card-detail-content" id="card-detail-content"></div> <label for="card-detail-title-input">Title:</label>
<div class="modal-buttons"> <input type="text" id="card-detail-title-input">
</div>
<div class="modal-section">
<label for="card-detail-content-textarea">Details:</label>
<textarea id="card-detail-content-textarea" placeholder="Add details..."></textarea>
</div>
<div class="modal-buttons" id="card-detail-buttons">
<button id="card-detail-close-btn">Close</button> <button id="card-detail-close-btn">Close</button>
</div> </div>
</div> </div>
@ -899,6 +919,13 @@ chmod 755 kbtd
sourceCardIndex: null sourceCardIndex: null
}; };
let cardDetailModalState = {
card: null,
originalTitle: '',
originalContent: '',
baseIndent: ''
};
function h(tag, props = {}, ...children) { function h(tag, props = {}, ...children) {
const el = document.createElement(tag); const el = document.createElement(tag);
for (const [k, v] of Object.entries(props)) { for (const [k, v] of Object.entries(props)) {
@ -1043,6 +1070,65 @@ chmod 755 kbtd
} }
} }
function detectIndentInFile(text) {
const lines = text.split('\n');
let minIndent = null;
for (const line of lines) {
if (line.trim() === '') continue;
const match = line.match(/^(\s+)/);
if (match) {
const indent = match[1];
if (minIndent === null || indent.length < minIndent.length) {
minIndent = indent;
}
}
}
return minIndent || ' ';
}
function detectBaseIndent(nestedLines) {
if (nestedLines.length === 0) return '';
let minIndent = null;
for (const line of nestedLines) {
if (line.trim() === '') continue;
const match = line.match(/^(\s+)/);
if (match) {
const indent = match[1];
if (minIndent === null || indent.length < minIndent.length) {
minIndent = indent;
}
}
}
return minIndent || '';
}
function stripBaseIndent(nestedLines, baseIndent) {
if (!baseIndent) return nestedLines.join('\n');
return nestedLines.map(line => {
if (line.startsWith(baseIndent)) {
return line.substring(baseIndent.length);
}
return line;
}).join('\n');
}
function applyBaseIndent(text, baseIndent) {
if (!text.trim()) return [];
return text.split('\n').map(line => {
if (line.trim() === '') return '';
return baseIndent + line;
});
}
function parseMarkdown(text) { function parseMarkdown(text) {
const lines = text.split('\n'); const lines = text.split('\n');
const sections = []; const sections = [];
@ -1174,6 +1260,29 @@ chmod 755 kbtd
return null; return null;
} }
async function updateCardInFile(lineNumber, newTitle, newNestedContent, baseIndent) {
console.log('updateCardInFile:', lineNumber, newTitle, newNestedContent, baseIndent);
await modifyFileLines(lines => {
const cardInfo = findCardByLineNumber(lineNumber);
if (!cardInfo) {
console.error('Could not find card with line number', lineNumber);
return;
}
const card = cardInfo.card;
const oldNestedLineCount = card.nestedLines.length;
const checkbox = card.completed ? '[x]' : '[ ]';
const newCardLine = `- ${checkbox} ${newTitle}`;
const newNestedLines = applyBaseIndent(newNestedContent, baseIndent);
lines.splice(lineNumber, 1 + oldNestedLineCount, newCardLine, ...newNestedLines);
console.log('Updated card at line', lineNumber);
});
}
async function moveLineInFile(fromLine, toLine, targetColumnIndex, insertPosition) { async function moveLineInFile(fromLine, toLine, targetColumnIndex, insertPosition) {
console.log('moveLineInFile: from line', fromLine, 'to line', toLine, 'targetColumnIndex:', targetColumnIndex, 'insertPosition:', insertPosition); console.log('moveLineInFile: from line', fromLine, 'to line', toLine, 'targetColumnIndex:', targetColumnIndex, 'insertPosition:', insertPosition);
await modifyFileLines(lines => { await modifyFileLines(lines => {
@ -1547,15 +1656,95 @@ chmod 755 kbtd
}; };
} }
function openCardDetailModal(card) { async function openCardDetailModal(card) {
$('#card-detail-title').textContent = card.text; const file = await state.fileHandle.getFile();
const fullText = [card.originalLine, ...card.nestedLines].join('\n'); const text = await file.text();
$('#card-detail-content').textContent = fullText; const defaultIndent = detectIndentInFile(text);
const baseIndent = card.nestedLines.length > 0
? detectBaseIndent(card.nestedLines)
: defaultIndent;
const strippedContent = stripBaseIndent(card.nestedLines, baseIndent);
cardDetailModalState = {
card: card,
originalTitle: card.text,
originalContent: strippedContent,
baseIndent: baseIndent
};
$('#card-detail-title-input').value = card.text;
$('#card-detail-content-textarea').value = strippedContent;
updateCardDetailButtons();
$('#card-detail-modal').classList.add('active'); $('#card-detail-modal').classList.add('active');
} }
function updateCardDetailButtons() {
const titleInput = $('#card-detail-title-input');
const contentTextarea = $('#card-detail-content-textarea');
const buttonsContainer = $('#card-detail-buttons');
const titleChanged = titleInput.value !== cardDetailModalState.originalTitle;
const contentChanged = contentTextarea.value !== cardDetailModalState.originalContent;
const isModified = titleChanged || contentChanged;
if (isModified) {
buttonsContainer.replaceChildren(
h('button', {
id: 'card-detail-cancel-btn',
textContent: 'Cancel',
onclick: closeCardDetailModal
}),
h('button', {
id: 'card-detail-save-btn',
textContent: 'Save',
onclick: saveCardDetail
})
);
} else {
buttonsContainer.replaceChildren(
h('button', {
id: 'card-detail-close-btn',
textContent: 'Close',
onclick: closeCardDetailModal
})
);
}
}
async function saveCardDetail() {
const titleInput = $('#card-detail-title-input');
const contentTextarea = $('#card-detail-content-textarea');
const newTitle = titleInput.value.trim();
const newContent = contentTextarea.value;
if (!newTitle) {
alert('Title cannot be empty');
return;
}
await updateCardInFile(
cardDetailModalState.card.lineNumber,
newTitle,
newContent,
cardDetailModalState.baseIndent
);
closeCardDetailModal();
}
function closeCardDetailModal() { function closeCardDetailModal() {
$('#card-detail-modal').classList.remove('active'); $('#card-detail-modal').classList.remove('active');
cardDetailModalState = {
card: null,
originalTitle: '',
originalContent: '',
baseIndent: ''
};
} }
function openAboutModal() { function openAboutModal() {
@ -1634,16 +1823,12 @@ chmod 755 kbtd
ondragend: handleDragEnd, ondragend: handleDragEnd,
ondragenter: (e) => handleCardDragEnter(e, cardDiv, colIndex, cardIndex), ondragenter: (e) => handleCardDragEnter(e, cardDiv, colIndex, cardIndex),
onclick: (e) => { onclick: (e) => {
if (hasNestedContent && !e.target.closest('.card-menu-btn')) { if (!e.target.closest('.card-menu-btn')) {
openCardDetailModal(card); openCardDetailModal(card);
} }
} }
}); });
if (hasNestedContent) {
cardDiv.style.cursor = 'pointer';
}
const expandIndicator = hasNestedContent ? const expandIndicator = hasNestedContent ?
h('span', { h('span', {
className: 'card-expand-indicator', className: 'card-expand-indicator',
@ -1966,6 +2151,9 @@ chmod 755 kbtd
} }
}); });
$('#card-detail-title-input').addEventListener('input', updateCardDetailButtons);
$('#card-detail-content-textarea').addEventListener('input', updateCardDetailButtons);
async function openRecentProject(projectName) { async function openRecentProject(projectName) {
console.log('Opening recent project:', projectName); console.log('Opening recent project:', projectName);
state.filePath = projectName; state.filePath = projectName;
@ -2192,10 +2380,6 @@ chmod 755 kbtd
closeAboutModal(); closeAboutModal();
}; };
$('#card-detail-close-btn').onclick = () => {
closeCardDetailModal();
};
$('#change-file-btn').onclick = () => { $('#change-file-btn').onclick = () => {
showScreen('landing-screen'); showScreen('landing-screen');
$('#project-name-input').value = state.filePath || ''; $('#project-name-input').value = state.filePath || '';