mirror of
https://github.com/apankrat/nullboard.git
synced 2025-08-07 21:56:34 +02:00
+ support for importing multiple boards in one go
This commit is contained in:
210
nullboard.html
210
nullboard.html
@@ -1222,18 +1222,18 @@
|
|||||||
|
|
||||||
function AppConfig()
|
function AppConfig()
|
||||||
{
|
{
|
||||||
this.format = NB.dataVersion;
|
this.format = NB.confVersion;
|
||||||
this.max_undo = 50; // board revisions to keep
|
this.max_undo = 50; // board revisions to keep
|
||||||
this.theme = null; // default or 'dark'
|
this.theme = null; // default or 'dark'
|
||||||
this.fsize = null; // default or 'z1'
|
this.fsize = null; // default or 'z1'
|
||||||
this.board = null; // active board
|
this.board = null; // active board
|
||||||
}
|
}
|
||||||
|
|
||||||
function BoardMeta()
|
function BoardMeta()
|
||||||
{
|
{
|
||||||
this.title = '';
|
this.title = '';
|
||||||
this.current = 1; // revision
|
this.current = 1; // revision
|
||||||
this.history = [ ]; // revision IDs
|
this.history = [ ]; // revision IDs
|
||||||
}
|
}
|
||||||
|
|
||||||
class Storage
|
class Storage
|
||||||
@@ -1295,46 +1295,52 @@
|
|||||||
var meta = this.boardIndex.get(board.id);
|
var meta = this.boardIndex.get(board.id);
|
||||||
var ok_data, ok_meta;
|
var ok_data, ok_meta;
|
||||||
|
|
||||||
|
delete board.history; // remove temporarily
|
||||||
|
|
||||||
if (! meta)
|
if (! meta)
|
||||||
{
|
{
|
||||||
|
board.revision = 1;
|
||||||
|
|
||||||
|
ok_data = this.setJson('board.' + board.id + '.' + board.revision, board);
|
||||||
|
|
||||||
meta = new BoardMeta();
|
meta = new BoardMeta();
|
||||||
meta.history = [ 0 ];
|
meta.title = board.title || '(Untitled board)';
|
||||||
meta.current = 0;
|
meta.current = board.revision;
|
||||||
|
meta.history = [ board.revision ];
|
||||||
|
|
||||||
this.boardIndex.set(board.id, meta);
|
this.boardIndex.set(board.id, meta);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var rev_old = board.revision;
|
|
||||||
var rev_new = meta.history[0] + 1;
|
|
||||||
|
|
||||||
board.revision = rev_new;
|
|
||||||
delete board.history; // remove temporarily
|
|
||||||
|
|
||||||
ok_data = this.setJson('board.' + board.id + '.' + board.revision, board);
|
|
||||||
|
|
||||||
// update meta
|
|
||||||
|
|
||||||
meta.title = board.title || '(Untitled board)';
|
|
||||||
meta.current = board.revision;
|
|
||||||
|
|
||||||
// trim revisions skipped over with undo, also cap revision count
|
|
||||||
|
|
||||||
var keep = [ rev_new ];
|
|
||||||
|
|
||||||
for (var rev of meta.history)
|
|
||||||
{
|
{
|
||||||
if ( ! rev || (rev_old < rev && rev < rev_new) || (keep.length >= this.conf.max_undo) )
|
var rev_old = board.revision;
|
||||||
{
|
var rev_new = meta.history[0] + 1;
|
||||||
this.delItem('board.' + board.id + '.' + rev);
|
|
||||||
console.log( `Deleted revision ${rev} of ${board.id} (${board.title})` );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
keep.push(rev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
meta.history = keep;
|
board.revision = rev_new;
|
||||||
|
|
||||||
|
ok_data = this.setJson('board.' + board.id + '.' + board.revision, board);
|
||||||
|
|
||||||
|
meta.title = board.title || '(Untitled board)';
|
||||||
|
meta.current = board.revision;
|
||||||
|
|
||||||
|
// trim revisions skipped over with undo and cap the revision count
|
||||||
|
|
||||||
|
var rebuild = [ board.revision ];
|
||||||
|
|
||||||
|
for (var rev of meta.history)
|
||||||
|
{
|
||||||
|
if ( (rev_old < rev && rev < rev_new) || (keep.length >= this.conf.max_undo) )
|
||||||
|
{
|
||||||
|
this.delItem('board.' + board.id + '.' + rev);
|
||||||
|
console.log( `Deleted revision ${rev} of ${board.id} (${board.title})` );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rebuild.push(rev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.history = rebuild;
|
||||||
|
}
|
||||||
|
|
||||||
ok_meta = this.setJson('board.' + board.id + '.meta', meta);
|
ok_meta = this.setJson('board.' + board.id + '.meta', meta);
|
||||||
|
|
||||||
@@ -1361,10 +1367,10 @@
|
|||||||
if (! board)
|
if (! board)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (board.format != NB.dataVersion)
|
if (board.format != NB.blobVersion)
|
||||||
{
|
{
|
||||||
console.log('Board ' + board_id + '/' + revision + ' format is unsupported');
|
console.log('Board ' + board_id + '/' + revision + ' format is unsupported');
|
||||||
console.log('Have [' + board.format + '], need [' + NB.dataVersion);
|
console.log('Have [' + board.format + '], need [' + NB.blobVersion);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1481,6 +1487,8 @@
|
|||||||
|
|
||||||
class Storage_Local extends Storage
|
class Storage_Local extends Storage
|
||||||
{
|
{
|
||||||
|
type = 'LocalStorage';
|
||||||
|
|
||||||
getItem(name)
|
getItem(name)
|
||||||
{
|
{
|
||||||
return localStorage.getItem('nullboard.' + name);
|
return localStorage.getItem('nullboard.' + name);
|
||||||
@@ -1504,15 +1512,16 @@
|
|||||||
|
|
||||||
this.conf = new AppConfig();
|
this.conf = new AppConfig();
|
||||||
|
|
||||||
|
if (conf && (conf.format != NB.confVersion))
|
||||||
|
{
|
||||||
|
if (! confirm('Preferences are stored in an unsupported format. Reset them?'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
conf = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (conf)
|
if (conf)
|
||||||
{
|
{
|
||||||
if (conf.format != NB.dataVersion)
|
|
||||||
{
|
|
||||||
alert('Saved data is from a different version of NB');
|
|
||||||
this.conf = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1587,8 +1596,6 @@
|
|||||||
|
|
||||||
this.conf = new AppConfig();
|
this.conf = new AppConfig();
|
||||||
this.boardIndex = new Map();
|
this.boardIndex = new Map();
|
||||||
|
|
||||||
this.type = 'LocalStorage';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1676,7 +1683,7 @@
|
|||||||
|
|
||||||
function Board(title)
|
function Board(title)
|
||||||
{
|
{
|
||||||
this.format = NB.dataVersion;
|
this.format = NB.blobVersion;
|
||||||
this.id = +new Date();
|
this.id = +new Date();
|
||||||
this.revision = 0;
|
this.revision = 0;
|
||||||
this.title = title || '';
|
this.title = title || '';
|
||||||
@@ -1976,15 +1983,17 @@
|
|||||||
/*
|
/*
|
||||||
* poor man's error handling -- $fixme
|
* poor man's error handling -- $fixme
|
||||||
*/
|
*/
|
||||||
|
var easyMartina = false;
|
||||||
|
|
||||||
window.onerror = function(message, file, line, col, e){
|
window.onerror = function(message, file, line, col, e){
|
||||||
var cb1;
|
var cb1;
|
||||||
alert("Error occurred: " + e.message);
|
if (! easyMartina) alert("Error occurred: " + e.message);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("error", function(e) {
|
window.addEventListener("error", function(e) {
|
||||||
var cb2;
|
var cb2;
|
||||||
alert("Error occurred: " + e.error.message);
|
if (! easyMartina) alert("Error occurred: " + e.error.message);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2054,56 +2063,85 @@
|
|||||||
NB.storage.nukeBoard(NB.board.id, null);
|
NB.storage.nukeBoard(NB.board.id, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkBoard(foo)
|
||||||
|
{
|
||||||
|
var props = [ 'format', 'id', 'revision', 'title', 'lists' ];
|
||||||
|
|
||||||
|
for (var i=0; i<props.length; i++)
|
||||||
|
if (! foo.hasOwnProperty(props[i]))
|
||||||
|
return "Required board properties are missing.";
|
||||||
|
|
||||||
|
if (! foo.id || ! foo.revision || ! Array.isArray(foo.lists))
|
||||||
|
return "Required board properties are empty.";
|
||||||
|
|
||||||
|
if (foo.format != NB.blobVersion)
|
||||||
|
return `Unsupported blob format "${board.format}", expecting "${NB.blobVersion}".`;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function importBoard(blob)
|
function importBoard(blob)
|
||||||
{
|
{
|
||||||
var board;
|
var data;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
board = JSON.parse(blob);
|
data = JSON.parse(blob);
|
||||||
}
|
}
|
||||||
catch (x)
|
catch (x)
|
||||||
{
|
{
|
||||||
alert('The file appears to be malformed');
|
alert('File is not in a valid JSON format.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof board.format === 'undefined' || ! board.format ||
|
if (! Array.isArray(data))
|
||||||
typeof board.revision === 'undefined' || ! board.revision)
|
data = [ data ];
|
||||||
{
|
|
||||||
alert("The file doesn't appear to be a Nullboard export");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (board.format != NB.dataVersion &&
|
|
||||||
board.format != 20190412)
|
|
||||||
{
|
|
||||||
console.log( `Unsupported file format - ${board.format}` );
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! confirm( `Import board called "${board.title}", ID ${board.id}, revision ${board.revision} ?`))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var index = NB.storage.getBoardIndex();
|
var index = NB.storage.getBoardIndex();
|
||||||
|
var msg, one, all = '';
|
||||||
|
|
||||||
if (index.has(board.id))
|
for (var i=0; i<data.length; i++)
|
||||||
{
|
{
|
||||||
if (! confirm("There is an existing board with the same ID. Import under a new ID ?"))
|
var board = data[i];
|
||||||
|
|
||||||
|
var whoops = checkBoard(board);
|
||||||
|
if (whoops)
|
||||||
|
{
|
||||||
|
alert(whoops);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
board.id = +new Date();
|
var title = board.title || '(untitled board)';
|
||||||
|
one = `"${title}", ID ${board.id}, revision ${board.revision}`;
|
||||||
|
all += ` ID ${board.id}, revision ${board.revision} - "${title}" \n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
board.revision--; // save will ++ it back
|
if (data.length == 1) msg = `Import a board called ${one} ?`;
|
||||||
|
else msg = `About to import the following boards:\n\n${all}\nProceed?`;
|
||||||
|
|
||||||
if (! NB.storage.saveBoard(board))
|
if (! confirm(msg))
|
||||||
{
|
|
||||||
alert("Failed to save the board. Import failed.");
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
for (var i=0; i<data.length; i++)
|
||||||
|
{
|
||||||
|
var board = data[i];
|
||||||
|
|
||||||
|
if (index.has(board.id))
|
||||||
|
{
|
||||||
|
console.log(`Import: board ${board.id} (${board.title}) will be assigned new ID`);
|
||||||
|
board.id = +new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
board.revision--; // save will ++ it back
|
||||||
|
|
||||||
|
if (! NB.storage.saveBoard(board)) // this updates 'index'
|
||||||
|
{
|
||||||
|
alert(`Failed to save board ${board.id}. Import failed.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openBoard(board.id);
|
openBoard(data[0].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -2452,7 +2490,7 @@
|
|||||||
if (NB.board)
|
if (NB.board)
|
||||||
{
|
{
|
||||||
title = NB.board.title;
|
title = NB.board.title;
|
||||||
title = 'NB - ' + (title || '(unnamed board)');
|
title = 'NB - ' + (title || '(untitled board)');
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = title;
|
document.title = title;
|
||||||
@@ -2778,8 +2816,8 @@
|
|||||||
var NB =
|
var NB =
|
||||||
{
|
{
|
||||||
codeVersion: 20210330,
|
codeVersion: 20210330,
|
||||||
dataVersion: 20190412,
|
blobVersion: 20190412, // blobs compatible
|
||||||
board: null
|
board: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
NB.storage = new Storage_Local();
|
NB.storage = new Storage_Local();
|
||||||
@@ -3125,10 +3163,12 @@
|
|||||||
/*
|
/*
|
||||||
* the init()
|
* the init()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (! NB.storage.open())
|
if (! NB.storage.open())
|
||||||
{
|
{
|
||||||
alert( `Failed to open storage (of ${NB.storage.type} type)` );
|
alert("Failed to load minimal required data from the storage");
|
||||||
exit;
|
easyMartina = true;
|
||||||
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
var boards = NB.storage.getBoardIndex();
|
var boards = NB.storage.getBoardIndex();
|
||||||
|
Reference in New Issue
Block a user