, e: JQuery.DragOverEvent) {
const originalEvent = e.originalEvent as DragEvent;
const mouseY = originalEvent.clientY;
const columnRect = $columnEl[0].getBoundingClientRect();
const relativeY = mouseY - columnRect.top;
// Find existing drop indicator or create one
let $dropIndicator = $columnEl.find(".board-drop-indicator");
if ($dropIndicator.length === 0) {
$dropIndicator = $("").addClass("board-drop-indicator");
$columnEl.append($dropIndicator);
}
// Find the best position to insert the note
const $notes = this.draggedNoteElement ?
$columnEl.find(".board-note").not(this.draggedNoteElement) :
$columnEl.find(".board-note");
let insertAfterElement: HTMLElement | null = null;
$notes.each((_, noteEl) => {
const noteRect = noteEl.getBoundingClientRect();
const noteMiddle = noteRect.top + noteRect.height / 2 - columnRect.top;
if (relativeY > noteMiddle) {
insertAfterElement = noteEl;
}
});
// Position the drop indicator
if (insertAfterElement) {
$(insertAfterElement).after($dropIndicator);
} else {
// Insert at the beginning (after the header)
const $header = $columnEl.find("h3");
$header.after($dropIndicator);
}
$dropIndicator.addClass("show");
}
private createTitleStructure(title: string): { $titleText: JQuery; $editIcon: JQuery } {
const $titleText = $("").text(title);
const $editIcon = $("")
.addClass("edit-icon icon bx bx-edit-alt")
.attr("title", "Click to edit column title");
return { $titleText, $editIcon };
}
private setupColumnTitleEdit($titleEl: JQuery, columnValue: string, columnItems: { branch: any; note: any; }[]) {
$titleEl.on("click", (e) => {
e.stopPropagation();
this.startEditingColumnTitle($titleEl, columnValue, columnItems);
});
}
private startEditingColumnTitle($titleEl: JQuery, columnValue: string, columnItems: { branch: any; note: any; }[]) {
if ($titleEl.hasClass("editing")) {
return; // Already editing
}
const $titleText = $titleEl.find("span").first();
const currentTitle = $titleText.text();
$titleEl.addClass("editing");
const $input = $("")
.attr("type", "text")
.val(currentTitle)
.attr("placeholder", "Column title");
$titleEl.empty().append($input);
$input.focus().select();
const finishEdit = async (save: boolean = true) => {
if (!$titleEl.hasClass("editing")) {
return; // Already finished
}
$titleEl.removeClass("editing");
let finalTitle = currentTitle;
if (save) {
const newTitle = $input.val() as string;
if (newTitle.trim() && newTitle !== currentTitle) {
await this.renameColumn(columnValue, newTitle.trim(), columnItems);
finalTitle = newTitle.trim();
}
}
// Recreate the title structure
const { $titleText, $editIcon } = this.createTitleStructure(finalTitle);
$titleEl.empty().append($titleText, $editIcon);
};
$input.on("blur", () => finishEdit(true));
$input.on("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
finishEdit(true);
} else if (e.key === "Escape") {
e.preventDefault();
finishEdit(false);
}
});
}
private async renameColumn(oldValue: string, newValue: string, columnItems: { branch: any; note: any; }[]) {
try {
// Get all note IDs in this column
const noteIds = columnItems.map(item => item.note.noteId);
// Use the API to rename the column (update all notes)
await this.api?.renameColumn(oldValue, newValue, noteIds);
// Refresh the board to reflect the changes
await this.renderList();
} catch (error) {
console.error("Failed to rename column:", error);
}
}
private async createNewItem(column: string) {
try {
// Get the parent note path
const parentNotePath = this.parentNote.noteId;
// Create a new note as a child of the parent note
const { note: newNote } = await noteCreateService.createNote(parentNotePath, {
activate: false
});
if (newNote) {
// Set the status label to place it in the correct column
await attributeService.setLabel(newNote.noteId, "status", column);
// Refresh the board to show the new item
await this.renderList();
// Optionally, open the new note for editing
appContext.triggerCommand("openInPopup", { noteIdOrPath: newNote.noteId });
}
} catch (error) {
console.error("Failed to create new item:", error);
}
}
private startCreatingNewColumn($addColumnEl: JQuery) {
if ($addColumnEl.hasClass("editing")) {
return; // Already editing
}
$addColumnEl.addClass("editing");
const $input = $("")
.attr("type", "text")
.attr("placeholder", "Enter column name...")
.css({
background: "var(--main-background-color)",
border: "1px solid var(--main-text-color)",
borderRadius: "4px",
padding: "0.5em",
color: "var(--main-text-color)",
fontFamily: "inherit",
fontSize: "inherit",
width: "100%",
textAlign: "center"
});
$addColumnEl.empty().append($input);
$input.focus();
const finishEdit = async (save: boolean = true) => {
if (!$addColumnEl.hasClass("editing")) {
return; // Already finished
}
$addColumnEl.removeClass("editing");
if (save) {
const columnName = $input.val() as string;
if (columnName.trim()) {
await this.createNewColumn(columnName.trim());
}
}
// Restore the add button
$addColumnEl.html('Add Column');
};
$input.on("blur", () => finishEdit(true));
$input.on("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
finishEdit(true);
} else if (e.key === "Escape") {
e.preventDefault();
finishEdit(false);
}
});
}
private async createNewColumn(columnName: string) {
try {
// Check if column already exists
if (this.api?.columns.includes(columnName)) {
console.warn("A column with this name already exists.");
return;
}
// Create the new column
await this.api?.createColumn(columnName);
// Refresh the board to show the new column
await this.renderList();
} catch (error) {
console.error("Failed to create new column:", error);
}
}
async onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
// React to changes in "status" attribute for notes in this board
if (loadResults.getAttributeRows().some(attr => attr.name === "status" && this.noteIds.includes(attr.noteId!))) {
return true;
}
// React to changes in note title.
if (loadResults.getNoteIds().some(noteId => this.noteIds.includes(noteId))) {
return true;
}
// React to changes in branches for subchildren (e.g., moved, added, or removed notes)
if (loadResults.getBranchRows().some(branch => this.noteIds.includes(branch.noteId!))) {
return true;
}
// React to attachment change.
if (loadResults.getAttachmentRows().some(att => att.ownerId === this.parentNote.noteId && att.title === "board.json")) {
return true;
}
return false;
}
private onSave() {
this.viewStorage.store(this.persistentData);
}
}