Modal for editing items.
This commit is contained in:
parent
c3e2f7e16a
commit
2ac5282248
1 changed files with 206 additions and 22 deletions
228
index.html
228
index.html
|
|
@ -75,7 +75,24 @@
|
|||
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);
|
||||
font-style: italic;
|
||||
opacity: 1;
|
||||
|
|
@ -247,7 +264,7 @@
|
|||
padding: 0.75em;
|
||||
border-radius: var(--radius);
|
||||
border: .1rem solid var(--shade);
|
||||
cursor: grab;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
|
|
@ -261,10 +278,6 @@
|
|||
border-color: var(--fg);
|
||||
}
|
||||
|
||||
.card:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.card.completed {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
|
@ -272,6 +285,7 @@
|
|||
.card.dragging {
|
||||
opacity: 0.5;
|
||||
border: .1rem dashed var(--fg);
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
|
|
@ -842,9 +856,15 @@ chmod 755 kbtd
|
|||
|
||||
<div id="card-detail-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h3 id="card-detail-title">Card Details</h3>
|
||||
<div class="card-detail-content" id="card-detail-content"></div>
|
||||
<div class="modal-buttons">
|
||||
<div class="modal-section">
|
||||
<label for="card-detail-title-input">Title:</label>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -899,6 +919,13 @@ chmod 755 kbtd
|
|||
sourceCardIndex: null
|
||||
};
|
||||
|
||||
let cardDetailModalState = {
|
||||
card: null,
|
||||
originalTitle: '',
|
||||
originalContent: '',
|
||||
baseIndent: ''
|
||||
};
|
||||
|
||||
function h(tag, props = {}, ...children) {
|
||||
const el = document.createElement(tag);
|
||||
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) {
|
||||
const lines = text.split('\n');
|
||||
const sections = [];
|
||||
|
|
@ -1174,6 +1260,29 @@ chmod 755 kbtd
|
|||
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) {
|
||||
console.log('moveLineInFile: from line', fromLine, 'to line', toLine, 'targetColumnIndex:', targetColumnIndex, 'insertPosition:', insertPosition);
|
||||
await modifyFileLines(lines => {
|
||||
|
|
@ -1547,15 +1656,95 @@ chmod 755 kbtd
|
|||
};
|
||||
}
|
||||
|
||||
function openCardDetailModal(card) {
|
||||
$('#card-detail-title').textContent = card.text;
|
||||
const fullText = [card.originalLine, ...card.nestedLines].join('\n');
|
||||
$('#card-detail-content').textContent = fullText;
|
||||
async function openCardDetailModal(card) {
|
||||
const file = await state.fileHandle.getFile();
|
||||
const text = await file.text();
|
||||
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');
|
||||
}
|
||||
|
||||
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() {
|
||||
$('#card-detail-modal').classList.remove('active');
|
||||
cardDetailModalState = {
|
||||
card: null,
|
||||
originalTitle: '',
|
||||
originalContent: '',
|
||||
baseIndent: ''
|
||||
};
|
||||
}
|
||||
|
||||
function openAboutModal() {
|
||||
|
|
@ -1634,16 +1823,12 @@ chmod 755 kbtd
|
|||
ondragend: handleDragEnd,
|
||||
ondragenter: (e) => handleCardDragEnter(e, cardDiv, colIndex, cardIndex),
|
||||
onclick: (e) => {
|
||||
if (hasNestedContent && !e.target.closest('.card-menu-btn')) {
|
||||
if (!e.target.closest('.card-menu-btn')) {
|
||||
openCardDetailModal(card);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hasNestedContent) {
|
||||
cardDiv.style.cursor = 'pointer';
|
||||
}
|
||||
|
||||
const expandIndicator = hasNestedContent ?
|
||||
h('span', {
|
||||
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) {
|
||||
console.log('Opening recent project:', projectName);
|
||||
state.filePath = projectName;
|
||||
|
|
@ -2192,10 +2380,6 @@ chmod 755 kbtd
|
|||
closeAboutModal();
|
||||
};
|
||||
|
||||
$('#card-detail-close-btn').onclick = () => {
|
||||
closeCardDetailModal();
|
||||
};
|
||||
|
||||
$('#change-file-btn').onclick = () => {
|
||||
showScreen('landing-screen');
|
||||
$('#project-name-input').value = state.filePath || '';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue