checked off items

This commit is contained in:
slynn1324 2021-01-24 11:16:09 -06:00
parent b79827087b
commit da9d05597b
14 changed files with 221 additions and 79 deletions

View file

@ -6,3 +6,6 @@
.git
docker-run.sh
docker-build.sh
images
images*
*.db*

2
.gitignore vendored
View file

@ -1,6 +1,8 @@
images
images*
node_modules
tinypin.db
tinypin.db*
.DS_Store
chrome-extension.crx
chrome-extension.pem

10
TODO.md
View file

@ -1,9 +1,9 @@
# todo list
- update brick layout algo to be height aware, rather than simply add to columns in order
- check breakpoints for iOS
- add hidden flag to boards / control
- --update brick layout algo to be height aware, rather than simply add to columns in order--
- --check breakpoints for iOS--
- --add hidden flag to boards / control--
- add change password function
- add logged in user name to menu
- addpin.html remember last board
- --add logged in user name to menu--
- --addpin.html remember last board--

138
server.js
View file

@ -200,6 +200,7 @@ app.use ( async (req, res, next) => {
if ( !req.user ){
res.redirect("/login");
return;
}
if ( req.method == "GET" && req.originalUrl == "/logout" ){
@ -247,6 +248,10 @@ const ALREADY_EXISTS = {status: "error", error: "already exists"};
const SERVER_ERROR = {status: "error", error: "server error"};
app.get("/api/whoami", (req, res) => {
res.send({name: req.user.name});
});
// list boards
app.get("/api/boards", async (req, res) => {
try{
@ -290,7 +295,7 @@ app.get("/api/boards/:boardId", async (req, res) => {
// create board
app.post('/api/boards', (req, res) => {
try{
let result = db.prepare("INSERT INTO boards (name, userId, createDate) VALUES (@name, @userId, @createDate)").run({name: req.body.name, userId: req.user.id, createDate: new Date().toISOString()});
let result = db.prepare("INSERT INTO boards (name, userId, hidden, createDate) VALUES (@name, @userId, @hidden, @createDate)").run({name: req.body.name, userId: req.user.id, hidden: req.body.hidden, createDate: new Date().toISOString()});
let id = result.lastInsertRowid;
let board = db.prepare("SELECT * FROM boards WHERE userId = @userId and id = @boardId").get({userId: req.user.id, boardId: id});
board.titlePinId = 0;
@ -310,7 +315,7 @@ app.post('/api/boards', (req, res) => {
// update board
app.post("/api/boards/:boardId", (req, res) =>{
try{
let result = db.prepare("UPDATE boards SET name = @name WHERE userId = @userId and id = @boardId").run({name: req.body.name, userId: req.user.id, boardId: req.params.boardId});
let result = db.prepare("UPDATE boards SET name = @name, hidden = @hidden WHERE userId = @userId and id = @boardId").run({name: req.body.name, hidden: req.body.hidden, userId: req.user.id, boardId: req.params.boardId});
if ( result.changes == 1 ){
res.send(OK);
} else {
@ -498,65 +503,102 @@ function initDb(){
`).run();
let schemaVersion = db.prepare('select max(id) as id from migrations').get().id;
let isNewDb = false;
let createdBackup = false;
if ( !schemaVersion || schemaVersion < 1 ){
console.log(" running migration v1");
isNewDb = true;
db.prepare(`
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
key TEXT NOT NULL,
salt TEXT NOT NULL,
createDate TEXT
)
`).run();
db.transaction( () => {
db.prepare(`
CREATE TABLE properties (
key TEXT NOT NULL UNIQUE PRIMARY KEY,
value TEXT NOT NULL
db.prepare(`
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
key TEXT NOT NULL,
salt TEXT NOT NULL,
createDate TEXT
)
`).run();
`).run();
db.prepare(`
CREATE TABLE boards (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
userId INTEGER NOT NULL,
createDate TEXT,
FOREIGN KEY (userId) REFERENCES users(id)
db.prepare(`
CREATE TABLE properties (
key TEXT NOT NULL UNIQUE PRIMARY KEY,
value TEXT NOT NULL
)
`).run();
db.prepare(`
CREATE TABLE boards (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
userId INTEGER NOT NULL,
createDate TEXT,
FOREIGN KEY (userId) REFERENCES users(id)
)
`).run();
// autoincrement on pins so that pin ids are stable and are not reused.
// this allows for better caching of images
db.prepare(`
CREATE TABLE pins (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
boardId INTEGER NOT NULL,
imageUrl TEXT,
siteUrl TEXT,
description TEXT,
sortOrder INTEGER,
originalHeight INTEGER,
originalWidth INTEGER,
thumbnailHeight INTEGER,
thumbnailWidth INTEGER,
userId INTEGER NOT NULL,
createDate TEXT,
FOREIGN KEY (boardId) REFERENCES boards(id),
FOREIGN KEY (userId) REFERENCES users(id)
)
`).run();
`).run();
// autoincrement on pins so that pin ids are stable and are not reused.
// this allows for better caching of images
db.prepare(`
CREATE TABLE pins (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
boardId INTEGER NOT NULL,
imageUrl TEXT,
siteUrl TEXT,
description TEXT,
sortOrder INTEGER,
originalHeight INTEGER,
originalWidth INTEGER,
thumbnailHeight INTEGER,
thumbnailWidth INTEGER,
userId INTEGER NOT NULL,
createDate TEXT,
db.prepare("INSERT INTO properties (key, value) VALUES (@key, @value)").run({key: "cookieKey", value: crypto.randomBytes(32).toString('hex')});
db.prepare("INSERT INTO migrations (id, createDate) VALUES ( @id, @createDate )").run({id:1, createDate: new Date().toISOString()});
FOREIGN KEY (boardId) REFERENCES boards(id),
FOREIGN KEY (userId) REFERENCES users(id)
)
`).run();
schemaVersion = 1;
db.prepare("INSERT INTO properties (key, value) VALUES (@key, @value)").run({key: "cookieKey", value: crypto.randomBytes(32).toString('hex')});
db.prepare("INSERT INTO migrations (id, createDate) VALUES ( @id, @createDate )").run({id:1, createDate: new Date().toISOString()});
})();
}
schemaVersion = 1;
if ( schemaVersion < 2 ){
console.log(" running migration v2");
if ( !isNewDb ){
let backupPath = DB_PATH + ".backup-" + new Date().toISOString();
console.log(" backing up to: " + backupPath);
db.prepare(`
VACUUM INTO ?
`).run(backupPath);
createdBackup = true;
}
db.transaction( () => {
db.prepare(`
ALTER TABLE boards ADD COLUMN hidden INTEGER
`).run();
db.prepare(`
UPDATE boards SET hidden = 0
`).run();
db.prepare(`
INSERT INTO migrations (id, createDate) VALUES ( @id, @createDate )
`).run({id:2, createDate: new Date().toISOString()});
schemaVersion = 2;
})();
}
console.log(`database ready - schema version v${schemaVersion}`);

View file

@ -77,7 +77,9 @@ app.addSetter("load.boards", async (data) => {
data.initialized = true;
if ( data.boards && data.boards.length > 0 ){
if ( window.localStorage.addPinLastBoardId ){
data.addPinModal.boardId = window.localStorage.addPinLastBoardId;
} else if ( data.boards && data.boards.length > 0 ){
data.addPinModal.boardId = data.boards[0].id;
} else {
data.addPinModal.boardId = "new";
@ -87,7 +89,6 @@ app.addSetter("load.boards", async (data) => {
});
app.addSetter('addPinModal.updatePreview', (data) => {
console.log("update preview");
if ( data.addPinModal.imageUrl.startsWith("http") ){
( async() => {
let res = await fetch(data.addPinModal.imageUrl, {
@ -161,6 +162,7 @@ app.addSetter('addPinModal.save', async (data) => {
});
if ( res.status == 200 ){
window.localStorage.addPinLastBoardId = boardId;
window.close();
}
@ -228,7 +230,7 @@ const appComponent = new Reef("#app", {
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Add Pin</p>
<p class="modal-card-title">Add Pin ${data.addPinModal.boardId}</p>
<div id="loader" class="button is-text ${data.loading ? 'is-loading' : ''}"></div>
</header>
<section class="modal-card-body">

View file

@ -36,6 +36,15 @@ app.addSetter('load.board', async (data) => {
store.do("loader.hide");
});
app.addSetter('load.user', async (data) => {
store.do("loader.show");
let res = await fetch("/api/whoami");
data.user = await res.json();
store.do("loader.hide");
});
app.addSetter("hash.update", (data) => {
console.log("hash update");
data.hash = parseQueryString(window.location.hash.substr(1));
@ -59,6 +68,8 @@ let store = new Reef.Store({
initialized: false,
menuOpen: false,
loading: 0,
user: null,
showHiddenBoards: window.localStorage.showHiddenBoards == "true" || false,
boards: [],
board: null,
addPinModal: {
@ -82,7 +93,8 @@ let store = new Reef.Store({
},
editBoardModal: {
active: false,
name: ""
name: "",
hidden: 0
},
editPinModal: {
active: false,
@ -203,6 +215,7 @@ window.addEventListener('resize', (evt) => {
Reef.databind(appComponent);
store.do('load.user');
store.do('load.boards');
store.do('hash.update');

View file

@ -11,7 +11,7 @@
margin: 0 5px 10px 5px;
}
.brick img {
.brick img.thumb {
width: 100%;
height: auto;
display: block;
@ -40,7 +40,7 @@
width: 100%;
}
.board-brick a img{
.board-brick a img.thumb{
height: 200px;
object-fit: cover;
}

View file

@ -42,6 +42,8 @@ app.addComponent('aboutModal', (store) => { return new Reef("#aboutModal", {
<br />
&nbsp;missing icon &raquo; <a href="https://materialdesignicons.com/icon/dots-square">dots-square by Jeff Hilnbrand</a>
<br />
&nbsp;hidden icon &raquo; <a href="https://thenounproject.com/term/hidden/3543981/">hidden by vittorio longo from the Noun Project</a>
<br />
<br />
server
<br />

View file

@ -2,6 +2,8 @@ app.addSetter('addPinModal.open', (data) => {
if ( data.board ){
data.addPinModal.boardId = data.board.id;
} else if ( window.localStorage.addPinLastBoardId ){
data.addPinModal.boardId = window.localStorage.addPinLastBoardId;
} else if ( data.boards && data.boards.length > 0 ){
data.addPinModal.boardId = data.boards[0].id;
} else {
@ -91,6 +93,7 @@ app.addSetter('addPinModal.save', async (data) => {
newBoard.titlePinId = body.id;
}
window.localStorage.addPinLastBoardId = boardId;
store.do("addPinModal.close");
}

View file

@ -1,3 +1,8 @@
app.addSetter('brickwall.toggleHiddenBoards', (data) => {
data.showHiddenBoards = !data.showHiddenBoards;
window.localStorage.showHiddenBoards = data.showHiddenBoards;
});
app.addComponent('brickwall', (store) => { return new Reef('#brickwall', {
store: store,
@ -27,7 +32,8 @@ app.addComponent('brickwall', (store) => { return new Reef('#brickwall', {
// TODO: check these breakpoints for iPhone
let numberOfColumns = 1;
let width = el.offsetWidth;
let width = window.innerWidth; //el.offsetWidth;
// matching bulma breakpoints - https://bulma.io/documentation/overview/responsiveness/
if( width >= 1216 ){
numberOfColumns = 5;
@ -37,6 +43,11 @@ app.addComponent('brickwall', (store) => { return new Reef('#brickwall', {
numberOfColumns = 3;
} else if ( width > 320 ){
numberOfColumns = 2;
}
if ( !data.hash.board && width < 400 ){
numberOfColumns = 1;
}
function createBrickForBoard(board){
@ -46,29 +57,36 @@ app.addComponent('brickwall', (store) => { return new Reef('#brickwall', {
let boardImage = null;
if ( board.titlePinId > 0 ){
boardImage = `<img src="${getThumbnailImagePath(board.titlePinId)}" />`;
boardImage = `<img class="thumb" src="${getThumbnailImagePath(board.titlePinId)}" />`;
} else {
boardImage = `<div class="board-brick-missing-thumbnail"><img src="${missingThumbnailSrc}" /></div>`;
boardImage = `<div class="board-brick-missing-thumbnail"><img class="thumb" src="${missingThumbnailSrc}" /></div>`;
}
return /*html*/`
let hiddenBoardImage = '';
if ( board.hidden ){
hiddenBoardImage = '<img alt="(hidden)" style="width: 24px; height: 24px; vertical-align: middle;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDI0IDI0IiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPkFydGJvYXJkIDY8L3RpdGxlPjxwYXRoIGQ9Ik0xMiw2LjkyQTguNjUsOC42NSwwLDAsMSwxOS44NSwxMmE4LjYxLDguNjEsMCwwLDEtMTUuNywwQTguNjUsOC42NSwwLDAsMSwxMiw2LjkybTAtMkExMC42MiwxMC42MiwwLDAsMCwyLDEyYTEwLjYsMTAuNiwwLDAsMCwyMCwwQTEwLjYyLDEwLjYyLDAsMCwwLDEyLDQuOTJaIj48L3BhdGg+PHBhdGggZD0iTTE0LjIxLDExLjEyYTEuMzQsMS4zNCwwLDAsMS0xLjMzLTEuMzMsMS4zMSwxLjMxLDAsMCwxLC41Mi0xQTMuNDQsMy40NCwwLDAsMCwxMiw4LjQ2LDMuNTQsMy41NCwwLDEsMCwxNS41NCwxMmEzLjQ0LDMuNDQsMCwwLDAtLjI5LTEuNEExLjMxLDEuMzEsMCwwLDEsMTQuMjEsMTEuMTJaIj48L3BhdGg+PHBhdGggZD0iTTE5LDIwYTEsMSwwLDAsMS0uNzEtLjI5bC0xNC0xNEExLDEsMCwwLDEsNS43MSw0LjI5bDE0LDE0YTEsMSwwLDAsMSwwLDEuNDJBMSwxLDAsMCwxLDE5LDIwWiI+PC9wYXRoPjwvc3ZnPg==" />';
}
return { height: 1, template: /*html*/`
<div class="brick board-brick">
<a href="#board=${board.id}">
${boardImage}
<div class="board-brick-name">${board.name}</div>
<div class="board-brick-name">${board.name}
${hiddenBoardImage}
</div>
</a>
</div>
`;
`};
}
function createBrickForPin(board, pin){
return /*html*/`
return { height: pin.thumbnailHeight, template: /*html*/`
<div class="brick" >
<a data-pinid="${pin.id}" data-onclick="pinZoomModal.open">
<img src="${getThumbnailImagePath(pin.id)}" width="${pin.thumbnailWidth}" height="${pin.thumbnailHeight}" />
<img class="thumb" src="${getThumbnailImagePath(pin.id)}" width="${pin.thumbnailWidth}" height="${pin.thumbnailHeight}" />
</a>
</div>
`;
`};
}
// create the brick elements
@ -79,8 +97,10 @@ app.addComponent('brickwall', (store) => { return new Reef('#brickwall', {
bricks.push(createBrickForPin(data.board, data.board.pins[i]));
}
} else {
for ( let i = 0; i < data.boards.length; ++i ){
bricks.push(createBrickForBoard(data.boards[i]));
for ( let i = 0; i < data.boards.length; ++i ){
if ( data.showHiddenBoards || !data.boards[i].hidden ) {
bricks.push(createBrickForBoard(data.boards[i]));
}
}
}
@ -96,7 +116,19 @@ app.addComponent('brickwall', (store) => { return new Reef('#brickwall', {
// TODO: make this height aware
// sort bricks into columns
for ( let i = 0; i < bricks.length; ++i ){
columns[i % columns.length].bricks.push(bricks[i]);
// find shortest column
let shortestIndex = 0;
let shortestHeight = columns[0].height;
for ( let c = 1; c < columns.length; ++c ){
if ( columns[c].height < shortestHeight ){
shortestIndex = c;
shortestHeight = c.height;
}
}
columns[shortestIndex].bricks.push(bricks[i]);
columns[shortestIndex].height += bricks[i].height;
}
@ -107,7 +139,7 @@ app.addComponent('brickwall', (store) => { return new Reef('#brickwall', {
result += '<div class="brickwall-column">';
for ( let i = 0; i < columns[col].bricks.length; ++i ){
result += columns[col].bricks[i];
result += columns[col].bricks[i].template;
}
result += '</div>';

View file

@ -1,10 +1,12 @@
app.addSetter('editBoardModal.open', (data) => {
data.editBoardModal.name = data.board.name;
data.editBoardModal.hidden = data.board.hidden;
data.editBoardModal.active = true;
});
app.addSetter('editBoardModal.close', (data) => {
data.editBoardModal.name = "";
data.editBoardModal.hidden = 0;
data.editBoardModal.active = false;
});
@ -14,11 +16,17 @@ app.addSetter('editBoardModal.save', async (data) => {
let boardId = data.board.id;
let name = data.editBoardModal.name;
let hidden = data.editBoardModal.hidden;
let idx = getBoardIndexById(boardId);
if ( idx >= 0 ){
data.boards[idx].name = name;
data.boards[idx].hidden = hidden;
}
if ( data.board ){
data.board.name = name;
data.board.hidden = hidden;
}
let res = await fetch(`/api/boards/${boardId}`, {
@ -27,7 +35,8 @@ app.addSetter('editBoardModal.save', async (data) => {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name
name: name,
hidden: hidden
})
});
@ -105,6 +114,11 @@ app.addComponent('editBoardModal', (store) => { return new Reef("#editBoardModal
<input class="input" type="text" data-bind="editBoardModal.name" />
</div>
</div>
<label class="checkbox">
<input type="checkbox" data-bind="editBoardModal.hidden" value="1">
Hidden
</label>
</section>
<footer class="modal-card-foot">

View file

@ -20,11 +20,17 @@ app.addComponent('navbar', (store) => { return new Reef("#navbar", {
let boardName = "";
let hiddenBoardImage = '';
if ( data.board && data.board.hidden ){
hiddenBoardImage = '<img alt="(hidden)" style="width: 16px; height: 16px; vertical-align: middle; margin-top: 2px;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDI0IDI0IiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPkFydGJvYXJkIDY8L3RpdGxlPjxwYXRoIGQ9Ik0xMiw2LjkyQTguNjUsOC42NSwwLDAsMSwxOS44NSwxMmE4LjYxLDguNjEsMCwwLDEtMTUuNywwQTguNjUsOC42NSwwLDAsMSwxMiw2LjkybTAtMkExMC42MiwxMC42MiwwLDAsMCwyLDEyYTEwLjYsMTAuNiwwLDAsMCwyMCwwQTEwLjYyLDEwLjYyLDAsMCwwLDEyLDQuOTJaIj48L3BhdGg+PHBhdGggZD0iTTE0LjIxLDExLjEyYTEuMzQsMS4zNCwwLDAsMS0xLjMzLTEuMzMsMS4zMSwxLjMxLDAsMCwxLC41Mi0xQTMuNDQsMy40NCwwLDAsMCwxMiw4LjQ2LDMuNTQsMy41NCwwLDEsMCwxNS41NCwxMmEzLjQ0LDMuNDQsMCwwLDAtLjI5LTEuNEExLjMxLDEuMzEsMCwwLDEsMTQuMjEsMTEuMTJaIj48L3BhdGg+PHBhdGggZD0iTTE5LDIwYTEsMSwwLDAsMS0uNzEtLjI5bC0xNC0xNEExLDEsMCwwLDEsNS43MSw0LjI5bDE0LDE0YTEsMSwwLDAsMSwwLDEuNDJBMSwxLDAsMCwxLDE5LDIwWiI+PC9wYXRoPjwvc3ZnPg==" />';
}
if ( data.board ){
boardName = /*html*/`
<span class="navbar-item">
<span>${data.board.name} &nbsp;</span>
<a data-onclick="editBoardModal.open"><img style="margin-top: -4px;" alt="edit" width="16" height="16" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+NTE8L3RpdGxlPjxwYXRoIGQ9Ik04NC44NTAxMiw1MFY4MS43NDUxMkExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw3MS41OTUyNCw5NUgxOC4yNTQ5MUExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw1LDgxLjc0NTEyVjI4LjQwNTI4QTEzLjI3MDEyLDEzLjI3MDEyLDAsMCwxLDE4LjI1NDkxLDE1LjE1MDRINTBhMi41LDIuNSwwLDAsMSwwLDVIMTguMjU0OTFBOC4yNjQyMyw4LjI2NDIzLDAsMCwwLDEwLDI4LjQwNTI4VjgxLjc0NTEyQTguMjY0MjQsOC4yNjQyNCwwLDAsMCwxOC4yNTQ5MSw5MEg3MS41OTUyNGE4LjI2NDIzLDguMjY0MjMsMCwwLDAsOC4yNTQ4OC04LjI1NDg5VjUwYTIuNSwyLjUsMCwwLDEsNSwwWk04OS4xNDg0Niw2LjIzNzkyYTQuMjI2NjEsNC4yMjY2MSwwLDAsMC01Ljk3NzI5LDBsLTMzLjk2MjksMzMuOTYzTDU5Ljc5OTE2LDUwLjc5MTc2bDMzLjk2Mjg5LTMzLjk2M2E0LjIyNjUzLDQuMjI2NTMsMCwwLDAsMC01Ljk3NzIzWk00My42MjM4LDU4LjMxMjg3bDEzLjAwOTQtNC4zNTUxNkw0Ni4wNDIyNiw0My4zNjY4M2wtNC4zNTUxLDEzLjAwOTRBMS41MzAwNSwxLjUzMDA1LDAsMCwwLDQzLjYyMzgsNTguMzEyODdaIj48L3BhdGg+PC9zdmc+" /></a>
<span>${data.board.name}</span>
${hiddenBoardImage}
<a data-onclick="editBoardModal.open"><img style="margin-left: 5px; margin-top: -3px; vertical-align: middle;" alt="edit" width="16" height="16" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+NTE8L3RpdGxlPjxwYXRoIGQ9Ik04NC44NTAxMiw1MFY4MS43NDUxMkExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw3MS41OTUyNCw5NUgxOC4yNTQ5MUExMy4yNzAxMiwxMy4yNzAxMiwwLDAsMSw1LDgxLjc0NTEyVjI4LjQwNTI4QTEzLjI3MDEyLDEzLjI3MDEyLDAsMCwxLDE4LjI1NDkxLDE1LjE1MDRINTBhMi41LDIuNSwwLDAsMSwwLDVIMTguMjU0OTFBOC4yNjQyMyw4LjI2NDIzLDAsMCwwLDEwLDI4LjQwNTI4VjgxLjc0NTEyQTguMjY0MjQsOC4yNjQyNCwwLDAsMCwxOC4yNTQ5MSw5MEg3MS41OTUyNGE4LjI2NDIzLDguMjY0MjMsMCwwLDAsOC4yNTQ4OC04LjI1NDg5VjUwYTIuNSwyLjUsMCwwLDEsNSwwWk04OS4xNDg0Niw2LjIzNzkyYTQuMjI2NjEsNC4yMjY2MSwwLDAsMC01Ljk3NzI5LDBsLTMzLjk2MjksMzMuOTYzTDU5Ljc5OTE2LDUwLjc5MTc2bDMzLjk2Mjg5LTMzLjk2M2E0LjIyNjUzLDQuMjI2NTMsMCwwLDAsMC01Ljk3NzIzWk00My42MjM4LDU4LjMxMjg3bDEzLjAwOTQtNC4zNTUxNkw0Ni4wNDIyNiw0My4zNjY4M2wtNC4zNTUxLDEzLjAwOTRBMS41MzAwNSwxLjUzMDA1LDAsMCwwLDQzLjYyMzgsNTguMzEyODdaIj48L3BhdGg+PC9zdmc+" /></a>
</span>`;
} else if ( !data.hash.board ) {
boardName = /*html*/`<span class="navbar-item">Boards</span>`;
@ -41,6 +47,26 @@ app.addComponent('navbar', (store) => { return new Reef("#navbar", {
`;
}
let hiddenBoardsItem = '';
let hasHiddenBoards = false;
for ( let i = 0; i < data.boards.length; ++i ){
if ( data.boards[i].hidden == true ){
hasHiddenBoards = true;
break;
}
}
if (hasHiddenBoards) {
hiddenBoardsItem = `
<a class="navbar-item has-text-right" data-onclick="brickwall.toggleHiddenBoards">
<span>${data.showHiddenBoards ? 'hide hidden boards' : 'show hidden boards'}</span>
<img style="24px; height:24px;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDI0IDI0IiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPkFydGJvYXJkIDY8L3RpdGxlPjxwYXRoIGQ9Ik0xMiw2LjkyQTguNjUsOC42NSwwLDAsMSwxOS44NSwxMmE4LjYxLDguNjEsMCwwLDEtMTUuNywwQTguNjUsOC42NSwwLDAsMSwxMiw2LjkybTAtMkExMC42MiwxMC42MiwwLDAsMCwyLDEyYTEwLjYsMTAuNiwwLDAsMCwyMCwwQTEwLjYyLDEwLjYyLDAsMCwwLDEyLDQuOTJaIj48L3BhdGg+PHBhdGggZD0iTTE0LjIxLDExLjEyYTEuMzQsMS4zNCwwLDAsMS0xLjMzLTEuMzMsMS4zMSwxLjMxLDAsMCwxLC41Mi0xQTMuNDQsMy40NCwwLDAsMCwxMiw4LjQ2LDMuNTQsMy41NCwwLDEsMCwxNS41NCwxMmEzLjQ0LDMuNDQsMCwwLDAtLjI5LTEuNEExLjMxLDEuMzEsMCwwLDEsMTQuMjEsMTEuMTJaIj48L3BhdGg+PHBhdGggZD0iTTE5LDIwYTEsMSwwLDAsMS0uNzEtLjI5bC0xNC0xNEExLDEsMCwwLDEsNS43MSw0LjI5bDE0LDE0YTEsMSwwLDAsMSwwLDEuNDJBMSwxLDAsMCwxLDE5LDIwWiI+PC9wYXRoPjwvc3ZnPg==" />
</a>`;
}
return /*html*/`
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
@ -72,11 +98,13 @@ app.addComponent('navbar', (store) => { return new Reef("#navbar", {
<span>about tinypin</span>
<img alt="about" style="width:24px;height:24px;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+QXJ0Ym9hcmQgNzwvdGl0bGU+PHJlY3QgeD0iNDciIHk9IjQ5IiB3aWR0aD0iNiIgaGVpZ2h0PSIxNSI+PC9yZWN0PjxjaXJjbGUgY3g9IjUwIiBjeT0iMzkiIHI9IjMiPjwvY2lyY2xlPjxwYXRoIGQ9Ik01MCwyMEEzMCwzMCwwLDAsMCwyMCw1MGEyNy44OCwyNy44OCwwLDAsMCwzLjM1LDEzLjc0bC0zLDguMjFhNiw2LDAsMCwwLDcuNjksNy42OWw4LjIxLTNBMjcuODgsMjcuODgsMCwwLDAsNTAsODBhMzAsMzAsMCwxLDAsMC02MFptMCw1NGEyMS4wOCwyMS4wOCwwLDAsMS0xMy00TDI2LDc0bDQtMTFhMjEuMDgsMjEuMDgsMCwwLDEtNC0xM0EyNCwyNCwwLDEsMSw1MCw3NFoiPjwvcGF0aD48L3N2Zz4=" />
</a>
${hiddenBoardsItem}
${refreshItem}
<a class="navbar-item has-text-right" data-onclick="navbar.logout">
<span>log out</span>
<span>log out ${data.user ? data.user.name : ''}</span>
<img alt="log out" width="32" height="32" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+QXJ0Ym9hcmQgODQ8L3RpdGxlPjxnPjxwYXRoIGQ9Ik0yOCw4MkgzOFY3NEgyOGEyLDIsMCwwLDEtMi0yVjI4YTIsMiwwLDAsMSwyLTJIMzhWMThIMjhBMTAsMTAsMCwwLDAsMTgsMjhWNzJBMTAsMTAsMCwwLDAsMjgsODJaIj48L3BhdGg+PHBhdGggZD0iTTY2LDMyLjM0LDYwLjM0LDM4bDgsOEgzNHY4SDY4LjM0bC04LDhMNjYsNjcuNjYsODAuODMsNTIuODNhNCw0LDAsMCwwLDAtNS42NloiPjwvcGF0aD48L2c+PC9zdmc+" />
<a>

View file

@ -106,13 +106,12 @@ Reef.databind = function(reef){
let store = reef.store;
if ( !store ){
console.log("Databind only works when using a store.");
console.error("Databind only works when using a store.");
return;
}
// bind all elements on the page that have a data-bind item
const bindData = debounce(() => {
console.log("bindData");
let elems = el.querySelectorAll("[data-bind]");
for ( let i = 0; i < elems.length; ++i ){
@ -135,7 +134,6 @@ Reef.databind = function(reef){
// multiple selects need special handling
if ( elem.tagName == "SELECT" && elem.matches("[multiple]") ){
console.log("multiple");
let options = elem.querySelectorAll("option");
for ( let i = 0; i < options.length; ++i ){
if ( val.indexOf(options[i].value) > -1 ){

View file

@ -56,6 +56,9 @@
</div>
<script>
// clear previously stored values
window.localStorage.clear();
if ( window.location.hash == "#nope" ){
document.getElementById("nope").innerText = "nope.";
}