From 342e7946a8b4a44c1d9c4d86dca57c1b6fe4f9b4 Mon Sep 17 00:00:00 2001 From: GF <32237768+gf-mse@users.noreply.github.com> Date: Fri, 5 Aug 2022 20:01:49 +1000 Subject: [PATCH] added a simple merge operation for two boards (see stash/unstash/merge entries in the menu) --- nullboard.html | 276 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 274 insertions(+), 2 deletions(-) diff --git a/nullboard.html b/nullboard.html index 8644f1f..040139f 100644 --- a/nullboard.html +++ b/nullboard.html @@ -1598,8 +1598,9 @@ Auto-backup... - => Stash board - <= Unstash board + => Stash board + <= Unstash board + <= Merge stashed +
UI preferences... @@ -2800,6 +2801,262 @@

Auto-backup

// # try to add this at the end : // _dump('NB'); +// ----------------------------------------------------------------------- + +function mergedBoardTitles(left_title, right_title) { + + if ( left_title == right_title ) + return left_title; + + // else + return left_title + ' | ' + right_title ; +} + + +function mergedListTitles(left_title, right_title) { + + if ( left_title == right_title ) + return left_title; + + // else + return left_title + ' /\n/ ' + right_title ; +} + +function mergedNoteTexts(left_text, right_text) { + + if ( left_text == right_text ) + return left_title; + + // else + return left_text + '\n-----\n' + right_text ; +} + + +// ----------------------------------------------------------------------- + +// merges two board .lists, ignoring their .notes +// (sets an empty .notes[] array for each list) +// +// returns a new list with merged board list titles for same list ids +function mergeBoardListsShallow(left_lists, right_lists) { + var result = []; + + var indexLookup = {} ; // list id -> list + + for (var L of left_lists) { + // L = L.shallowClone(); + L = clone_list_shallow(L); + L.notes = []; + indexLookup[L.id] = L; + + result.push(L); + } + + for (var R of right_lists) { + // R = R.shallowClone(); + R = clone_list_shallow(R); + R.notes = []; + + L = indexLookup[R.id]; + if (L) { + L.title = mergedListTitles(L.title, R.title); + // the list object is already in the array, no push needed + } else { + result.push(R); + } + } + + return result; +} + + +// ----------------------------------------------------------------------- + +// +// (a) consider note_b "new" ; +// (b) keep the 'raw' or 'min' flag if once set ; +// (c) ignore existing old|marked|new flags and set them here +// +function mergeTwoNotesData( note_a, note_b ) { + + var result = clone_note_shallow(note_a); // shall keep the id + // aliases + var note = result, a = note_a, b = note_b; + + if ( ! (a.id === b.id ) ) { + _dbg(`mergeNoteData(): a.id=${a.id} != b.id=${b.id}`); + _dbg(`mergeNoteData(): a.text=${a.text}`); + _dbg(`mergeNoteData(): b.text=${b.text}`); + // merge anyway though + } + + if (a.text == b.text) { + + // result.text = a.text ; + // preserve 'marked' if the new state has it + result.marked = b.marked; + + } else { + result.text = mergedNoteTexts(a.text, b.text); + result.marked = true; + } + + result.raw = a.raw || b.raw; + result.min = a.min || b.min; + + result.old = false; + result.new = false; + + return result; +} + + +// ----------------------------------------------------------------------- + +// => FILLME: add a prerequisite check procedure to ensure that every list / note has an id + +// const NOTE_STATES = { 'SAME' : 'same', 'OLD' : 'old', 'NEW' : 'new', 'MERGED' : 'merged' }; + +function NoteData(note, list_id, list_pos, status) { + + this.note = note ; // note object + this.list_id = list_id ; + this.list_pos = list_pos ; // note index in the list + + // '.state' is obsolete, check note flags instead +/* + this.state = status ; // originally old|new <=> left|right, + // then old|same|merged|new + */ +} + + +// add board notes to a { note_id => note_data } lookup index +function boardToIndex(lookup_index, board, old_or_new) { + + _dbg(`boardToIndex(B=${board.id}.${board.revision}, ${old_or_new}): started`); + + var I = lookup_index || {} ; // a short alias + + for (var L of board.lists) { + // _dbg(`boardToIndex(L=${L.id}) => `); + + for (var i = 0; i < L.notes.length; ++i) { + _dbg(`in boardToIndex(L=${L.id}, n=${i})`); + + var note_to_add = L.notes[i]; + var updated, existing = I[note_to_add.id]; + + if (existing) { + _dbg(`boardToIndex(): merging n=${existing.note.id} and n=${note_to_add.id} (${old_or_new})`); + + // merge note texts if needed, update list id / list pos to latest ; + // nb: "merged/marked" flag shall be set or cleared internally by mergeTwoNotes() + updated = new NoteData(mergeTwoNotesData(existing.note, note_to_add), L.id, i); + } else { + _dbg(`boardToIndex(): adding n=${note_to_add.id} (${old_or_new})`); + + // updated = new NoteData(note_to_add.shallowClone(), L.id, i); + updated = new NoteData(clone_note_deep(note_to_add), L.id, i); + // everything that is not 'new' or 'right' is considered 'left' / 'old' ) + if ( old_or_new == 'right' || old_or_new == 'new' ) { + + // fix old/new state, ignore the rest ; in particular, leave old "marked" untouched + updated.note.new = true; + updated.note.old = false; + + } else { + updated.note.new = false; + updated.note.old = true; + } + } + + I[note_to_add.id] = updated ; + } + } + + // convenience + return I; +} + +// ----------------------------------------------------------------------- + + +// "right" is considered new and takes precedence in position +/* + * FILLME: describe + */ +function mergeBoards(left_board, right_board) { + + // var result = new Board(); + // # copy "everything shallow" (everything but .lists) + var new_board = clone_board_shallow(left_board); + new_board.lists = [] ; + // ------------------------------------------------------------------- + + // merge board titles + new_board.title = mergedBoardTitles(left_board.title, right_board.title); + // merge list headers + var board_lists = mergeBoardListsShallow(left_board.lists, right_board.lists); + new_board.lists = board_lists; + _dump(board_lists, 'mergeBoards(): board_lists'); + + // now let us merge the note sets + var note_index = boardToIndex({}, left_board, 'left'); + // merging ... + note_index = boardToIndex(note_index, right_board, 'right'); + _dump(note_index, 'mergeBoards(): note_index'); + + // now populate the new board with the results + var listIndexLookup = {} ; // list id -> list + for (var i in board_lists) { + // list id => list index + listIndexLookup[board_lists[i].id] = i; + } + + // populate lists + for (var key_id in note_index) { + var note_data = note_index[key_id]; + _dbg(`mergeBoards(${left_board.id}, ${right_board.id}): adding note ${note_data.note.id} to list ${note_data.list_id}`); + + // not using .list_pos here, but will use to sort .notes below ) + board_lists[ listIndexLookup[note_data.list_id] ].notes.push(note_data.note); + } + + // sort function : +1 for "a below b" # .sort() uses an (a > b) to sort ascending ; + // # in our case ascending is "below" + function cmp_below(note_a, note_b) { + + // since array::sort() takes two arguments only, we shall be using a closure ) + var a_data = note_index[note_a.id]; + var b_data = note_index[note_b.id]; + // we assume that both notes belong to the same list id ; + // in this case, we can simply compare note list positions, + // and put "new" notes on top in the case of conflict + + var result = a_data.list_pos - b_data.list_pos; + if (0 === result) { + result = ( (+ (note_a.old) ) - (+ (note_b.old)) ) + // a.old is true => it goes below + ( (+ (note_b.new) ) - (+ (note_a.new)) ) ; // b.new is true => it goes above + } + + // todo: alternatively, move deleted / old to the bottom and new to the top ) + + return result; + } + + // sort lists + for (var L of board_lists) { + L.notes.sort(cmp_below); + } + + // new_board.revision ++ ; + + // ------------------------------------------------------------------- + return new_board; +} + +