Move card to different project modal.
This commit is contained in:
parent
df2d789d3f
commit
2b408658e6
1 changed files with 354 additions and 8 deletions
362
index.html
362
index.html
|
|
@ -178,6 +178,9 @@
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover {
|
.card:hover {
|
||||||
|
|
@ -198,19 +201,77 @@
|
||||||
border: .1rem dashed var(--fg);
|
border: .1rem dashed var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-checkbox {
|
.card-text {
|
||||||
float: right;
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu-btn {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--shade);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2em;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu-btn:hover {
|
||||||
|
color: var(--fg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 2em;
|
||||||
|
background: var(--bg);
|
||||||
|
border: .1rem solid var(--fg);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 0.5em 0;
|
||||||
|
min-width: 150px;
|
||||||
|
z-index: 100;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu-item {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--fg);
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu-item:hover {
|
||||||
|
background: var(--shade);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-menu-checkbox {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
border: .1rem solid var(--fg);
|
border: .1rem solid var(--fg);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
cursor: pointer;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
margin-left: 0.5em;
|
flex-shrink: 0;
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-checkbox.checked {
|
.card-menu-checkbox.checked {
|
||||||
background: var(--fg);
|
background: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,6 +432,77 @@
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: var(--bg);
|
||||||
|
border: .1rem solid var(--fg);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
padding: 2em;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-section {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-section label {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-select, .column-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
|
padding-right: 2.5em;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
border: .1rem solid var(--fg);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8"><path d="M1 1 L6 6 L11 1" stroke="%23ffe" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 0.75em center;
|
||||||
|
background-size: 12px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
|
@ -475,6 +607,24 @@ chmod 755 kbtd
|
||||||
</header>
|
</header>
|
||||||
<div id="board-container" class="board"></div>
|
<div id="board-container" class="board"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="move-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>Move to Project</h3>
|
||||||
|
<div class="modal-section">
|
||||||
|
<label for="target-project-select">Project:</label>
|
||||||
|
<select id="target-project-select" class="project-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="modal-section">
|
||||||
|
<label for="target-column-select">Column:</label>
|
||||||
|
<select id="target-column-select" class="column-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button id="move-cancel-btn">Cancel</button>
|
||||||
|
<button id="move-confirm-btn">Move</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -499,6 +649,13 @@ chmod 755 kbtd
|
||||||
currentDropTarget: null
|
currentDropTarget: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let moveModalState = {
|
||||||
|
cardLineNumber: null,
|
||||||
|
cardOriginalLine: null,
|
||||||
|
sourceColIndex: null,
|
||||||
|
sourceCardIndex: null
|
||||||
|
};
|
||||||
|
|
||||||
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)) {
|
||||||
|
|
@ -789,6 +946,55 @@ chmod 755 kbtd
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function moveLineBetweenSections(fromLine, targetSectionIndex, targetColumnIndex) {
|
||||||
|
console.log('moveLineBetweenSections: from line', fromLine, 'to section', targetSectionIndex, 'column', targetColumnIndex);
|
||||||
|
await modifyFileLines(lines => {
|
||||||
|
const [movedLine] = lines.splice(fromLine, 1);
|
||||||
|
|
||||||
|
const targetSection = state.sections[targetSectionIndex];
|
||||||
|
const targetColumn = targetSection.columns[targetColumnIndex];
|
||||||
|
|
||||||
|
let insertLine;
|
||||||
|
if (targetColumn.cards.length === 0) {
|
||||||
|
insertLine = targetColumn.headerLineNumber + 1;
|
||||||
|
|
||||||
|
if (fromLine < insertLine) {
|
||||||
|
insertLine--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertLine < lines.length && lines[insertLine].trim() === '') {
|
||||||
|
lines.splice(insertLine, 0, movedLine);
|
||||||
|
} else {
|
||||||
|
lines.splice(insertLine, 0, '', movedLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
const afterLine = insertLine + (lines[insertLine].trim() === '' ? 2 : 1);
|
||||||
|
if (afterLine >= lines.length || lines[afterLine].trim() !== '') {
|
||||||
|
lines.splice(afterLine, 0, '');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const lastCard = targetColumn.cards[targetColumn.cards.length - 1];
|
||||||
|
insertLine = lastCard.lineNumber + 1;
|
||||||
|
if (lastCard.nestedLines.length > 0) {
|
||||||
|
insertLine += lastCard.nestedLines.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromLine < insertLine) {
|
||||||
|
insertLine--;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.splice(insertLine, 0, movedLine);
|
||||||
|
|
||||||
|
const afterLine = insertLine + 1;
|
||||||
|
if (afterLine >= lines.length || lines[afterLine].trim() !== '') {
|
||||||
|
lines.splice(afterLine, 0, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Moved line to position', insertLine);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function createProject(projectName) {
|
async function createProject(projectName) {
|
||||||
console.log('Creating project:', projectName);
|
console.log('Creating project:', projectName);
|
||||||
await modifyFileLines(lines => {
|
await modifyFileLines(lines => {
|
||||||
|
|
@ -951,6 +1157,84 @@ chmod 755 kbtd
|
||||||
showScreen('section-screen');
|
showScreen('section-screen');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeAllCardMenus() {
|
||||||
|
$$('.card-menu').forEach(menu => menu.classList.remove('active'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMoveModal(cardLineNumber, cardOriginalLine, sourceColIndex, sourceCardIndex) {
|
||||||
|
moveModalState = {
|
||||||
|
cardLineNumber,
|
||||||
|
cardOriginalLine,
|
||||||
|
sourceColIndex,
|
||||||
|
sourceCardIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectSelect = $('#target-project-select');
|
||||||
|
projectSelect.replaceChildren(
|
||||||
|
...state.sections.map((section, index) =>
|
||||||
|
h('option', {
|
||||||
|
value: index,
|
||||||
|
textContent: section.name,
|
||||||
|
selected: index === state.currentSectionIndex
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
updateColumnSelect();
|
||||||
|
|
||||||
|
$('#move-modal').classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateColumnSelect() {
|
||||||
|
const projectSelect = $('#target-project-select');
|
||||||
|
const selectedProjectIndex = parseInt(projectSelect.value);
|
||||||
|
const selectedProject = state.sections[selectedProjectIndex];
|
||||||
|
|
||||||
|
const columnSelect = $('#target-column-select');
|
||||||
|
columnSelect.replaceChildren(
|
||||||
|
...selectedProject.columns.map((column, index) =>
|
||||||
|
h('option', {
|
||||||
|
value: index,
|
||||||
|
textContent: column.name
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMoveModal() {
|
||||||
|
$('#move-modal').classList.remove('active');
|
||||||
|
moveModalState = {
|
||||||
|
cardLineNumber: null,
|
||||||
|
cardOriginalLine: null,
|
||||||
|
sourceColIndex: null,
|
||||||
|
sourceCardIndex: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmMove() {
|
||||||
|
const targetSectionIndex = parseInt($('#target-project-select').value);
|
||||||
|
const targetColumnIndex = parseInt($('#target-column-select').value);
|
||||||
|
|
||||||
|
console.log('Moving card from section', state.currentSectionIndex, 'to section', targetSectionIndex, 'column', targetColumnIndex);
|
||||||
|
|
||||||
|
if (targetSectionIndex === state.currentSectionIndex) {
|
||||||
|
await moveLineInFile(
|
||||||
|
moveModalState.cardLineNumber,
|
||||||
|
0,
|
||||||
|
targetColumnIndex,
|
||||||
|
state.sections[targetSectionIndex].columns[targetColumnIndex].cards.length
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await moveLineBetweenSections(
|
||||||
|
moveModalState.cardLineNumber,
|
||||||
|
targetSectionIndex,
|
||||||
|
targetColumnIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMoveModal();
|
||||||
|
}
|
||||||
|
|
||||||
function renderBoard() {
|
function renderBoard() {
|
||||||
const section = state.sections[state.currentSectionIndex];
|
const section = state.sections[state.currentSectionIndex];
|
||||||
if (!section) return renderSectionPicker();
|
if (!section) return renderSectionPicker();
|
||||||
|
|
@ -995,10 +1279,34 @@ chmod 755 kbtd
|
||||||
ondragenter: (e) => handleCardDragEnter(e, cardDiv, colIndex, cardIndex)
|
ondragenter: (e) => handleCardDragEnter(e, cardDiv, colIndex, cardIndex)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cardText = h('span', {
|
||||||
|
className: 'card-text',
|
||||||
|
textContent: card.text
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuBtn = h('button', {
|
||||||
|
className: 'card-menu-btn',
|
||||||
|
textContent: '⋮',
|
||||||
|
onclick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const menu = cardDiv.querySelector('.card-menu');
|
||||||
|
const wasActive = menu.classList.contains('active');
|
||||||
|
closeAllCardMenus();
|
||||||
|
if (!wasActive) {
|
||||||
|
menu.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const checkbox = h('div', {
|
const checkbox = h('div', {
|
||||||
className: `card-checkbox ${card.completed ? 'checked' : ''}`,
|
className: `card-menu-checkbox ${card.completed ? 'checked' : ''}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const completeItem = h('button', {
|
||||||
|
className: 'card-menu-item',
|
||||||
onclick: async (e) => {
|
onclick: async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
closeAllCardMenus();
|
||||||
const lineNum = getDataInt(cardDiv, 'lineNumber');
|
const lineNum = getDataInt(cardDiv, 'lineNumber');
|
||||||
const originalLine = cardDiv.dataset.originalLine;
|
const originalLine = cardDiv.dataset.originalLine;
|
||||||
const newLine = originalLine.includes('- [x]')
|
const newLine = originalLine.includes('- [x]')
|
||||||
|
|
@ -1006,9 +1314,28 @@ chmod 755 kbtd
|
||||||
: originalLine.replace('- [ ]', '- [x]');
|
: originalLine.replace('- [ ]', '- [x]');
|
||||||
await updateLineInFile(lineNum, newLine);
|
await updateLineInFile(lineNum, newLine);
|
||||||
}
|
}
|
||||||
|
}, checkbox, h('span', { textContent: 'Complete' }));
|
||||||
|
|
||||||
|
const moveItem = h('button', {
|
||||||
|
className: 'card-menu-item',
|
||||||
|
textContent: 'Move to project...',
|
||||||
|
onclick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
closeAllCardMenus();
|
||||||
|
openMoveModal(
|
||||||
|
getDataInt(cardDiv, 'lineNumber'),
|
||||||
|
cardDiv.dataset.originalLine,
|
||||||
|
colIndex,
|
||||||
|
cardIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cardDiv.append(checkbox, h('span', { textContent: card.text }));
|
const menu = h('div', {
|
||||||
|
className: 'card-menu'
|
||||||
|
}, completeItem, moveItem);
|
||||||
|
|
||||||
|
cardDiv.append(cardText, menuBtn, menu);
|
||||||
|
|
||||||
cardElements.push(
|
cardElements.push(
|
||||||
cardDiv,
|
cardDiv,
|
||||||
|
|
@ -1132,6 +1459,7 @@ chmod 755 kbtd
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragStart(e, cardDiv) {
|
function handleDragStart(e, cardDiv) {
|
||||||
|
closeAllCardMenus();
|
||||||
dragState.source = {
|
dragState.source = {
|
||||||
lineNumber: getDataInt(cardDiv, 'lineNumber'),
|
lineNumber: getDataInt(cardDiv, 'lineNumber'),
|
||||||
colIndex: getDataInt(cardDiv, 'colIndex'),
|
colIndex: getDataInt(cardDiv, 'colIndex'),
|
||||||
|
|
@ -1253,6 +1581,12 @@ chmod 755 kbtd
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('.card-menu') && !e.target.closest('.card-menu-btn')) {
|
||||||
|
closeAllCardMenus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
insertInstallInstructions('landing-instructions');
|
insertInstallInstructions('landing-instructions');
|
||||||
insertInstallInstructions('section-instructions');
|
insertInstallInstructions('section-instructions');
|
||||||
|
|
@ -1375,6 +1709,18 @@ chmod 755 kbtd
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$('#target-project-select').onchange = () => {
|
||||||
|
updateColumnSelect();
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#move-cancel-btn').onclick = () => {
|
||||||
|
closeMoveModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
$('#move-confirm-btn').onclick = async () => {
|
||||||
|
await confirmMove();
|
||||||
|
};
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue