nullboard/nullboard.html

3176 lines
61 KiB
HTML
Raw Normal View History

2019-05-28 12:09:54 +02:00
<!doctype html>
<html>
<head>
<!--
2019-05-28 12:09:54 +02:00
+-------------------------------------------------------------+
2019-05-28 12:42:04 +02:00
| |
| Nullboard, a minimalist kanban board |
| https://nullboard.io |
| |
+-------------------------------------------------------------+
2019-05-28 12:09:54 +02:00
LICENSE
The 2-clause BSD license with the Commons Clause condition.
IN SHORT
You can use, change and distribute the software free of charge
provided that you do not sell it or make money off it in
certain less direct ways as specified below. When distributed,
the software must include a complete copy of this license.
IN FULL
The Software is provided to you by the Licensor under the
License, as defined below, subject to the following condition.
Without limiting other conditions in the License, the grant of
rights under the License will not include, and the License does
not grant to you, the right to Sell the Software.
For purposes of the foregoing, "Sell" means practicing any or
all of the rights granted to you under the License to provide
to third parties, for a fee or other consideration (including
without limitation fees for hosting or consulting/ support
services related to the Software), a product or service whose
value derives, entirely or substantially, from the functionality
of the Software. Any license notice or attribution required
by the License must also include this Commons Clause License
Condition notice.
Software: Nullboard
License: The 2-clause BSD License
Licensor: Alexander Pankratov / swapped.ch
-->
<meta charset="utf-8">
<title>Nullboard</title>
<style>
/***/
@font-face {
font-family: 'Nullboard';
font-weight: 400;
font-style: normal;
src: url('extras/Barlow-Regular.woff') format('woff');
}
2019-05-28 12:09:54 +02:00
@font-face {
font-family: 'Nullboard';
font-weight: 500;
font-style: normal;
src: url('extras/Barlow-Medium.woff') format('woff');
}
2019-06-10 13:19:05 +02:00
/***/
html, body, h1, textarea, input {
padding: 0;
margin: 0;
}
body, input, textarea {
font-family: Nullboard, sans-serif;
font-size: 13px;
}
body {
background: #fff;
background: #f8f9fb;
}
body.dragging {
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-webkit-touch-callout: none;
-o-user-select: none;
-moz-user-select: none;
}
2021-03-31 18:27:33 +02:00
@keyframes shake {
33% { margin-left: -5px; }
66% { margin-left: 5px; }
100% { margin-left: 0px; }
}
body.ding {
2021-03-31 18:27:33 +02:00
animation-name: shake;
animation-duration: 200ms;
}
a {
text-decoration: none;
transition: color 200ms;
}
a, a:active, a:focus, textarea, input {
outline: none;
}
tt {
display: none;
}
/***/
.clearfix:after,
.board:after,
.lists:after,
.notes:after,
.head:after,
.menu:after {
content: "";
display: table;
clear: both;
}
/***/
.board {
min-width: 250px;
width: -moz-max-content; /* firefox */
width: -webkit-max-content; /* chrome */
width: intrinsic; /* safari */
margin: 0 auto;
padding: 20px;
user-select: none;
}
body.crowded .board {
margin-top: 28px;
}
.board u {
text-decoration: none;
}
.board u:before {
content: '\00D7';
position: relative;
top: 2px;
font-size: 17px;
line-height: 10px;
font-weight: 400;
}
/***/
.board .head {
padding: 5px 0;
margin: 0 5px;
position: relative;
}
.board .head .text,
.board .head .edit {
font-weight: 500;
font-size: 13.5px;
line-height: 20px;
padding: 0 5px 2px;
border: none;
}
.board .head .text {
min-height: 20px;
white-space: pre;
overflow: hidden;
}
.board .head .edit {
display: none;
}
.board .head .edit::placeholder {
font-weight: 400;
font-size: 10px;
line-height: 22px;
text-transform: uppercase;
color: #1489db;
2020-02-19 14:25:11 +01:00
opacity: 0.8;
}
.board .head.editing .text {
display: none;
}
.board .head.editing .edit {
display: block;
outline: 1px solid #8eaedd;
}
.board .menu {
position: absolute;
top: 0;
right: 0;
height: 20px;
padding: 5px 6px 7px 30px;
background: linear-gradient(to right, #EAEDF000, #EAEDF0 10px);
font-size: 11px;
line-height: 20px;
}
.board .menu a,
.board .ops a {
color: #000000A0;
transition: color 200ms;
}
.board .menu a {
padding-left: 10px;
}
.board .menu a:hover,
.board .ops a:hover {
color: #000;
}
.board .menu a.warn:hover,
.board .ops a.warn:hover {
color: #c40;
}
.board .menu .undo-board,
.board .menu .redo-board {
display: none;
}
.board .head.editing .menu {
display: none;
}
.board > .head {
background: #EAEDF0;
padding: 5px;
margin: 0 0 10px;
border-radius: 2px;
position: relative;
}
.board > .head .menu {
margin-right: 5px;
}
/***/
.board .lists-scroller {
height: auto;
margin: -1px 0 10px;
overflow-x: auto;
overflow-y: hidden;
display: none;
}
.lists-scroller div {
height: 1px;
}
.board .lists {
white-space: nowrap;
overflow: auto;
scrollbar-width: none;
}
.board .list {
display: inline-block;
vertical-align: top;
width: 250px;
margin: 0 5px 10px;
background: linear-gradient(#EAEDF0 30px, #DDE1E5 90px);
border-radius: 2px;
}
.board .list::-webkit-scrollbar {
display: none;
}
.board .list:first-child {
margin-left: 0;
}
.board .list:last-child {
margin-right: 0;
}
.board .list .notes {
padding: 0 5px;
}
/***/
.board .head .menu .teaser {
position: absolute;
right: 3px;
top: 5px;
padding: 0 3px;
}
.board .head .menu .bulk {
display: none;
opacity: 0;
z-index: 1;
}
.board .head .menu:hover .bulk {
display: block;
opacity: 1;
}
.board .head .menu:hover .teaser {
display: none;
}
/***/
.board .list .menu .mov-list-r.half {
padding-left: 0;
}
.board .list .menu .full {
display: none;
}
.board .list:first-child .menu .half,
.board .list:last-child .menu .half {
display: none;
}
.board .list:first-child .menu .mov-list-r.full,
.board .list:last-child .menu .mov-list-l.full {
display: inline-block;
}
.board .list:first-child:last-child .menu .half,
.board .list:first-child:last-child .menu .full {
display: none;
}
/***/
.board .note {
background: #fff;
margin-top: 5px;
box-shadow: 0 1px 2px #bbb, 0 0 1px #ddd;
position: relative;
}
.board .note.dragging,
.board .note.dragging.raw {
background: #CED4DA;
box-shadow: 0 +1px 0 #00000010 inset,
0 -1px 0 #00000010 inset,
+1px 0 0 #00000010 inset,
-1px 0 0 #00000010 inset;
}
.board .note.dragging * {
opacity: 0 !important;
}
/***/
.board .note:last-child {
margin-bottom: 5px;
}
.board .note .text,
.board .note .edit {
padding: 5px 10px;
margin-right: 15px;
font-size: 11px;
}
.board .note .text {
min-height: 13px;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
2020-02-20 01:13:00 +01:00
/***/
.board .head .text a,
.board .note .text a {
2020-02-20 01:13:00 +01:00
color: inherit;
cursor: default;
transition: none;
}
@keyframes whoomp {
0% { color: inherit; }
30% { color: #888; }
100% { color: inherit; }
}
.board .head .text a:hover,
.board .note .text a:hover {
2020-02-20 01:13:00 +01:00
animation-name: whoomp;
animation-duration: 700ms;
}
.reveal .board .head .text a,
.reveal .board .note .text a {
2020-02-20 01:13:00 +01:00
color: #1489db;
cursor: pointer;
}
.reveal .board .head .text a:hover,
.reveal .board .note .text a:hover {
2020-02-20 01:13:00 +01:00
animation-name: none;
}
/***/
.board .note .edit {
display: none;
border: none;
}
.board .note.editing {
box-shadow: none;
outline: 1px solid #8eaedd;
}
.board .note.editing .text {
display: none;
}
.board .note.editing .edit {
display: block;
resize: none;
}
/***/
.board .note .ops {
position: absolute;
top: 0;
right: 0;
opacity: 0;
transition: opacity 400ms;
cursor: default;
font-size: 9px;
}
.board .note.editing .ops {
display: none;
}
.board .note:hover .ops {
opacity: 1;
}
.board .note .ops .teaser {
display: block;
margin-top: 2px;
margin-right: 1px;
padding-right: 3px;
}
.board .note .ops .teaser:before {
content: '\2261';
}
.board .note .ops .bulk {
display: none;
background: #fff;
border-left: none;
padding: 0 0 1px 5px;
font-weight: 500;
border-left: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
.board .note .ops .bulk a {
padding-right: 4px;
}
.board .note .ops:hover .bulk {
display: block;
}
.board .note .ops:hover .teaser {
display: none;
}
/***/
.board .note.raw {
background: transparent;
box-shadow: none;
font-weight: 500;
}
.board .note.raw.editing {
background: #fff;
}
.board .note.raw .text {
}
.board .note.collapsed {
height: 23px;
}
.board .note.collapsed .text {
height: 13px;
overflow: hidden;
line-height: 22px;
padding-top: 0;
}
.board .note.collapsed .ops {
opacity: 1;
}
.board .note.collapsed .ops .teaser {
padding: 1px 3px 0 1px;
}
.board .note.collapsed .ops .teaser:before {
content: '_';
top: 1px;
}
.board .note.collapsed:hover .ops .teaser:before {
content: '\2261';
}
/***/
.dragster {
z-index: 2;
position: absolute;
opacity: 0;
box-shadow: 1px 2px 3px #00000050;
background: #fff;
white-space: pre-wrap;
cursor: move;
padding: 5px 25px 5px 10px;
font-size: 11px;
box-shadow: 0 +1px 0 #ACB4BC inset,
0 -1px 0 #ACB4BC inset,
+1px 0 0 #ACB4BC inset,
-1px 0 0 #ACB4BC inset,
0px 1px 3px #00000030;
}
2020-02-20 01:13:00 +01:00
.dragster a {
color: #000;
}
.dragster.collapsed {
overflow: hidden;
line-height: 22px;
padding-top: 0;
}
/***/
.logo {
position: absolute;
top: 9px;
left: 9px;
font-size: 11px;
line-height: 19px;
padding: 6px 12px;
opacity: 0.6;
z-index: 3;
background: #f8f9fb;
}
body.crowded .logo:hover {
background: #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.logo:hover {
opacity: 1.0;
}
.logo .bulk {
overflow: hidden;
height: 0;
opacity: 0;
transition: opacity 400ms;
}
.logo:hover .bulk {
height: auto;
opacity: 1;
}
.logo a {
color: #000;
display: block;
}
.logo a:hover {
color: #1489db;
}
.logo .bulk a:before {
display: inline-block;
content: '-';
width: 11px;
}
/***/
.config {
position: absolute;
top: 10px;
right: 21px;
font-size: 11px;
line-height: 19px;
z-index: 3;
background: #f8f9fb;
}
body.crowded .config:hover {
background: #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.config a {
display: block;
color: #000;
transition: color 200ms;
}
.config a:hover {
color: #1489db;
}
.config .teaser {
padding: 5px;
color: #999;
}
.config .bulk {
margin-right: 20px;
padding: 5px 0 10px 22px;
transition: opacity 400ms;
}
.config .bulk div {
display: none;
padding: 6px 2px;
margin: 6px -2px;
border-top: 1px solid #00000028;
border-bottom: 1px solid #00000028;
}
.config .bulk div a {
display: block;
margin-right: 10px;
}
.config .bulk div a.active {
color: #1489db;
}
.config .bulk div a.active:before {
content: '\2022 ';
display: inline-block;
width: 13px;
margin-left: -13px;
}
.config .bulk div a.active:hover {
color: #000;
}
2020-02-19 14:25:11 +01:00
.config .bulk a i,
.config .bulk a b {
font-style: normal;
font-weight: inherit;
}
.config .bulk a i {
display: none;
}
.config .bulk input.imp-board-select {
position: absolute;
width: 1px;
height: 1px;
visibility: hidden;
}
2020-02-19 14:25:11 +01:00
.config .bulk a.switch-theme {
padding-top: 6px;
margin-top: 6px;
border-top: 1px solid #00000028;
}
/***/
.config .bulk {
display: none;
opacity: 0;
}
.config:hover .teaser {
display: none;
}
.config:hover .bulk {
display: block;
opacity: 1;
}
/***/
.overlay {
position: fixed;
z-index: 10;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: none;
color: #000;
}
/***/
.overlay .license {
font-size: 12px;
line-height: 16px;
white-space: pre-wrap;
width: 384px;
height: auto;
max-height: 600px;
padding: 20px 25px 22px;
overflow-y: auto;
background: #fff;
position: absolute;
top: 100px;
left: 50%;
margin-left: -192px;
}
.overlay .license a {
color: #1489db;
}
.overlay .license span {
display: inline-block;
padding-right: 8px;
}
/***/
.overlay .about {
font-size: 12px;
line-height: 16px;
text-align: center;
width: 240px;
height: auto;
padding: 50px 25px;
background: #fff;
position: absolute;
top: 100px;
left: 50%;
margin-left: -145px;
}
.overlay .about h1 {
font-size: 15px;
font-weight: 500;
margin-bottom: 10px;
}
.overlay .about a {
display: block;
color: #1489db;
}
.overlay .about div {
position: absolute;
bottom: -30px;
width: 240px;
color: #888;
}
.overlay .about div span {
display: inline-block;
text-align: left;
padding-right: 10px;
}
/***/
@media print {
.logo, .config,
.board .head .teaser,
.list .head .teaser {
visibility: hidden;
display: none;
}
.board .note {
box-shadow: none;
outline: 1px solid #ccc;
}
.board .note.raw {
outline: none;
}
}
/*
* Dark mode
*/
2021-03-31 18:27:33 +02:00
body.theme-dark {
background-color: #15171A;
color: #d6d6d6;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head {
background: #202224;
}
/***/
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu {
background: linear-gradient(to right, #20222400, #202224 10px);
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu a {
color: #aaa;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu a:hover {
color: #fc4;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu a.warn:hover,
.theme-dark .board .ops a.warn:hover {
color: #fc5555 !important;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head {
color: #eee;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head .edit::placeholder {
color: #bf9d21;
}
/***/
2021-03-31 18:27:33 +02:00
.theme-dark .board .list {
background: #202224;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note {
background: #2B2C2F;
background: linear-gradient(#2B2C2F, #27282B);
box-shadow: 0 1px 3px #0005;
text-shadow: 0 0 4px #0008;
}
2021-03-31 18:27:33 +02:00
.theme-dark.reveal .board .head .text a,
.theme-dark.reveal .board .note .text a {
2020-02-20 01:13:00 +01:00
color: #fc0;
}
@keyframes whoomp-dark {
0% { color: inherit; }
30% { color: #fff; }
100% { color: inherit; }
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head .text a:hover,
.theme-dark .board .note .text a:hover {
2020-02-20 01:13:00 +01:00
animation-name: whoomp-dark;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.raw {
background: transparent;
box-shadow: none;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.raw .text {
color: #eee;
text-shadow: 0 0 4px #0008;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .teaser {
color: #ccc;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .bulk {
background: #202224;
border-left: 1px solid #111;
border-bottom: 1px solid #111;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.raw .ops .bulk {
border: none;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .bulk a {
color: #ccc;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .bulk a:hover {
color: #fc2;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.dragging,
.theme-dark .board .note.dragging.raw {
background: #15171A;
box-shadow: 0 +1px 0 #000 inset,
0 -1px 0 #000 inset,
+1px 0 0 #000 inset,
-1px 0 0 #000 inset;
}
2021-03-31 18:27:33 +02:00
.theme-dark .dragster {
box-shadow: 0px 1px 4px #000000;
background: #2e3032;
}
2021-03-31 18:27:33 +02:00
.theme-dark .dragster a {
2020-02-20 01:13:00 +01:00
color: #d6d6d6;
}
/***/
2021-03-31 18:27:33 +02:00
.theme-dark textarea,
.theme-dark input {
background: #111315;
color: #eee;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head.editing .edit {
outline: 1px solid #bc9908;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.editing {
background: #111315;
outline: 1px solid #bc9908;
}
/***/
2021-03-31 18:27:33 +02:00
.theme-dark .config, .theme-dark.crowded .config:hover {
background-color: #15171A;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config a {
color: #ddd;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config a:hover {
color: #fc2;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config .bulk div {
2020-02-19 14:25:11 +01:00
border-top: 1px solid #fff2;
border-bottom: 1px solid #fff2;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config .bulk div a.active {
color: #fc2;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config .bulk a.switch-theme {
2020-02-19 14:25:11 +01:00
border-top: 1px solid #fff2;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config a.switch-theme i {
2020-02-19 14:25:11 +01:00
display: inline;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config a.switch-theme b {
2020-02-19 14:25:11 +01:00
display: none;
}
/***/
2021-03-31 18:27:33 +02:00
.theme-dark .logo a {
color: #ccc;
}
2021-03-31 18:27:33 +02:00
.theme-dark .logo, .theme-dark.crowded .logo:hover {
background: #15171A;
}
2021-03-31 18:27:33 +02:00
.theme-dark .logo a:hover {
color: #fc2;
}
2021-03-31 18:27:33 +02:00
.theme-dark .logo > a:hover {
color: #fff;
}
2020-02-19 14:25:11 +01:00
/*
* Larger font
*/
2021-03-31 18:27:33 +02:00
body.fsize-z1,
body.fsize-z1 input,
body.fsize-z1 textarea {
2020-02-19 14:25:11 +01:00
font-size: 15px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board {
2020-02-19 14:25:11 +01:00
min-width: 290px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board u:before {
2020-02-19 14:25:11 +01:00
font-size: 19px;
line-height: 12px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .head .text {
2020-02-19 14:25:11 +01:00
min-height: 22px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .head .text,
.fsize-z1 .board .head .edit {
2020-02-19 14:25:11 +01:00
font-size: 15px;
line-height: 22px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .head .edit::placeholder {
2020-02-19 14:25:11 +01:00
font-size: 10px;
line-height: 23px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .menu {
2020-02-19 14:25:11 +01:00
height: 22px;
font-size: 13px;
line-height: 22px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .list {
2020-02-19 14:25:11 +01:00
width: 290px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .note .text,
.fsize-z1 .board .note .edit,
.fsize-z1 .dragster {
2020-02-19 14:25:11 +01:00
font-size: 13px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .note .text {
2020-02-19 14:25:11 +01:00
min-height: 16px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .note.collapsed {
2020-02-19 14:25:11 +01:00
height: 26px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .note.collapsed .text {
2020-02-19 14:25:11 +01:00
height: 17px;
line-height: 25px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .dragster.collapsed {
2020-02-19 14:25:11 +01:00
line-height: 24px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .board .note .ops {
2020-02-19 14:25:11 +01:00
font-size: 10px;
}
/***/
2021-03-31 18:27:33 +02:00
.fsize-z1 .logo {
2020-02-19 14:25:11 +01:00
font-size: 13px;
line-height: 21px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .logo .bulk a:before {
2020-02-19 14:25:11 +01:00
width: 13px;
}
/***/
2021-03-31 18:27:33 +02:00
.fsize-z1 .config {
2020-02-19 14:25:11 +01:00
font-size: 13px;
line-height: 21px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .config .bulk div a.active:before {
2020-02-19 14:25:11 +01:00
width: 15px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .config a.switch-fsize i {
2020-02-19 14:25:11 +01:00
display: inline;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .config a.switch-fsize b {
2020-02-19 14:25:11 +01:00
display: none;
}
/***/
2021-03-31 18:27:33 +02:00
.fsize-z1 .overlay .license {
2020-02-19 14:25:11 +01:00
font-size: 14px;
line-height: 18px;
width: 443px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .overlay .about {
2020-02-19 14:25:11 +01:00
font-size: 12px;
line-height: 18px;
width: 277px;
}
2021-03-31 18:27:33 +02:00
.fsize-z1 .overlay .about h1 {
2020-02-19 14:25:11 +01:00
font-size: 17px;
width: 277px;
}
</style>
</head>
2019-05-28 12:09:54 +02:00
<body>
<div class=logo>
<a href=https://nullboard.io>Nullboard</a>
<div class=bulk>
<a href=# class=view-about>About</a>
<a href=# class=view-license>License</a>
<a href=https://nullboard.io/github target=_blank>Github</a>
<a href=https://nullboard.io/twitter target=_blank>Twitter</a>
</div>
</div>
2019-05-28 12:09:54 +02:00
<div class=config>
<a href=# class=teaser>&equiv;</a>
<div class=bulk>
<a href=# class=add-board>Add new board...</a>
<div class=boards>
2019-05-28 12:09:54 +02:00
<!-- here'll be boards -->
</div>
<a href=# class=imp-board>Import board...</a>
<input class=imp-board-select type="file" accept=".nbx">
<a href=# class=exp-board>Export board...</a>
2020-02-19 14:25:11 +01:00
<a href="#" class="switch-theme">Use <i>light</i><b>dark</b> theme</a>
<a href="#" class="switch-fsize">Use <i>smaller</i><b>larger</b> font</a>
2019-05-28 12:09:54 +02:00
</div>
</div>
</div>
2019-05-28 12:09:54 +02:00
<div class=wrap>
2019-05-28 12:09:54 +02:00
</div>
<div class=overlay>
2019-05-28 12:09:54 +02:00
</div>
<tt>
<!-- templates -->
<div class=board>
<div class=head>
<div class=text></div>
<input class=edit spellcheck=false placeholder='Name of the board'>
<div class=menu>
<a href=# class=teaser>&equiv;</a>
<div class=bulk>
<a href=# class='del-board warn'><u></u> Board</a>
<a href=# class='undo-board'>Undo</a>
<a href=# class='redo-board'>Redo</a>
<a href=# class='add-list'>+ List</a>
2019-05-28 12:09:54 +02:00
</div>
</div>
</div>
<div class=lists-scroller><div></div></div>
<div class=lists>
2019-05-28 12:09:54 +02:00
</div>
</div>
<div class=list>
<div class=head>
<div class=text></div>
<input class=edit spellcheck=false placeholder='Name of the list'>
<div class=menu>
<a href=# class=teaser>&equiv;</a>
<div class=bulk>
<a href=# class='del-list warn'><u></u> List</a>
<a href=# class='mov-list-l full'>&lt; Move</a>
<a href=# class='mov-list-l half'>&lt; Mo</a><a href=# class='mov-list-r half'>ve &gt;</a>
<a href=# class='mov-list-r full'>Move &gt;</a>
<a href=# class='add-note'>+ Note</a>
2019-05-28 12:09:54 +02:00
</div>
</div>
</div>
<div class=notes>
2019-05-28 12:09:54 +02:00
</div>
</div>
<div class=note>
<div class=ops>
<a href=# class=teaser></a>
<div class=bulk>
<a href=# class='del-note warn'><u></u></a>
<a href=# class='raw-note'>R</a>
<a href=# class='collapse'>_</a>
2019-05-28 12:09:54 +02:00
</div>
</div>
<div class=text>
2019-05-28 12:09:54 +02:00
</div>
<textarea class=edit spellcheck=false></textarea>
2019-05-28 12:09:54 +02:00
</div>
<a href=# class=load-board></a>
2019-05-28 12:09:54 +02:00
<textarea class=encoder></textarea>
2019-05-28 12:09:54 +02:00
<div class=about>
2019-05-28 12:09:54 +02:00
<h1>Nullboard</h1>
Minimalist locally-stored kanban board
<a href=https://nullboard.io target=_blank>https://nullboard.io</a>
2019-05-28 12:09:54 +02:00
<div></div>
</div>
<div class=license>
2019-05-28 12:09:54 +02:00
</div>
</tt>
</body>
2021-03-29 10:09:46 +02:00
<script src="extras/jquery-3.3.1.min.js"></script>
<script>window.jQuery || document.write('<script src="https://code.jquery.com/jquery-3.3.1.min.js">\x3C/script>')</script>
2021-03-31 18:27:33 +02:00
2019-05-28 12:09:54 +02:00
<script type="text/javascript">
2021-03-31 18:27:33 +02:00
function AppConfig()
{
2021-03-31 18:27:33 +02:00
this.format = NB.dataVersion;
this.max_undo = 50; // board revisions to keep
this.theme = null; // default or 'dark'
this.fsize = null; // default or 'z1'
this.board = null; // active board
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function BoardMeta()
{
2021-03-31 18:27:33 +02:00
this.title = '';
this.current = 1; // revision
this.history = [ ]; // revision IDs
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
class Storage
{
2021-03-31 18:27:33 +02:00
type = '?';
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
open()
{
2021-03-31 18:27:33 +02:00
return this.openInner();
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
wipe()
{
return this.wipeInner();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
getConfig()
{
2021-03-31 18:27:33 +02:00
return this.conf;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
setActiveBoard(board_id)
{
var meta = board_id ? this.boardIndex.get(board_id) : true;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta)
throw `Invalid board_id in setActiveBoard(... ${board_id})`;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.conf.board = board_id;
return this.setJson('config', this.conf);
}
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
setTheme(theme)
{
this.conf.theme = theme;
return this.setJson('config', this.conf);
}
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
setFsize(fsize)
{
this.conf.fsize = fsize;
return this.setJson('config', this.conf);
}
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
//
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
getBoardIndex()
{
return this.boardIndex;
}
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
saveBoard(board)
{
/*
* 1. assign new revision (next unused)
* 2. trim all in-between revisions bypassed by undos if any
* 3. cap history as per config
*/
var meta = this.boardIndex.get(board.id);
var ok_data, ok_meta;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta)
{
meta = new BoardMeta();
meta.history = [ 0 ];
meta.current = 0;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.boardIndex.set(board.id, meta);
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var rev_old = board.revision;
var rev_new = meta.history[0] + 1;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
board.revision = rev_new;
delete board.history; // remove temporarily
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
ok_data = this.setJson('board.' + board.id + '.' + board.revision, board);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
// update meta
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
meta.title = board.title || '(Untitled board)';
meta.current = board.revision;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
// trim revisions skipped over with undo, also cap revision count
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var keep = [ rev_new ];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
for (var rev of meta.history)
{
if ( ! rev || (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
{
keep.push(rev);
}
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
meta.history = keep;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
ok_meta = this.setJson('board.' + board.id + '.meta', meta);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
board.history = meta.history; // restore
console.log( `Saved revision ${board.revision} of ${board.id} (${board.title}), ok = ${ok_data} | ${ok_meta}` );
return ok_data && ok_meta;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
loadBoard(board_id, revision)
{
2021-03-31 18:27:33 +02:00
var meta = this.boardIndex.get(board_id);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta)
throw `Invalid board_id in loadBoard(${board_id}, ${revision})`;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (revision == null)
revision = meta.current;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta.history.includes(revision))
throw `Invalid revision in loadBoard(${board_id}, ${revision})`;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var board = this.getJson('board.' + board_id + '.' + revision);
if (! board)
return false;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (board.format != NB.dataVersion)
{
console.log('Board ' + board_id + '/' + revision + ' format is unsupported');
console.log('Have [' + board.format + '], need [' + NB.dataVersion);
return false;
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (board.revision != revision)
{
console.log('Board ' + board_id + '/' + revision + ' revision is wrong');
console.log('Have [' + board.revision + ']');
return false;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
board.history = meta.history;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
console.log( `Loaded revision ${board.revision} of ${board.id} (${board.title})` );
return Object.assign(new Board(), board);
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
nukeBoard(board_id, revision)
{
var meta = this.boardIndex.get(board_id);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta)
throw `Invalid board_id in nukeBoard(${board.id})`;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var title = meta.title + '';
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (revision != null)
{
var i = meta.history.indexOf(revision);
if (i == -1)
return false;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
meta.history.splice(i, 1);
this.setJson('board.' + board_id + '.meta', meta);
this.delItem('board.' + board_id + '.' + revision);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
console.log( `Deleted revision ${revision} of ${board_id} (${title}) #` );
}
2019-05-28 12:09:54 +02:00
else
2021-03-31 18:27:33 +02:00
{
for (var rev of meta.history)
this.delItem('board.' + board_id + '.' + rev);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.delItem('board.' + board_id + '.meta');
this.boardIndex.delete(board_id);
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
console.log( `Deleted board ${board_id} (${title})` );
}
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
getBoardHistory(board_id)
{
var meta = this.boardIndex.get(board_id);
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (! meta)
throw `Invalid board_id in getBoardHistory(${board_id})`;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
return meta.history;
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
setBoardRevision(board_id, revision)
{
var meta = this.boardIndex.get(board_id);
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (! meta)
throw `Invalid board_id in setBoardRevision(${board_id}, ${revision})`;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta.history.includes(revision))
throw `Invalid revision in setBoardRevision(${board_id}, ${revision})`;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (meta.current == revision) // wth
return true;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
meta.current = revision;
return this.setJson('board.' + board_id + '.meta', meta);
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
/*
* private
*/
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
conf = null;
boardIndex = new Map();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
getItem(name) { throw 'implement-me'; }
setItem(name) { throw 'implement-me'; }
delItem(name) { throw 'implement-me'; }
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
openInner() { throw 'implement-me'; }
wipeInner() { throw 'implement-me'; }
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
getJson(name)
{
var foo = this.getItem(name);
if (! foo) return false;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
try { foo = JSON.parse(foo); } catch (x) { return false; }
return foo;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
setJson(name, val)
{
if (! this.setItem(name, JSON.stringify(val)))
{
console.log("setJson(" + name + ") failed");
return false;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
return true;
}
};
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
class Storage_Local extends Storage
{
2021-03-31 18:27:33 +02:00
getItem(name)
{
2021-03-31 18:27:33 +02:00
return localStorage.getItem('nullboard.' + name);
}
2021-03-31 18:27:33 +02:00
setItem(name, val)
{
2021-03-31 18:27:33 +02:00
localStorage.setItem('nullboard.' + name, val);
return true;
}
2021-03-31 18:27:33 +02:00
delItem(name)
{
2021-03-31 18:27:33 +02:00
localStorage.removeItem('nullboard.' + name);
return true;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
openInner()
{
var conf = this.getJson('config');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.conf = new AppConfig();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (conf)
{
if (conf.format != NB.dataVersion)
{
alert('Saved data is from a different version of NB');
this.conf = null;
return false;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.conf = conf;
}
else
{
this.conf.theme = this.getItem('theme');
this.conf.fsize = this.getItem('fsize');
this.conf.board = this.getItem('last_board');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! this.setJson('config', this.conf))
{
this.conf = null;
return false;
}
}
2021-03-31 18:27:33 +02:00
this.boardIndex = new Map();
2021-03-31 18:27:33 +02:00
for (var i=0; i<localStorage.length; i++)
{
var k = localStorage.key(i);
var m = k.match(/^nullboard\.board\.(\d+).meta$/);
var meta;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (m)
{
// new format
meta = this.getJson('board.' + m[1] + '.meta');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (meta)
{
for (var x of meta.history)
if (! this.getJson('board.' + m[1] + '.' + x))
{
console.log( `bad meta for ${m[1]}` );
meta = this.rebuildMeta(m[1]);
break;
}
}
}
else
{
// old format
m = k.match(/^nullboard\.board\.(\d+)$/);
meta = m ? this.rebuildMeta(m[1]) : null;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta)
continue;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
meta = Object.assign(new BoardMeta(), meta);
this.boardIndex.set( parseInt(m[1]), meta );
}
2021-03-31 18:27:33 +02:00
if (this.conf.board && ! this.boardIndex.has(this.conf.board))
this.conf.board = null;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.type = 'LocalStorage';
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
return true;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
wipeInner()
{
for (var i=0; i<localStorage.length; )
{
var k = localStorage.key(i);
var m = k.match(/^nullboard\./);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (m) localStorage.removeItem(k);
else i++;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.conf = new AppConfig();
this.boardIndex = new Map();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.type = 'LocalStorage';
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
/*
* private
*/
rebuildMeta(board_id)
{
var meta = new BoardMeta();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
// get current revision
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
meta.current = this.getItem('board.' + board_id); // may be null
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
// load history
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var re = new RegExp('^nullboard\.board\.' + board_id + '\.(\\d+)$');
var revs = new Array();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
for (var i=0; i<localStorage.length; i++)
{
var m = localStorage.key(i).match(re);
if (m) revs.push( parseInt(m[1]) );
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! revs.length)
return false;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
revs.sort(function(a,b){ return b-a; });
meta.history = revs;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
// validate current revision
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! meta.history.includes(meta.current))
meta.current = meta.history[meta.history.length-1];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
// get board title
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var board = this.getJson('board.' + board_id + '.' + meta.current)
meta.title = (board.title || '(untitled board)');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.setJson('board.' + board_id + '.meta', meta);
return meta;
2019-05-28 12:09:54 +02:00
}
}
2021-03-31 18:27:33 +02:00
function _rollback()
{
2021-03-31 18:27:33 +02:00
localStorage.removeItem('nullboard.config');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var metas = [];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
for (var i=0; i<localStorage.length; i++)
{
var m = localStorage.key(i).match(/^nullboard\.board\.(\d+)\.meta$/);
if (m) metas.push(m[0]);
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
for (var k of metas) localStorage.removeItem(k);
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
</script>
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
<script type="text/javascript">
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function Note(text)
{
this.text = text;
this.raw = false;
this.min = false;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
function List(title)
{
2021-03-31 18:27:33 +02:00
this.title = title;
this.notes = [ ];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.addNote = function(text)
{
var x = new Note(text);
this.notes.push(x);
return x;
}
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
function Board(title)
{
2021-03-31 18:27:33 +02:00
this.format = NB.dataVersion;
this.id = +new Date();
this.revision = 0;
this.title = title || '';
this.lists = [ ];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.addList = function(title)
{
2021-03-31 18:27:33 +02:00
var x = new List(title);
this.lists.push(x);
return x;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
</script>
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
<script type="text/javascript">
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function Drag()
{
2021-03-31 18:27:33 +02:00
this.item = null; // .text of .note
this.org_loc = 0; // original noteLocation();
this.priming = null;
this.primexy = { x: 0, y: 0 };
this.$drag = null;
this.mouse = null;
this.delta = { x: 0, y: 0 };
this.in_swap = false;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.prime = function(item, ev)
{
var self = this;
this.item = item;
this.priming = setTimeout(function(){ self.onPrimed.call(self); }, ev.altKey ? 1 : 500);
this.primexy.x = ev.clientX;
this.primexy.y = ev.clientY;
this.mouse = ev;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.cancelPriming = function()
{
if (this.item && this.priming)
{
clearTimeout(this.priming);
this.priming = null;
this.item = null;
}
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.end = function()
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
this.cancelPriming();
this.stopDragging();
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
this.isActive = function()
{
return this.item && (this.priming == null);
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
this.onPrimed = function()
{
clearTimeout(this.priming);
this.priming = null;
this.item.was_dragged = true;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var $text = $(this.item);
var $note = $text.parent();
$note.addClass('dragging');
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
this.org_loc = noteLocation($note);
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$('body').append('<div class=dragster></div>');
var $drag = $('body .dragster').last();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if ($note.hasClass('collapsed'))
$drag.addClass('collapsed');
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$drag.html( $text.html() );
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$drag.innerWidth ( $note.innerWidth() );
$drag.innerHeight( $note.innerHeight() );
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
this.$drag = $drag;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var $win = $(window);
var scroll_x = $win.scrollLeft();
var scroll_y = $win.scrollTop();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var pos = $note.offset();
this.delta.x = pos.left - this.mouse.clientX - scroll_x;
this.delta.y = pos.top - this.mouse.clientY - scroll_y;
this.adjustDrag();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$drag.css({ opacity: 1 });
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$('body').addClass('dragging');
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
this.adjustDrag = function()
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
if (! this.$drag)
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var $win = $(window);
var scroll_x = $win.scrollLeft();
var scroll_y = $win.scrollTop();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var drag_x = this.mouse.clientX + this.delta.x + scroll_x;
var drag_y = this.mouse.clientY + this.delta.y + scroll_y;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
this.$drag.offset({ left: drag_x, top: drag_y });
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (this.in_swap)
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
/*
* see if a swap is in order
*/
var pos = this.$drag.offset();
var x = pos.left + this.$drag.width()/2 - $win.scrollLeft();
var y = pos.top + this.$drag.height()/2 - $win.scrollTop();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var drag = this;
var prepend = null; // if dropping on the list header
var target = null; // if over some item
var before = false; // if should go before that item
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$(".board .list").each(function(){
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var list = this;
var rc = list.getBoundingClientRect();
var y_min = rc.bottom;
var n_min = null;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (x <= rc.left || rc.right <= x || y <= rc.top || rc.bottom <= y)
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var $list = $(list);
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$list.find('.note').each(function(){
var note = this;
var rc = note.getBoundingClientRect();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (rc.top < y_min)
{
y_min = rc.top;
n_min = note;
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (y <= rc.top || rc.bottom <= y)
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (note == drag.item.parentNode)
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
target = note;
before = (y < (rc.top + rc.bottom)/2);
});
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
/*
* dropping on the list header
*/
if (! target && y < y_min)
{
if (n_min) // non-empty list
{
target = n_min;
before = true;
}
else
{
prepend = list;
}
}
});
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (! target && ! prepend)
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (target)
{
if (target == drag.item.parentNode)
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (! before && target.nextSibling == drag.item.parentNode ||
before && target.previousSibling == drag.item.parentNode)
return;
}
else
{
if (prepend.firstChild == drag.item.parentNode)
return;
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
/*
* swap 'em
*/
var $have = $(this.item.parentNode);
var $want = $have.clone();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$want.css({ display: 'none' });
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (target)
{
var $target = $(target);
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (before)
{
$want.insertBefore($target);
$want = $target.prev();
}
else
{
$want.insertAfter($target);
$want = $target.next();
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
drag.item = $want.find('.text')[0];
}
else
{
var $notes = $(prepend).find('.notes');
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$notes.prepend($want);
drag.item = $notes.find('.note .text')[0];
}
//
var h = $have.height();
drag.in_swap = true;
$have.animate({ height: 0 }, 'fast', function(){
$have.remove();
$want.css({ marginTop: 5 });
});
$want.css({ display: 'block', height: 0, marginTop: 0 });
$want.animate({ height: h }, 'fast', function(){
$want.css({ opacity: '', height: '' });
drag.in_swap = false;
drag.adjustDrag();
});
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
this.onMouseMove = function(ev)
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
this.mouse = ev;
if (! this.item)
2021-03-30 10:18:01 +02:00
return;
2021-03-31 18:27:33 +02:00
if (this.priming)
{
var x = ev.clientX - this.primexy.x;
var y = ev.clientY - this.primexy.y;
if (x*x + y*y > 5*5)
this.onPrimed();
}
else
{
this.adjustDrag();
2021-03-30 10:18:01 +02:00
}
}
2021-03-31 18:27:33 +02:00
this.stopDragging = function()
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
var $text = $(this.item);
var $note = $text.parent();
$note.addClass('dragging');
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$note.removeClass('dragging');
$('body').removeClass('dragging');
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (this.$drag)
{
this.$drag.remove();
this.$drag = null;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (window.getSelection) { window.getSelection().removeAllRanges(); }
else if (document.selection) { document.selection.empty(); }
var loc_now = noteLocation($note);
if (loc_now != this.org_loc)
saveBoard();
}
this.item = null;
}
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
</script>
<script type="text/javascript">
/*
* poor man's error handling -- $fixme
*/
window.onerror = function(message, file, line, col, e){
var cb1;
alert("Error occurred: " + e.message);
return false;
};
window.addEventListener("error", function(e) {
var cb2;
alert("Error occurred: " + e.error.message);
return false;
});
/*
* html ops
*/
function htmlEncode(raw)
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
return $('tt .encoder').text(raw).html();
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
// function htmlDecode(enc)
// {
// return $('tt .encoder').html(enc).text();
// }
function setText($note, text)
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
$note.attr('_text', text);
text = htmlEncode(text);
hmmm = /\b(https?:\/\/[^\s]+)/mg;
text = text.replace(hmmm, function(url){
return '<a href="' + url + '" target=_blank>' + url + '</a>';
2021-03-30 10:18:01 +02:00
});
2021-03-31 18:27:33 +02:00
$note.html(text);
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
function getText($note)
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
return $note.attr('_text');
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
/*
* board ops - save/load/parse/peek/nuke/import
*/
function saveBoard()
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
var $board = $('.wrap .board');
var board = Object.assign({}, NB.board); // id, revision & title
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
board.lists = [];
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$board.find('.list').each(function(){
var $list = $(this);
var l = board.addList( getText($list.find('.head .text')) );
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
$list.find('.note').each(function(){
var $note = $(this)
var n = l.addNote( getText($note.find('.text')) );
n.raw = $note.hasClass('raw');
n.min = $note.hasClass('collapsed');
});
2021-03-30 10:18:01 +02:00
});
2021-03-31 18:27:33 +02:00
NB.storage.saveBoard(board);
NB.board = board;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
updateUndoRedo();
updateBoardIndex();
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
function nukeBoard()
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
NB.storage.nukeBoard(NB.board.id, null);
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
function importBoard(blob)
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
var board;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
try
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
board = JSON.parse(blob);
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
catch (x)
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
alert('The file appears to be malformed');
return false;
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
if (typeof board.format === 'undefined' || ! board.format ||
typeof board.revision === 'undefined' || ! board.revision)
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
alert("The file doesn't appear to be a Nullboard export");
return false;
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
if (board.format != NB.dataVersion &&
board.format != 20190412)
{
console.log( `Unsupported file format - ${board.format}` );
return false;
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (! confirm( `Import board called "${board.title}", ID ${board.id}, revision ${board.revision} ?`))
return false;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var index = NB.storage.getBoardIndex();
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (index.has(board.id))
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
if (! confirm("There is an existing board with the same ID. Import under a new ID ?"))
return false;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
board.id = +new Date();
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
board.revision--; // save will ++ it back
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (! NB.storage.saveBoard(board))
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
alert("Failed to save the board. Import failed.");
return false;
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
openBoard(board.id);
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
//
function createDemoBoard()
2021-03-30 10:18:01 +02:00
{
2021-03-31 18:27:33 +02:00
var blob =
'{"format":20190412,"id":1555071015420,"revision":581,"title":"Welcome to Nullboard","lists":[{"title":"The Use' +
'r Manual","notes":[{"text":"This is a note.\\nA column of notes is a list.\\nA set of lists is a board.","raw"' +
':false,"min":false},{"text":"All data is saved locally.\\nThe whole thing works completely offline.","raw":fal' +
'se,"min":false},{"text":"Last 50 board revisions are retained.","raw":false,"min":false},{"text":"Ctrl-Z is Un' +
'do - goes one revision back.\\nCtrl-Y is Redo - goes one revision forward.","raw":false,"min":false},{"tex' +
't":"Caveats","raw":true,"min":false},{"text":"Desktop-oriented.\\nMobile support is basically untested.","raw"' +
':false,"min":false},{"text":"Works in Firefox, Chrome is supported.\\nShould work in Safari, may work in Edge.' +
'","raw":false,"min":false},{"text":"Still very much in beta. Caveat emptor.","raw":false,"min":false},{"text":' +
'"Issues and suggestions","raw":true,"min":false},{"text":"Post them on Github.\\nSee \\"Nullboard\\" at the to' +
'p left for the link.","raw":false,"min":false}]},{"title":"Things to try","notes":[{"text":"\u2022 Click on ' +
'a note to edit.","raw":false,"min":false},{"text":"\u2022 Click outside of it when done editing.\\n\u2022 ' +
'Alternatively, use Shift-Enter.","raw":false,"min":false},{"text":"\u2022 To discard changes press Escape.",' +
'"raw":false,"min":false},{"text":"\u2022 Try Ctrl-Enter, see what it does.\\n\u2022 Try Ctrl-Shift-Enter t' +
'oo.","raw":false,"min":false},{"text":"\u2022 Hover over a note to show its \u2261 menu.\\n\u2022 Hover ' +
'over \u2261 to reveal the options.","raw":false,"min":false},{"text":"\u2022 X deletes the note.\\n\u2022' +
' R changes how a note looks.\\n\u2022 _ collapses the note.","raw":false,"min":false},{"text":"This is a ' +
'raw note.","raw":true,"min":false},{"text":"This is a collapsed note. Only its first line is visible. Useful f' +
'or keeping lists compact.","raw":false,"min":true}, {"text":"Links","raw":true,"min":false}, {"text":"Links pu' +
'lse on hover and can be opened via the right-click menu - https://nullboard.io","raw":false,"min":false}, {"tex' +
't":"Pressing CapsLock highlights all links and makes them left-clickable.","raw":false,"min":false}]},{"title"' +
':"More things to try","notes":[{"text":"\u2022 Drag notes around to rearrange.\\n\u2022 Works between the ' +
'lists too.","raw":false,"min":false},{"text":"\u2022 Click on a list name to edit.\\n\u2022 Enter to save,' +
' Esc to cancel.","raw":false,"min":false},{"text":"\u2022 Try adding a new list.\\n\u2022 Try deleting one' +
'. This _can_ be undone.","raw":false,"min":false},{"text":"\u2022 Same for the board name.","raw":false,"m' +
'in":false},{"text":"Boards","raw":true,"min":false},{"text":"\u2022 Check out \u2261 at the top right.",' +
'"raw":false,"min":false},{"text":"\u2022 Try adding a new board.\\n\u2022 Try switching between the boards' +
'.","raw":false,"min":false},{"text":"\u2022 Try deleting a board. Unlike deleting a\\n list this _canno' +
't_ be undone.","raw":false,"min":false},{"text":"\u2022 Export the board (save to a file, as json)\\n' +
'\u2022 Import the board (load from a save)","raw":false,"min":false}]}]}';
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var demo = JSON.parse(blob);
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
if (! demo)
return false;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
demo.id = +new Date();
demo.revision = 0;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
NB.storage.saveBoard(demo);
NB.storage.setActiveBoard(demo.id);
return Object.assign(new Board(), demo);
2019-05-28 12:09:54 +02:00
}
/*
2021-03-31 18:27:33 +02:00
*
2019-05-28 12:09:54 +02:00
*/
2021-03-31 18:27:33 +02:00
function addNote($list, $after, $before)
{
2021-03-31 18:27:33 +02:00
var $note = $('tt .note').clone();
var $notes = $list.find('.notes');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$note.find('.text').html('');
$note.addClass('brand-new');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if ($before)
{
2021-03-31 18:27:33 +02:00
$before.before($note);
$note = $before.prev();
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
else
if ($after)
{
2021-03-31 18:27:33 +02:00
$after.after($note);
$note = $after.next();
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
else
{
2021-03-31 18:27:33 +02:00
$notes.append($note);
$note = $notes.find('.note').last();
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
$note.find('.text').click();
}
2021-03-30 10:31:28 +02:00
2021-03-31 18:27:33 +02:00
function deleteNote($note)
{
$note
.animate({ opacity: 0 }, 'fast')
.slideUp('fast')
.queue(function(){
$note.remove();
saveBoard();
});
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
function addList()
{
var $board = $('.wrap .board');
var $lists = $board.find('.lists');
var $list = $('tt .list').clone();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$list.find('.text').html('');
$list.find('.head').addClass('brand-new');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$lists.append($list);
$board.find('.lists .list .head .text').last().click();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var lists = $lists[0];
lists.scrollLeft = Math.max(0, lists.scrollWidth - lists.clientWidth);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
setupListScrolling();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function deleteList($list)
{
var empty = true;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$list.find('.note .text').each(function(){
empty &= ($(this).html().length == 0);
});
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! empty && ! confirm("Delete this list and all its notes?"))
return;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$list
.animate({ opacity: 0 })
.queue(function(){
$list.remove();
saveBoard();
});
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
setupListScrolling();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function moveList($list, left)
{
var $a = $list;
var $b = left ? $a.prev() : $a.next();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var $menu_a = $a.find('> .head .menu .bulk');
var $menu_b = $b.find('> .head .menu .bulk');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var pos_a = $a.offset().left;
var pos_b = $b.offset().left;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$a.css({ position: 'relative' });
$b.css({ position: 'relative' });
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$menu_a.hide();
$menu_b.hide();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$a.animate({ left: (pos_b - pos_a) + 'px' }, 'fast');
$b.animate({ left: (pos_a - pos_b) + 'px' }, 'fast', function(){
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (left) $list.prev().before($list);
else $list.before($list.next());
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$a.css({ position: '', left: '' });
$b.css({ position: '', left: '' });
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$menu_a.css({ display: '' });
$menu_b.css({ display: '' });
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
saveBoard();
});
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
function openBoard(board_id)
{
closeBoard(true);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
NB.board = NB.storage.loadBoard(board_id, null);
NB.storage.setActiveBoard(board_id);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
showBoard(true);
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function reopenBoard(revision)
{
var board_id = NB.board.id;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var via_menu = $('.wrap .board > .head .menu .bulk').is(':visible');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
NB.storage.setBoardRevision(board_id, revision);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
openBoard(board_id);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (via_menu)
{
var $menu = $('.wrap .board > .head .menu');
var $teaser = $menu.find('.teaser');
var $bulk = $menu.find('.bulk');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$teaser.hide().delay(100).queue(function(){ $(this).css('display', '').dequeue(); });
$bulk.show().delay(100).queue(function(){ $(this).css('display', '').dequeue(); });
}
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function closeBoard(quick)
{
var $board = $('.wrap .board');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (quick)
$board.remove();
else
$board
.animate({ opacity: 0 }, 'fast')
.queue(function(){ $board.remove(); });
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
NB.board = null;
NB.storage.setActiveBoard(null);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
updateUndoRedo();
updateBoardIndex();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
function addBoard()
{
closeBoard(true);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
NB.board = new Board();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
showBoard(true);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap .board .head').addClass('brand-new');
$('.wrap .board .head .text').click();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function deleteBoard()
{
var $list = $('.wrap .board .list');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if ($list.length && ! confirm("PERMANENTLY delete this board, all its lists and their notes?"))
return;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
nukeBoard();
closeBoard();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
function undoBoard()
{
if (! NB.board)
return false;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var hist = NB.storage.getBoardHistory(NB.board.id);
var have = NB.board.revision;
var want = 0;
for (var i=0; i<hist.length-1 && ! want; i++)
if (have == hist[i])
want = hist[i+1];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! want)
{
2021-03-31 18:27:33 +02:00
console.log('Undo - failed');
return false;
}
2021-03-30 10:31:28 +02:00
2021-03-31 18:27:33 +02:00
console.log('Undo -> ' + want);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
reopenBoard(want);
return true;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function redoBoard()
{
if (! NB.board)
return false;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var hist = NB.storage.getBoardHistory(NB.board.id);
var have = NB.board.revision;
var want = 0;
2021-03-30 10:31:28 +02:00
2021-03-31 18:27:33 +02:00
for (var i=1; i<hist.length && ! want; i++)
if (have == hist[i])
want = hist[i-1];
if (! want)
{
console.log('Redo - failed');
return false;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
console.log('Redo -> ' + want);
reopenBoard(want);
return true;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
//
function showBoard(quick)
{
var board = NB.board;
2021-03-30 10:37:15 +02:00
2021-03-31 18:27:33 +02:00
var $wrap = $('.wrap');
var $bdiv = $('tt .board');
var $ldiv = $('tt .list');
var $ndiv = $('tt .note');
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
var $b = $bdiv.clone();
var $b_lists = $b.find('.lists');
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
$b[0].board_id = board.id;
setText( $b.find('.head .text'), board.title );
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
board.lists.forEach(function(list){
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var $l = $ldiv.clone();
var $l_notes = $l.find('.notes');
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
setText( $l.find('.head .text'), list.title );
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
list.notes.forEach(function(n){
var $n = $ndiv.clone();
setText( $n.find('.text'), n.text );
if (n.raw) $n.addClass('raw');
if (n.min) $n.addClass('collapsed');
$l_notes.append($n);
});
$b_lists.append($l);
});
if (quick)
$wrap.html('').append($b);
else
2021-03-31 18:27:33 +02:00
$wrap.html('')
.css({ opacity: 0 })
.append($b)
.animate({ opacity: 1 });
updatePageTitle();
updateUndoRedo();
updateBoardIndex();
setupListScrolling();
}
/*
*
*/
function updatePageTitle()
{
var title = 'Nullboard';
if (NB.board)
{
2021-03-31 18:27:33 +02:00
title = NB.board.title;
title = 'NB - ' + (title || '(unnamed board)');
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
document.title = title;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function updateUndoRedo()
{
var $undo = $('.board .menu .undo-board');
var $redo = $('.board .menu .redo-board');
var undo = false;
var redo = false;
if (NB.board && NB.board.revision)
{
2021-03-31 18:27:33 +02:00
var history = NB.storage.getBoardHistory(NB.board.id);
var rev = NB.board.revision;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
undo = (rev != history[history.length-1]);
redo = (rev != history[0]);
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (undo) $undo.show(); else $undo.hide();
if (redo) $redo.show(); else $redo.hide();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function updateBoardIndex()
{
var $index = $('.config .boards');
var $export = $('.config .exp-board');
var $entry = $('tt .load-board');
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
var $board = $('.wrap .board');
var id_now = NB.board && NB.board.id;
var empty = true;
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
$index.html('');
$index.hide();
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
var boards = NB.storage.getBoardIndex();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
boards.forEach(function(meta, board_id){
2021-03-31 18:27:33 +02:00
var $e = $entry.clone();
$e[0].board_id = board_id;
$e.html(meta.title);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (board_id == id_now)
$e.addClass('active');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$index.append($e);
empty = false;
});
2021-03-31 18:27:33 +02:00
if (id_now) $export.show();
else $export.hide();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! empty) $index.show();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
/*
*
*/
function startEditing($text, ev)
{
var $note = $text.parent();
var $edit = $note.find('.edit');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$note[0]._collapsed = $note.hasClass('collapsed');
$note.removeClass('collapsed');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$edit.val( getText($text) );
$edit.width( $text.width() );
$edit.height( $text.height() );
$note.addClass('editing');
2021-03-31 18:27:33 +02:00
$edit.focus();
}
2021-03-31 18:27:33 +02:00
function stopEditing($edit, via_escape, via_xclick)
{
var $item = $edit.parent();
if (! $item.hasClass('editing'))
return;
2021-03-31 18:27:33 +02:00
$item.removeClass('editing');
if ($item[0]._collapsed)
$item.addClass('collapsed')
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
var $text = $item.find('.text');
var text_now = $edit.val().trimRight();
var text_was = getText( $text );
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
var brand_new = $item.hasClass('brand-new');
$item.removeClass('brand-new');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (via_escape && brand_new)
{
2021-03-31 18:27:33 +02:00
$item.closest('.note, .list, .board').remove();
return;
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
if (via_xclick && brand_new && !text_now.length)
{
$item.closest('.note, .list, .board').remove();
return;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (text_now != text_was || brand_new)
{
setText( $text, text_now );
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if ($item.parent().hasClass('board'))
NB.board.title = text_now;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
updatePageTitle();
saveBoard();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
if (brand_new && $item.hasClass('list'))
addNote($item);
}
2019-05-28 12:09:54 +02:00
//
2021-03-31 18:27:33 +02:00
function showDing()
{
$('body')
.addClass('ding')
.delay(250)
.queue(function(){ $(this).removeClass('ding').dequeue(); });
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
function showOverlay($div)
{
$('.overlay')
.html('')
.append($div)
.css({ opacity: 0, display: 'block' })
.animate({ opacity: 1 });
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function hideOverlay()
{
$('.overlay').animate({ opacity: 0 }, function(){
$(this).hide();
});
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
function haveOverlay()
{
return $('.overlay').css('display') != 'none';
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
function formatLicense()
{
var text = document.head.childNodes[1].nodeValue;
var pos = text.search('LICENSE');
var qos = text.search('Software:');
var bulk;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
bulk = text.substr(pos, qos-pos);
bulk = bulk.replace(/([^\n])\n\t/g, '$1 ');
bulk = bulk.replace(/\n\n\t/g, '\n\n');
bulk = bulk.replace(/([A-Z ]{7,})/g, '<u>$1</u>');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
var c1 = [];
var c2 = [];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
text.substr(qos).trim().split('\n').forEach(function(line){
line = line.split(':');
c1.push( line[0].trim() + ':' );
c2.push( line[1].trim() );
});
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
bulk += '<span>' + c1.join('<br>') + '</span>';
bulk += '<span>' + c2.join('<br>') + '</span>';
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
var links =
[
{ text: '2-clause BSD license', href: 'https://opensource.org/licenses/BSD-2-Clause/' },
{ text: 'Commons Clause', href: 'https://commonsclause.com/' }
];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
links.forEach(function(l){
bulk = bulk.replace(l.text, '<a href="' + l.href + '" target=_blank>' + l.text + '</a>');
});
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
return bulk.trim();
}
2019-05-28 12:09:54 +02:00
//
2021-03-31 18:27:33 +02:00
function setRevealState(ev)
{
var raw = ev.originalEvent;
var caps = raw.getModifierState && raw.getModifierState( 'CapsLock' );
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (caps) $('body').addClass('reveal');
else $('body').removeClass('reveal');
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
function handleTab(ev)
{
var $this = $(this);
var $note = $this.closest('.note');
var $sibl = ev.shiftKey ? $note.prev() : $note.next();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if ($sibl.length)
{
stopEditing($this, false, false);
$sibl.find('.text').click();
}
}
2019-05-28 12:09:54 +02:00
//
2021-03-31 18:27:33 +02:00
function adjustLayout()
{
var $body = $('body');
var $board = $('.board');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (! $board.length)
return;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var lists = $board.find('.list').length;
var lists_w = (lists < 2) ? 250 : 260 * lists - 10;
var body_w = $body.width();
if (lists_w + 190 <= body_w)
{
$board.css('max-width', '');
$body.removeClass('crowded');
}
else
{
var max = Math.floor( (body_w - 40) / 260 );
max = (max < 2) ? 250 : 260 * max - 10;
$board.css('max-width', max + 'px');
$body.addClass('crowded');
}
}
2019-05-28 12:09:54 +02:00
//
2021-03-31 18:27:33 +02:00
function adjustListScroller()
{
var $board = $('.board');
if (! $board.length)
return;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var $lists = $('.board .lists');
var $scroller = $('.board .lists-scroller');
var $inner = $scroller.find('div');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
var max = $board.width();
var want = $lists[0].scrollWidth;
var have = $inner.width();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (want <= max)
{
$scroller.hide();
return;
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$scroller.show();
if (want == have)
return;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$inner.width(want);
cloneScrollPos($lists, $scroller);
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
//
function cloneScrollPos($src, $dst)
{
var src = $src[0];
var dst = $dst[0];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (src._busyScrolling)
{
src._busyScrolling--;
return;
}
2021-03-31 18:27:33 +02:00
dst._busyScrolling++;
dst.scrollLeft = src.scrollLeft;
}
2020-02-19 14:25:11 +01:00
2021-03-31 18:27:33 +02:00
function setupListScrolling()
{
var $lists = $('.board .lists');
var $scroller = $('.board .lists-scroller');
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
adjustListScroller();
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$lists[0]._busyScrolling = 0;
$scroller[0]._busyScrolling = 0;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$scroller.on('scroll', function(){ cloneScrollPos($scroller, $lists); });
$lists .on('scroll', function(){ cloneScrollPos($lists, $scroller); });
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
adjustLayout();
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
/*
* this should *really* be in a separate file
*/
function noteLocation($item)
{
var loc = 0;
for (var $p = $item.closest('.note'); $p.length; $p = $p.prev(), loc += 1);
for (var $p = $item.closest('.list'); $p.length; $p = $p.prev(), loc += 10000);
return loc;
}
2021-03-30 10:18:01 +02:00
/*
2021-03-31 18:27:33 +02:00
* our stuff
2021-03-30 10:18:01 +02:00
*/
2021-03-31 18:27:33 +02:00
var NB =
{
codeVersion: 20210330,
dataVersion: 20190412,
board: null
};
2021-03-31 18:27:33 +02:00
NB.storage = new Storage_Local();
NB.drag = new Drag(),
2021-03-31 18:27:33 +02:00
/*
* UI
*/
$(window).on('blur', function(){
$('body').removeClass('reveal');
});
2020-02-19 14:25:11 +01:00
2021-03-31 18:27:33 +02:00
$(document).on('keydown', function(ev){
setRevealState(ev);
});
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
$(document).on('keyup', function(ev){
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var raw = ev.originalEvent;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
setRevealState(ev);
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (ev.target.nodeName == 'TEXTAREA' ||
ev.target.nodeName == 'INPUT')
return;
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (ev.ctrlKey && (raw.code == 'KeyZ'))
{
var ok = ev.shiftKey ? redoBoard() : undoBoard();
if (! ok)
showDing();
}
else
if (ev.ctrlKey && (raw.code == 'KeyY'))
{
if (! redoBoard())
showDing();
}
});
2021-03-30 10:37:15 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .text', function(ev){
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
if (this.was_dragged)
{
this.was_dragged = false;
return false;
}
2021-03-31 18:27:33 +02:00
NB.drag.cancelPriming();
2021-03-31 18:27:33 +02:00
startEditing($(this), ev);
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .note .text a', function(ev){
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (! $('body').hasClass('reveal'))
return true;
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
ev.stopPropagation();
return true;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.wrap').on('keydown', '.board .edit', function(ev){
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
var isNote = (this.tagName == 'TEXTAREA');
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
// esc
if (ev.keyCode == 27)
2021-03-31 12:35:41 +02:00
{
2021-03-31 18:27:33 +02:00
stopEditing($(this), true, false);
return false;
2021-03-31 12:35:41 +02:00
}
2021-03-31 18:27:33 +02:00
// tab
if (ev.keyCode == 9)
2021-03-31 12:35:41 +02:00
{
2021-03-31 18:27:33 +02:00
handleTab.call(this, ev);
return false;
2021-03-31 12:35:41 +02:00
}
2021-03-31 18:27:33 +02:00
// done
if (ev.keyCode == 13 && ! isNote ||
ev.keyCode == 13 && ev.altKey ||
ev.keyCode == 13 && ev.shiftKey && ! ev.ctrlKey)
2021-03-31 12:35:41 +02:00
{
2021-03-31 18:27:33 +02:00
stopEditing($(this), false, false);
return false;
2021-03-31 12:35:41 +02:00
}
2021-03-31 18:27:33 +02:00
// done + (add after / add before)
if (ev.keyCode == 13 && ev.ctrlKey)
2021-03-31 12:35:41 +02:00
{
2021-03-31 18:27:33 +02:00
var $this = $(this);
var $note = $this.closest('.note');
var $list = $note.closest('.list');
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
stopEditing($this, false, false);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if ($note && ev.shiftKey) // ctrl-shift-enter
addNote($list, null, $note);
else
if ($note && !ev.shiftKey) // ctrl-enter
addNote($list, $note);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
return false;
}
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
// done + collapse
if (isNote && ev.altKey && ev.key == 'ArrowUp')
{
var $item = $(this).parent();
$item[0]._collapsed = true;
stopEditing($(this), false, false);
return false;
}
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
// done + expand
if (isNote && ev.altKey && ev.key == 'ArrowDown')
{
var $item = $(this).parent();
$item[0]._collapsed = false;
stopEditing($(this), false, false);
return false;
2021-03-31 12:35:41 +02:00
}
2021-03-31 18:27:33 +02:00
// done + toggle 'raw'
if (isNote && ev.altKey && ev.keyCode == 82)
2021-03-31 12:35:41 +02:00
{
2021-03-31 18:27:33 +02:00
$(this).parent().toggleClass('raw');
stopEditing($(this), false, false);
return false;
}
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
// ctrl-shift-8
if (isNote && ev.key == '*' && ev.ctrlKey)
{
var have = this.value;
var pos = this.selectionStart;
var want = have.substr(0, pos) + '\u2022 ' + have.substr(this.selectionEnd);
$(this).val(want);
this.selectionStart = this.selectionEnd = pos + 2;
return false;
}
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
return true;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('keypress', '.board .edit', function(ev){
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
// tab
if (ev.keyCode == 9)
2021-03-31 12:35:41 +02:00
{
2021-03-31 18:27:33 +02:00
handleTab.call(this, ev);
return false;
}
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.wrap').on('blur', '.board .edit', function(ev){
if (document.activeElement != this)
stopEditing($(this), false, true);
else
; // switch away from the browser window
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.wrap').on('input propertychange', '.board .note .edit', function(){
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
var delta = $(this).outerHeight() - $(this).height();
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$(this).height(10);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (this.scrollHeight > this.clientHeight)
$(this).height(this.scrollHeight-delta);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.config').on('click', '.add-board', function(){
addBoard();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.config').on('click', '.load-board', function(){
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
var board_id = $(this)[0].board_id;
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (NB.board && (NB.board.id == board_id))
closeBoard();
else
openBoard( $(this)[0].board_id );
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .del-board', function(){
deleteBoard();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .undo-board', function(){
undoBoard();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .redo-board', function(){
redoBoard();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.wrap').on('click', '.board .add-list', function(){
addList();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .del-list', function(){
deleteList( $(this).closest('.list') );
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .mov-list-l', function(){
moveList( $(this).closest('.list'), true );
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .mov-list-r', function(){
moveList( $(this).closest('.list'), false );
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.wrap').on('click', '.board .add-note', function(){
addNote( $(this).closest('.list') );
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .del-note', function(){
deleteNote( $(this).closest('.note') );
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .raw-note', function(){
$(this).closest('.note').toggleClass('raw');
saveBoard();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.wrap').on('click', '.board .collapse', function(){
$(this).closest('.note').toggleClass('collapsed');
saveBoard();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.wrap').on('mousedown', '.board .note .text', function(ev){
NB.drag.prime(this, ev);
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$(document).on('mouseup', function(ev){
NB.drag.end();
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$(document).on('mousemove', function(ev){
setRevealState(ev);
NB.drag.onMouseMove(ev);
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.config').on('click', '.imp-board', function(ev){
$('.config .imp-board-select').click();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.config').on('change', '.imp-board-select' , function(){
var files = this.files;
var reader = new FileReader();
reader.onload = function(ev){ importBoard(ev.target.result); };
reader.readAsText(files[0]);
return true;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.config').on('click', '.exp-board', function(){
var board = NB.board;
var blob = JSON.stringify(board);
var file = `Nullboard-${board.id}-${board.title}.nbx`;
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
blob = encodeURIComponent(blob);
blob = "data:application/octet-stream," + blob;
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$(this).attr('href', blob);
$(this).attr('download', file);
return true;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.config').on('click', '.switch-theme', function() {
var $body = $('body');
$body.toggleClass('theme-dark');
NB.storage.setTheme($body.hasClass('theme-dark') ? 'dark' : '');
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.config').on('click', '.switch-fsize', function(){
var $body = $('body');
$body.toggleClass('fsize-z1');
NB.storage.setFsize($body.hasClass('fsize-z1') ? 'z1' : '');
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$('.overlay').click(function(ev){
if (ev.originalEvent.target != this)
return true;
hideOverlay();
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$(window).keydown(function(ev){
if (haveOverlay() && ev.keyCode == 27)
hideOverlay();
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.view-about').click(function(){
var $div = $('tt .about').clone();
$div.find('div').html('Version ' + nb_codeVersion);
showOverlay($div);
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$('.view-license').click(function(){
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
var $div = $('tt .license').clone();
$div.html(formatLicense());
showOverlay($div);
return false;
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
$(window).resize(adjustLayout);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
/*
* the init()
*/
if (! NB.storage.open())
{
alert( `Failed to open storage (of ${NB.storage.type} type)` );
exit;
}
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
var boards = NB.storage.getBoardIndex();
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
boards.forEach(function(meta, board_id){
var hist = meta.history.join(', ');
console.log( `Found board ${board_id} - "${meta.title}", revision ${meta.current}, history [${hist}]` );
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
var conf = NB.storage.getConfig();
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
console.log( `Active: [${conf.board}]` );
console.log( `Theme: [${conf.theme}]` );
console.log( `Fsize: [${conf.fsize}]` );
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (conf.theme)
$('body').addClass('theme-' + conf.theme);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (conf.fsize)
$('body').addClass('fsize-' + conf.fsize);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (conf.board)
openBoard(conf.board)
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
adjustLayout();
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
updateBoardIndex();
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (! NB.board && ! $('.config .load-board').length)
NB.board = createDemoBoard();
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (NB.board)
showBoard(true);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
setInterval(adjustListScroller, 100);
setupListScrolling();
2021-03-31 12:35:41 +02:00
2019-05-28 12:09:54 +02:00
</script>
</html>