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;
|
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 || '';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue