2019-05-28 12:09:54 +02:00
<!doctype html>
< html >
< head >
2020-02-08 12:02:45 -05:00
<!--
2019-05-28 12:09:54 +02:00
2020-02-08 12:02:45 -05:00
+-------------------------------------------------------------+
2019-05-28 12:42:04 +02:00
| |
| Nullboard, a minimalist kanban board |
| https://nullboard.io |
| |
2020-02-08 12:02:45 -05:00
+-------------------------------------------------------------+
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
-->
2020-02-08 12:02:45 -05:00
< meta charset = "utf-8" >
< title > Nullboard< / title >
< style >
/***/
@font-face {
2021-04-05 11:50:02 +02:00
font-family: 'f-barlow';
2020-02-08 12:02:45 -05:00
font-weight: 400;
font-style: normal;
src: url('extras/Barlow-Regular.woff') format('woff');
}
2019-05-28 12:09:54 +02:00
2020-02-08 12:02:45 -05:00
@font-face {
2021-04-05 11:50:02 +02:00
font-family: 'f-barlow';
2020-02-08 12:02:45 -05:00
font-weight: 500;
font-style: normal;
src: url('extras/Barlow-Medium.woff') format('woff');
}
2019-06-10 13:19:05 +02:00
2021-04-05 11:50:02 +02:00
/***/
@font-face {
font-family: 'f-ibm-plex';
font-weight: 400;
font-style: normal;
src: url('extras/IBMPlexSans-Regular.woff') format('woff');
}
@font-face {
font-family: 'f-ibm-plex';
font-weight: 500;
font-style: normal;
src: url('extras/IBMPlexSans-Medium.woff') format('woff');
}
/***/
@font-face {
font-family: 'f-open-sans';
font-weight: 400;
font-style: normal;
src: url('extras/OpenSans-Regular.woff') format('woff');
}
@font-face {
font-family: 'f-open-sans';
font-weight: 500;
font-style: normal;
src: url('extras/OpenSans-600.woff') format('woff');
}
/***/
@font-face {
font-family: 'f-segoe-ui';
font-weight: 400;
font-style: normal;
src: local('Segoe UI');
}
@font-face {
font-family: 'f-segoe-ui';
font-weight: 500;
font-style: normal;
src: local('Segoe UI Semibold');
}
/***/
@font-face {
font-family: 'f-maven-pro';
font-weight: 400;
font-style: normal;
src: url('extras/MavenPro-Regular.woff') format('woff');
}
@font-face {
font-family: 'f-maven-pro';
font-weight: 500;
font-style: normal;
src: url('extras/MavenPro-500.woff') format('woff');
}
/***/
html {
--ff: f-barlow, sans-serif;
2021-04-07 13:56:10 +02:00
--fs: 11; /* font-size */
--lh: calc( var(--fs) * 1.25 ); /* line-height */
2021-04-05 11:50:02 +02:00
--fs-head: 13.6; /* font-size of board heading */
--fs-nops: 9; /* font-size of the note ops */
--lw: calc(20 * var(--fs) + 30); /* .list width */
}
html, body, input, textarea {
font-family: var(--ff);
font-size: calc( var(--fs) * 1px );
line-height: calc( var(--lh) * 1px );
}
/***/
html.f-ibm-plex {
--ff: f-ibm-plex, sans-serif;
--fs: 11;
--fs-head: 12.5;
--lw: calc(20 * var(--fs) + 40); /* .list width */
}
/***/
html.f-open-sans {
--ff: f-open-sans, sans-serif;
--fs: 11;
--fs-head: 12.5;
--lw: calc(20 * var(--fs) + 50); /* .list width */
}
/***/
html.f-segoe-ui {
--ff: f-segoe-ui, sans-serif;
--fs: 11;
--fs-head: 13;
--fs-nops: 8;
2021-04-06 13:21:48 +02:00
--lw: calc(22 * var(--fs) + 40); /* .list width */
2021-04-05 11:50:02 +02:00
}
2021-04-07 13:56:10 +02:00
/***/
html.f-maven-pro {
--ff: f-maven-pro, sans-serif;
--fs: 12;
--fs-head: 13;
--lw: calc(20 * var(--fs) + 47); /* .list width */
}
2020-02-08 12:02:45 -05:00
/***/
html, body, h1, textarea, input {
padding: 0;
margin: 0;
}
body {
background: #fff;
background: #f8f9fb;
}
2021-04-06 23:54:46 +02:00
/***/
2021-03-31 18:27:33 +02:00
@keyframes shake {
33% { margin-left: -5px; }
66% { margin-left: 5px; }
100% { margin-left: 0px; }
}
2020-02-08 12:02:45 -05:00
a {
text-decoration: none;
2020-02-19 03:50:03 +01:00
transition: color 200ms;
2020-02-08 12:02:45 -05:00
}
a, a:active, a:focus, textarea, input {
outline: none;
}
tt {
display: none;
}
2021-04-06 23:54:46 +02:00
/***/
2021-04-06 19:32:46 +02:00
.ding {
animation-name: shake;
animation-duration: 200ms;
}
.no-user-select {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}
2020-02-08 12:02:45 -05:00
/***/
.clearfix:after,
.board:after,
.lists:after,
.notes:after,
.head:after,
.menu:after {
content: "";
display: table;
clear: both;
}
2021-08-04 12:05:04 +02:00
/***/
body.dragging {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
2020-02-08 12:02:45 -05:00
/***/
.board {
2021-04-06 19:54:47 +02:00
min-width: calc( var(--lw) * 1px );
2020-02-08 12:02:45 -05:00
width: -moz-max-content; /* firefox */
width: -webkit-max-content; /* chrome */
width: intrinsic; /* safari */
margin: 0 auto;
padding: 20px;
2020-04-30 18:39:43 +02:00
2021-08-04 12:05:04 +02:00
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
2020-04-30 18:39:43 +02:00
user-select: none;
2020-02-08 12:02:45 -05:00
}
body.crowded .board {
margin-top: 28px;
}
.board u {
text-decoration: none;
}
.board u:before {
content: '\00D7';
position: relative;
top: 2px;
2021-04-05 11:50:02 +02:00
font-size: calc(16rem / 11);
line-height: calc(10 / 17);
2020-02-08 12:02:45 -05:00
font-weight: 400;
}
/***/
.board .head {
padding: 5px 0;
2021-04-06 23:54:46 +02:00
margin: 1px 5px 0;
2020-02-08 12:02:45 -05:00
position: relative;
}
.board .head .text,
.board .head .edit {
font-weight: 500;
2021-04-05 11:50:02 +02:00
font-size: calc( var(--fs-head) / 11 * 1rem );
line-height: calc( 20 / var(--fs-head) );
2020-02-08 12:02:45 -05:00
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;
2021-04-05 11:50:02 +02:00
font-size: calc(10rem / 11);
line-height: calc(22 / 10);
2020-02-08 12:02:45 -05:00
text-transform: uppercase;
color: #1489db;
2020-02-19 14:25:11 +01:00
opacity: 0.8;
2020-02-08 12:02:45 -05:00
}
.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);
2021-04-05 11:50:02 +02:00
line-height: calc(20 / 11);
2020-02-08 12:02:45 -05:00
}
.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;
2021-04-05 11:50:02 +02:00
width: calc( var(--lw) * 1px );
2020-02-08 12:02:45 -05:00
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;
}
/***/
2020-02-20 23:37:13 +01:00
.board .note {
2020-02-08 12:02:45 -05:00
background: #fff;
margin-top: 5px;
box-shadow: 0 1px 2px #bbb, 0 0 1px #ddd;
position: relative;
}
2020-02-20 23:37:13 +01:00
.board .note.dragging,
.board .note.dragging.raw {
2020-02-08 12:02:45 -05:00
background: #CED4DA;
2021-04-05 11:50:02 +02:00
box-shadow: 0 +1px 0 #0001 inset,
0 -1px 0 #0001 inset,
+1px 0 0 #0001 inset,
-1px 0 0 #0001 inset;
2020-02-08 12:02:45 -05:00
}
2020-02-20 23:37:13 +01:00
.board .note.dragging * {
2020-02-08 12:02:45 -05:00
opacity: 0 !important;
}
/***/
2020-02-20 23:37:13 +01:00
.board .note:last-child {
2020-02-08 12:02:45 -05:00
margin-bottom: 5px;
}
2021-04-05 11:50:02 +02:00
.board .note {
padding-bottom: 6px;
}
2020-02-20 23:37:13 +01:00
.board .note .text,
.board .note .edit {
2021-04-05 11:50:02 +02:00
padding: 5px 10px 0;
2020-02-08 12:02:45 -05:00
margin-right: 15px;
2021-04-05 11:50:02 +02:00
min-height: 100%;
2020-02-08 12:02:45 -05:00
}
2020-02-20 23:37:13 +01:00
.board .note .text {
2020-02-08 12:02:45 -05:00
white-space: pre-wrap;
2020-02-20 17:32:37 +01:00
overflow-wrap: anywhere;
2021-04-05 11:50:02 +02:00
min-height: calc( var(--lh) * 1px );
2020-02-08 12:02:45 -05:00
}
2020-02-20 01:13:00 +01:00
/***/
2020-02-20 23:37:13 +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; }
}
2020-02-20 23:37:13 +01:00
.board .head .text a:hover,
.board .note .text a:hover {
2020-02-20 01:13:00 +01:00
animation-name: whoomp;
animation-duration: 700ms;
}
2020-02-20 23:37:13 +01:00
.reveal .board .head .text a,
.reveal .board .note .text a {
2020-02-20 01:13:00 +01:00
color: #1489db;
cursor: pointer;
}
2020-02-20 23:37:13 +01:00
.reveal .board .head .text a:hover,
.reveal .board .note .text a:hover {
2020-02-20 01:13:00 +01:00
animation-name: none;
}
/***/
2020-02-20 23:37:13 +01:00
.board .note .edit {
2020-02-08 12:02:45 -05:00
display: none;
border: none;
}
2020-02-20 23:37:13 +01:00
.board .note.editing {
2020-02-08 12:02:45 -05:00
box-shadow: none;
outline: 1px solid #8eaedd;
}
2020-02-20 23:37:13 +01:00
.board .note.editing .text {
2020-02-08 12:02:45 -05:00
display: none;
}
2020-02-20 23:37:13 +01:00
.board .note.editing .edit {
2020-02-08 12:02:45 -05:00
display: block;
resize: none;
}
/***/
2020-02-20 23:37:13 +01:00
.board .note .ops {
2020-02-08 12:02:45 -05:00
position: absolute;
top: 0;
right: 0;
opacity: 0;
transition: opacity 400ms;
cursor: default;
2021-04-05 11:50:02 +02:00
font-size: calc(9rem / 11);
2020-02-08 12:02:45 -05:00
}
2020-02-20 23:37:13 +01:00
.board .note.editing .ops {
2020-02-08 12:02:45 -05:00
display: none;
}
2020-02-20 23:37:13 +01:00
.board .note:hover .ops {
2020-02-08 12:02:45 -05:00
opacity: 1;
}
2020-02-20 23:37:13 +01:00
.board .note .ops .teaser {
2020-02-08 12:02:45 -05:00
display: block;
margin-top: 2px;
margin-right: 1px;
padding-right: 3px;
}
2020-02-20 23:37:13 +01:00
.board .note .ops .teaser:before {
2020-02-08 12:02:45 -05:00
content: '\2261';
}
2020-02-20 23:37:13 +01:00
.board .note .ops .bulk {
2020-02-08 12:02:45 -05:00
display: none;
background: #fff;
border-left: none;
2021-04-05 11:50:02 +02:00
padding: 1px 0 2px 5px;
font-size: calc( var(--fs-nops) * 1px );
2020-02-08 12:02:45 -05:00
font-weight: 500;
border-left: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
2020-02-20 23:37:13 +01:00
.board .note .ops .bulk a {
2020-02-08 12:02:45 -05:00
padding-right: 4px;
}
2020-02-20 23:37:13 +01:00
.board .note .ops:hover .bulk {
2020-02-08 12:02:45 -05:00
display: block;
}
2020-02-20 23:37:13 +01:00
.board .note .ops:hover .teaser {
2020-02-08 12:02:45 -05:00
display: none;
}
/***/
2020-02-20 23:37:13 +01:00
.board .note.raw {
2020-02-08 12:02:45 -05:00
background: transparent;
box-shadow: none;
font-weight: 500;
}
2020-02-20 23:37:13 +01:00
.board .note.raw.editing {
2020-02-08 12:02:45 -05:00
background: #fff;
}
2020-02-20 23:37:13 +01:00
.board .note.raw .text {
2020-02-08 12:02:45 -05:00
}
2021-04-05 11:50:02 +02:00
/***/
2020-02-20 23:37:13 +01:00
.board .note.collapsed {
2021-04-05 11:50:02 +02:00
padding-bottom: 6px;
2020-02-08 12:02:45 -05:00
}
2021-04-05 11:50:02 +02:00
.board .note.collapsed .text,
.note-dragster.collapsed .text {
max-height: calc( var(--lh) * 1px );
2020-02-08 12:02:45 -05:00
overflow: hidden;
2021-04-05 11:50:02 +02:00
padding-bottom: 0;
2020-02-08 12:02:45 -05:00
}
2020-02-20 23:37:13 +01:00
.board .note.collapsed .ops {
2020-02-08 12:02:45 -05:00
opacity: 1;
}
2020-02-20 23:37:13 +01:00
.board .note.collapsed .ops .teaser {
2020-02-08 12:02:45 -05:00
padding: 1px 3px 0 1px;
}
2020-02-20 23:37:13 +01:00
.board .note.collapsed .ops .teaser:before {
2020-02-08 12:02:45 -05:00
content: '_';
top: 1px;
}
2020-02-20 23:37:13 +01:00
.board .note.collapsed:hover .ops .teaser:before {
2020-02-08 12:02:45 -05:00
content: '\2261';
}
/***/
2021-04-02 01:59:34 +02:00
.note-dragster {
2020-02-08 12:02:45 -05:00
z-index: 2;
position: absolute;
opacity: 0;
background: #fff;
white-space: pre-wrap;
cursor: move;
2021-04-05 11:50:02 +02:00
padding: 5px 25px 6px 10px;
2020-02-08 12:02:45 -05:00
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;
}
2021-04-02 01:59:34 +02:00
.note-dragster a {
2020-02-20 01:13:00 +01:00
color: #000;
}
2020-02-08 12:02:45 -05:00
/***/
.logo {
position: absolute;
top: 9px;
left: 9px;
2021-04-05 11:50:02 +02:00
line-height: calc(19 / 11);
2020-02-08 12:02:45 -05:00
padding: 6px 12px;
z-index: 3;
2021-04-10 14:12:32 +02:00
opacity: 0.6;
2020-02-08 12:02:45 -05:00
background: #f8f9fb;
}
body.crowded .logo:hover {
background: #fff;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.logo:hover {
opacity: 1.0;
}
2021-04-10 14:12:32 +02:00
.logo .alert {
display: none;
font-style: normal;
margin-left: 5px;
color: #d20;
}
2020-02-08 12:02:45 -05:00
.logo .bulk {
overflow: hidden;
height: 0;
opacity: 0;
transition: opacity 400ms;
2021-04-05 11:50:02 +02:00
padding-top: 1px;
2020-02-08 12:02:45 -05:00
}
.logo:hover .bulk {
height: auto;
opacity: 1;
}
2021-04-10 14:12:32 +02:00
.logo a.site,
.logo .bulk a {
2020-02-08 12:02:45 -05:00
color: #000;
}
2021-04-10 14:12:32 +02:00
.logo .bulk a:hover {
2020-02-08 12:02:45 -05:00
color: #1489db;
}
2021-04-10 14:12:32 +02:00
.logo .bulk a {
display: block;
}
2020-02-08 12:02:45 -05:00
.logo .bulk a:before {
display: inline-block;
content: '-';
width: 11px;
}
2021-04-10 14:12:32 +02:00
/***/
.logo.updated {
opacity: 1;
}
.logo.updated .alert {
display: inline-block;
}
.logo.updated .bulk .view-changes {
color: #d20;
}
2020-02-08 12:02:45 -05:00
/***/
.config {
position: absolute;
top: 10px;
right: 21px;
2021-04-06 23:54:46 +02:00
line-height: calc(19 / 11);
2020-02-08 12:02:45 -05:00
z-index: 3;
background: #f8f9fb;
}
2021-04-08 13:24:35 +02:00
.crowded .config:hover,
.crowded.adjusting .config {
2020-02-08 12:02:45 -05:00
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;
2021-04-14 13:54:03 +02:00
position: relative;
}
2021-04-14 17:00:49 +02:00
.config .teaser i {
/* backups off */
font-style: normal;
}
2021-04-14 13:54:03 +02:00
.config .teaser u {
2021-04-14 17:00:49 +02:00
/* backups on */
2021-04-14 13:54:03 +02:00
display: none;
text-decoration: none;
2020-02-08 12:02:45 -05:00
}
.config .bulk {
margin-right: 20px;
2021-04-05 11:50:02 +02:00
padding: 5px 0 0 22px;
2020-02-08 12:02:45 -05:00
transition: opacity 400ms;
}
2021-04-05 11:50:02 +02:00
.config .bulk .boards {
2020-02-08 12:02:45 -05:00
display: none;
padding: 6px 2px;
margin: 6px -2px;
border-top: 1px solid #00000028;
border-bottom: 1px solid #00000028;
}
2021-04-05 11:50:02 +02:00
.config .bulk .boards a.load-board {
2020-02-08 12:02:45 -05:00
display: block;
2021-04-06 23:54:46 +02:00
padding-left: 5px;
margin-left: -5px;
transition: none;
2020-02-08 12:02:45 -05:00
}
2021-04-05 11:50:02 +02:00
.config .bulk .boards a.load-board.active {
2020-02-08 12:02:45 -05:00
color: #1489db;
}
2021-04-05 11:50:02 +02:00
.config .bulk .boards a.load-board.active:before {
2020-02-08 12:02:45 -05:00
content: '\2022 ';
display: inline-block;
width: 13px;
margin-left: -13px;
}
2020-01-30 11:19:37 -05:00
2021-04-05 11:50:02 +02:00
.config .bulk .boards a.load-board.active:hover {
2021-04-02 01:59:34 +02:00
color: #000;
}
2021-04-05 11:50:02 +02:00
.config .bulk .boards a.load-board.dragging {
2021-04-02 01:59:34 +02:00
background: #DDE1E5;
border-radius: 2px;
color: transparent;
2021-04-06 23:54:46 +02:00
transition: none;
2021-04-05 11:50:02 +02:00
box-shadow: 0 0 1px #0002 inset;
2021-04-02 01:59:34 +02:00
}
2021-04-06 23:54:46 +02:00
.config .bulk .boards a.load-board.dragging.active:before {
content: '';
}
2021-04-02 01:59:34 +02:00
.load-dragster {
z-index: 10;
position: absolute;
opacity: 0;
2021-04-05 11:50:02 +02:00
line-height: calc(19 / 11);
2021-04-02 01:59:34 +02:00
padding-left: 5px;
background: #fff;
2020-02-08 12:02:45 -05:00
color: #000;
2021-04-02 01:59:34 +02:00
border-radius: 2px;
cursor: move;
2021-04-06 23:54:46 +02:00
outline: 1px solid #ACB4BC;
box-shadow: 0px 1px 3px #00000030;
2020-02-08 12:02:45 -05:00
}
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;
}
2021-04-14 13:54:03 +02:00
.config .bulk .break {
padding: 6px 2px 0;
margin: 6px -2px 0;
border-top: 1px solid #00000028;
}
/***/
2020-02-08 12:02:45 -05:00
.config .bulk input.imp-board-select {
position: absolute;
width: 1px;
height: 1px;
visibility: hidden;
}
2021-04-14 13:54:03 +02:00
/***/
2021-04-14 17:00:49 +02:00
.config.backups-on .teaser i {
display: none;
}
2021-04-14 13:54:03 +02:00
.config.backups-on .teaser u {
display: inline-block;
font-size: calc(8rem / 11);
color: #555;
}
2021-04-14 17:00:49 +02:00
.config.backups-on.backing-up .teaser u {
2021-04-14 13:54:03 +02:00
opacity: 0.4;
}
.config.backups-on .bulk .auto-backup:before {
content: '\2713 ';
display: inline-block;
width: 15px;
margin-left: -15px;
}
/***/
2021-04-14 17:00:49 +02:00
.config.backups-on.backup-err .teaser i {
display: inline-block;
2021-04-14 13:54:03 +02:00
}
.config.backups-on.backup-err .teaser u {
display: inline-block;
position: absolute;
width: 4px;
height: 4px;
border-radius: 10px;
top: 13px;
right: -4px;
background: #d20;
2021-04-14 17:00:49 +02:00
color: transparent;
2021-04-14 13:54:03 +02:00
}
.config.backups-on.backup-err .bulk .auto-backup {
color: #d20;
}
.config.backups-on.backup-err .bulk .auto-backup:before {
content: '! ';
color: #d20;
width: 13px;
margin-left: -13px;
2020-02-19 14:25:11 +01:00
}
2021-04-05 11:50:02 +02:00
/***/
2021-04-10 14:12:32 +02:00
2021-04-06 16:51:20 +02:00
.config .bulk .section .title {
2021-04-05 11:50:02 +02:00
text-align: center;
width: 0;
display: inline-block;
white-space: pre;
2021-04-06 16:51:20 +02:00
transition: width 250ms, padding 250ms;
2021-04-05 11:50:02 +02:00
}
2021-04-06 20:21:08 +02:00
.config .bulk .section .title u {
text-decoration: none;
2021-04-06 16:51:20 +02:00
transition: opacity 100ms;
2021-04-05 11:50:02 +02:00
}
2021-04-06 16:51:20 +02:00
.config .bulk .section .details {
height: 0;
2021-04-05 11:50:02 +02:00
opacity: 0;
transition: opacity 250ms;
2021-04-05 18:20:23 +02:00
padding-right: 8px;
margin-right: -8px;
}
2021-04-06 13:21:48 +02:00
/***/
2021-04-06 16:51:20 +02:00
.config .bulk .section.open {
padding-bottom: 8px;
border-bottom: 1px solid #00000028;
}
2021-04-06 19:54:47 +02:00
.crowded .config .bulk .section.open {
border-bottom: none;
}
2021-04-06 16:51:20 +02:00
.config .bulk .section.open .title {
2021-04-06 13:21:48 +02:00
width: 100%;
font-weight: 500;
2021-04-06 16:51:20 +02:00
padding-left: 5px;
transition: width 250ms, padding 250ms;
2021-04-06 13:21:48 +02:00
}
2021-04-06 20:21:08 +02:00
.config .bulk .section.open .title u {
2021-04-06 16:51:20 +02:00
opacity: 0;
transition: opacity 100ms;
2021-04-06 13:21:48 +02:00
}
2021-04-06 16:51:20 +02:00
.config .bulk .section.open .details {
height: 100%;
2021-04-06 13:21:48 +02:00
opacity: 1;
transition: opacity 250ms;
}
/***/
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .f-prefs table {
2021-04-10 14:12:32 +02:00
width: 10px; /* mobile safari */
2021-04-05 18:20:23 +02:00
padding: 0;
margin: 0;
border-collapse: collapse;
}
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .f-prefs td {
2021-04-05 18:20:23 +02:00
margin: 0;
padding: 0 0 0 5px;
}
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .f-prefs td.name {
2021-04-05 18:20:23 +02:00
width: 100%;
white-space: pre;
padding-left: 0;
}
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .f-prefs tr td.val {
2021-04-05 18:20:23 +02:00
text-align: center;
}
2021-04-06 18:37:26 +02:00
.fs-set .config .bulk .ui-prefs .f-prefs tr.ui-fs td.val,
2021-04-06 19:32:46 +02:00
.lh-set .config .bulk .ui-prefs .f-prefs tr.ui-lh td.val,
.lw-set .config .bulk .ui-prefs .f-prefs tr.ui-lw td.val {
2021-04-06 13:21:48 +02:00
color: #1489db;
cursor: pointer;
}
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .f-prefs a {
2021-04-05 18:20:23 +02:00
display: inline-block;
padding: 0 2px;
transition: padding 250ms;
}
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .f-prefs a.less:hover {
2021-04-05 18:20:23 +02:00
padding: 0 4px 0 0;
transition: padding 250ms;
}
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .f-prefs a.more:hover {
padding: 0 0 0 4px;
transition: padding 250ms;
}
.config .bulk .ui-prefs .f-prefs a.more:hover {
2021-04-05 18:20:23 +02:00
padding: 0 0 0 4px;
transition: padding 250ms;
2021-04-05 11:50:02 +02:00
}
/***/
2021-04-06 16:51:20 +02:00
.config .bulk .ui-prefs .switch-font.active:before {
2021-04-05 11:50:02 +02:00
content: '\2022 ';
display: inline-block;
width: 13px;
margin-left: -13px;
}
2020-02-08 12:02:45 -05:00
/***/
.config .bulk {
display: none;
opacity: 0;
}
2021-04-08 13:24:35 +02:00
.config:hover .teaser,
.adjusting .config .teaser {
2020-02-08 12:02:45 -05:00
display: none;
}
2021-04-08 13:24:35 +02:00
.config:hover .bulk,
.adjusting .config .bulk {
2020-02-08 12:02:45 -05:00
display: block;
opacity: 1;
}
2021-04-07 18:51:52 +02:00
/***/
.adjusting * {
cursor: row-resize !important;
}
2020-02-08 12:02:45 -05:00
/***/
.overlay {
position: fixed;
z-index: 10;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
2020-02-19 12:46:15 +01:00
color: #000;
2021-04-18 13:55:59 +02:00
display: none; /* flex */
justify-content: center;
2020-02-08 12:02:45 -05:00
}
2021-04-18 13:55:59 +02:00
.overlay > div {
2021-04-05 11:50:02 +02:00
font-size: calc(12rem / 11);
line-height: calc(16 / 12);
2021-04-18 13:55:59 +02:00
background: #fff;
align-self: start;
margin-top: 100px;
border-radius: 1px;
box-shadow: 0 2px 6px #000;
}
/***/
.overlay > div.license {
2020-02-08 12:02:45 -05:00
white-space: pre-wrap;
padding: 20px 25px 22px;
overflow-y: auto;
2021-04-05 11:50:02 +02:00
width: calc( (var(--fs) * 36 + 6) * 1px ); /* 402px, 476px */
2020-02-08 12:02:45 -05:00
}
2021-04-18 13:55:59 +02:00
.overlay > div.license a {
2020-02-08 12:02:45 -05:00
color: #1489db;
}
2021-04-18 13:55:59 +02:00
.overlay > div.license span {
2020-02-08 12:02:45 -05:00
display: inline-block;
padding-right: 8px;
}
/***/
2021-04-18 13:55:59 +02:00
.overlay > div.about {
2020-02-08 12:02:45 -05:00
text-align: center;
2021-04-18 13:55:59 +02:00
padding: 50px 50px;
2021-08-04 12:44:59 +02:00
position: relative;
2020-02-08 12:02:45 -05:00
}
2021-04-18 13:55:59 +02:00
.overlay > div.about h1 {
2021-04-05 11:50:02 +02:00
font-size: calc(15rem / 11);
2020-02-08 12:02:45 -05:00
font-weight: 500;
margin-bottom: 10px;
}
2021-04-18 13:55:59 +02:00
.overlay > div.about a {
2020-02-08 12:02:45 -05:00
display: block;
color: #1489db;
}
2021-04-18 13:55:59 +02:00
.overlay > div.about div {
2020-02-08 12:02:45 -05:00
position: absolute;
bottom: -30px;
2021-08-04 12:44:59 +02:00
left: 0;
width: 100%;
color: #fff9;
2020-02-08 12:02:45 -05:00
}
2021-04-18 13:55:59 +02:00
.overlay > div.about div span {
2020-02-08 12:02:45 -05:00
display: inline-block;
text-align: left;
padding-right: 10px;
}
/***/
2021-04-18 13:55:59 +02:00
.overlay .backup-conf {
padding: 20px 40px;
min-width: 300px;
2021-04-19 17:52:26 +02:00
position: relative;
}
.overlay .backup-conf .close {
position: absolute;
top: 0px;
right: 12px;
font-size: calc(22rem / 11);
color: #000;
opacity: 0.3;
transition: opacity 300ms;
}
.overlay .backup-conf .close:hover {
opacity: 1;
transition: opacity 300ms;
2021-04-18 13:55:59 +02:00
}
.overlay .backup-conf h3 {
font-size: calc(15rem / 11);
font-weight: 500;
text-align: center;
margin: 20px 0 0 0;
}
.overlay .backup-conf .what {
display: block;
text-align: center;
border-bottom: 1px solid #888;
margin: 0 -10px 20px;
padding: 0 10px 30px;
}
.overlay .backup-conf .what a {
color: #1489db;
}
.overlay .backup-conf .opt {
margin-top: 5px;
}
.overlay .backup-conf .opt:before {
--col: #333;
content: ' ';
display: inline-block;
margin-top: -1px;
display: inline-block;
content: ' ';
width: calc(9rem / 11);
height: calc(9rem / 11);
margin-right: 8px;
position: relative;
top: 1px;
background: var(--col);
border: 1px solid var(--col);
border-radius: 1px;
box-shadow: 0 2px 0 #fff inset, 0 -2px 0 #fff inset, 2px 0 0 #fff inset, -2px 0 0 #fff inset;
}
.overlay .backup-conf .opt:hover:before {
--col: #1489db;
}
.overlay .backup-conf .off .opt:before {
background: #fff;
}
.overlay .backup-conf .opt:hover {
color: #1489db;
}
.overlay .backup-conf .opt {
cursor: pointer;
}
.overlay .backup-conf .etc {
2021-04-19 17:52:26 +02:00
margin-top: 3px;
2021-04-18 13:55:59 +02:00
margin-bottom: 10px;
2021-04-18 22:36:39 +02:00
margin-left: calc( 9rem / 22 );
2021-04-19 17:52:26 +02:00
padding: 2px 0 0 14px;
2021-04-18 22:36:39 +02:00
border-left: 1px solid #888;
2021-04-18 13:55:59 +02:00
}
.overlay .backup-conf .off .etc {
display: none;
}
.overlay .backup-conf .etc span {
display: inline-block;
min-width: calc( 3px + var(--fs) * 7px );
margin-right: calc( var(--fs) * 1px );
}
.overlay .backup-conf .etc input {
display: inline-block;
width: 170px;
padding: 0 5px;
line-height: calc(20 / 11);
margin-bottom: 3px;
2021-04-18 22:36:39 +02:00
border: 1px solid #ccc;
2021-04-18 13:55:59 +02:00
}
.overlay .backup-conf .etc input:focus {
2021-04-18 22:36:39 +02:00
border-bottom: 1px solid #888;
2021-04-18 13:55:59 +02:00
}
.overlay .backup-conf .etc input::placeholder {
font-weight: 400;
font-size: calc(10rem / 11);
line-height: calc(20 / 10);
text-transform: uppercase;
color: #000;
opacity: 0.4;
}
2021-04-18 22:36:39 +02:00
.overlay .backup-conf .etc .status {
2021-04-18 13:55:59 +02:00
display: none;
margin: 0;
}
2021-04-18 22:36:39 +02:00
.overlay .backup-conf .etc .status input {
color: #222;
background: #0000000E;
border: 1px solid transparent;
}
.overlay .backup-conf .etc .status.error input {
color: #d20;
background: #dd220018;
2021-04-18 13:55:59 +02:00
}
.overlay .backup-conf .save {
display: block;
text-align: center;
margin: 20px -10px 5px;
padding: 20px 10px 0;
border-top: 1px solid #888;
word-spacing: 30px;
2021-04-19 17:52:26 +02:00
position: relative;
2021-04-18 13:55:59 +02:00
}
.overlay .backup-conf .save a {
color: #1489db;
}
2021-04-19 17:52:26 +02:00
.overlay .backup-conf.off .save .check {
color: #aaa;
pointer-events: none;
cursor: default;
}
2021-04-18 13:55:59 +02:00
/***/
2020-02-08 12:02:45 -05:00
@media print {
.logo, .config,
.board .head .teaser,
.list .head .teaser {
visibility: hidden;
display: none;
}
2020-02-20 23:37:13 +01:00
.board .note {
2020-02-08 12:02:45 -05:00
box-shadow: none;
outline: 1px solid #ccc;
}
2020-02-20 23:37:13 +01:00
.board .note.raw {
2020-02-08 12:02:45 -05:00
outline: none;
}
}
2020-02-12 09:08:28 -05:00
/*
2020-02-19 03:50:03 +01:00
* Dark mode
*/
2021-04-06 23:54:46 +02:00
.theme-dark body {
background: #09090A;
2020-02-19 03:50:03 +01:00
color: #d6d6d6;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head {
2021-04-06 23:54:46 +02:00
background: #17171a;
2020-02-12 09:08:28 -05:00
}
2020-02-19 03:50:03 +01:00
/***/
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu {
2021-04-06 23:54:46 +02:00
background: linear-gradient(to right, #17171a00, #17171a 10px);
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu a {
2020-02-19 03:50:03 +01:00
color: #aaa;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu a:hover {
2020-02-19 03:50:03 +01:00
color: #fc4;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .menu a.warn:hover,
.theme-dark .board .ops a.warn:hover {
2020-02-19 03:50:03 +01:00
color: #fc5555 !important;
2021-04-05 11:50:02 +02:00
text-shadow: 0 0 1px #fc555544;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head {
2020-02-19 03:50:03 +01:00
color: #eee;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head .edit::placeholder {
2020-02-19 12:42:00 +01:00
color: #bf9d21;
}
2021-04-06 23:54:46 +02:00
.theme-dark .board > .head {
box-shadow: 0 0 2px #fff1 inset;
}
2020-02-19 03:50:03 +01:00
/***/
2021-03-31 18:27:33 +02:00
.theme-dark .board .list {
2021-04-06 23:54:46 +02:00
background: #17171a;
box-shadow: 0 0 2px #fff1 inset;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note {
2021-04-06 23:54:46 +02:00
background: linear-gradient(#242426, #202022);
box-shadow: 0 1px 3px #000a;
2020-02-19 03:50:03 +01:00
text-shadow: 0 0 4px #0008;
2020-02-12 09:08:28 -05:00
}
2021-04-06 23:54:46 +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 {
2020-02-19 03:50:03 +01:00
background: transparent;
2020-02-14 08:19:49 -05:00
box-shadow: none;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.raw .text {
2020-02-19 03:50:03 +01:00
color: #eee;
text-shadow: 0 0 4px #0008;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .teaser {
2020-02-19 03:50:03 +01:00
color: #ccc;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .bulk {
2021-04-06 23:54:46 +02:00
background: #202022;
2020-02-19 03:50:03 +01:00
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 {
2020-02-19 03:50:03 +01:00
border: none;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .bulk a {
2020-02-19 03:50:03 +01:00
color: #ccc;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note .ops .bulk a:hover {
2020-02-19 03:50:03 +01:00
color: #fc2;
}
2021-04-02 01:59:34 +02:00
/***/
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.dragging,
.theme-dark .board .note.dragging.raw {
2021-04-06 23:54:46 +02:00
background: #09090A;
outline: 1px solid #fff2;
2020-02-19 03:50:03 +01:00
}
2021-04-02 01:59:34 +02:00
.theme-dark .note-dragster {
2021-04-06 23:54:46 +02:00
box-shadow: 0px 1px 4px #000;
background: linear-gradient(#242426, #202022);
outline: 1px solid #fc28;
2020-02-19 03:50:03 +01:00
}
2021-04-02 01:59:34 +02:00
.theme-dark .note-dragster a {
2020-02-20 01:13:00 +01:00
color: #d6d6d6;
}
2020-02-19 03:50:03 +01:00
/***/
2021-04-22 16:32:02 +02:00
.theme-dark .wrap textarea,
.theme-dark .wrap input {
2020-02-19 03:50:03 +01:00
background: #111315;
color: #eee;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .head.editing .edit {
2020-02-19 03:50:03 +01:00
outline: 1px solid #bc9908;
}
2021-03-31 18:27:33 +02:00
.theme-dark .board .note.editing {
2020-02-19 03:50:03 +01:00
background: #111315;
outline: 1px solid #bc9908;
}
2021-04-06 23:54:46 +02:00
/***/
.theme-dark .logo a {
color: #ccc;
}
.theme-dark .logo,
.theme-dark.crowded .logo:hover {
background: #09090A;
}
.theme-dark .logo a:hover {
color: #fc2;
}
.theme-dark .logo > a:hover {
color: #fff;
}
2020-02-19 03:50:03 +01:00
/***/
2021-04-05 11:50:02 +02:00
.theme-dark .config,
.theme-dark.crowded .config:hover {
2021-04-06 23:54:46 +02:00
background: #09090A !important;
2020-02-12 09:08:28 -05:00
}
2021-03-31 18:27:33 +02:00
.theme-dark .config a {
2020-02-12 09:08:28 -05:00
color: #ddd;
}
2021-03-31 18:27:33 +02:00
.theme-dark .config a:hover {
2020-02-19 03:50:03 +01:00
color: #fc2;
}
2021-04-05 11:50:02 +02:00
.theme-dark .config .bulk .boards {
2020-02-19 14:25:11 +01:00
border-top: 1px solid #fff2;
border-bottom: 1px solid #fff2;
}
2021-04-05 11:50:02 +02:00
.theme-dark .config .bulk .boards a.load-board.active {
2020-02-19 03:50:03 +01:00
color: #fc2;
}
2021-04-05 11:50:02 +02:00
.theme-dark .config .bulk .boards a.load-board.active:hover {
2021-04-02 01:59:34 +02:00
color: #ddd;
}
2021-04-05 11:50:02 +02:00
.theme-dark .config .bulk .break {
2020-02-19 14:25:11 +01:00
border-top: 1px solid #fff2;
}
2021-04-06 23:54:46 +02:00
.theme-dark.fs-set .config .bulk .ui-prefs .f-prefs tr.ui-fs td.val,
.theme-dark.lh-set .config .bulk .ui-prefs .f-prefs tr.ui-lh td.val,
.theme-dark.lw-set .config .bulk .ui-prefs .f-prefs tr.ui-lw td.val {
2021-04-06 16:51:20 +02:00
color: #fc2;
}
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-04-02 01:59:34 +02:00
/***/
2021-04-06 23:54:46 +02:00
.theme-dark .config .bulk .boards .load-board.dragging,
.theme-dark .config .bulk .boards .load-board.active.dragging {
background: #fff2;
color: transparent;
2021-04-02 01:59:34 +02:00
}
.theme-dark .load-dragster {
2021-04-07 18:51:52 +02:00
background: #09090A;
2021-04-02 01:59:34 +02:00
box-shadow: 0px 2px 4px #000;
2021-04-06 23:54:46 +02:00
outline: 1px solid #fc2a;
color: #ddd;
2020-02-12 09:08:28 -05:00
}
2020-02-08 12:02:45 -05:00
< / style >
< / head >
2019-05-28 12:09:54 +02:00
< body >
2021-04-10 14:12:32 +02:00
< div class = "logo" >
< a class = site href = https://nullboard.io > Nullboard< / a >
< i class = alert > < / i >
2020-02-08 12:02:45 -05:00
< div class = bulk >
< a href = # class = view-about > About< / a >
< a href = # class = view-license > License< / a >
2021-04-10 14:12:32 +02:00
< a href = https://nullboard.io/changes target = _blank class = view-changes > Changes< / a >
2020-02-08 12:02:45 -05:00
< 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
2021-04-06 16:51:20 +02:00
< div class = 'config no-user-select' >
2021-04-14 17:00:49 +02:00
< a href = # class = teaser > < i > ≡ < / i > < u > ✔ < / u > < / a >
2020-02-08 12:02:45 -05:00
< div class = bulk >
< a href = # class = add-board > Add new board...< / a >
2021-04-05 11:50:02 +02:00
2021-04-06 23:54:46 +02:00
< div class = boards draggable = false >
2019-05-28 12:09:54 +02:00
<!-- here'll be boards -->
< / div >
2021-04-05 11:50:02 +02:00
2021-03-31 20:20:49 +02:00
< a href = # class = exp-board > Export current board...< / a >
2021-03-31 20:49:29 +02:00
< a href = # class = imp-board > Import boards...< / a >
2020-02-08 12:02:45 -05:00
< input class = imp-board-select type = "file" accept = ".nbx" >
2021-04-05 11:50:02 +02:00
2021-04-14 13:54:03 +02:00
< a href = # class = auto-backup > Auto-backup...< / a >
2021-04-06 16:51:20 +02:00
< div class = "section ui-prefs break" >
2021-04-06 20:21:08 +02:00
< a href = # class = title > UI preferences< u > ...< / u > < / a >
2021-04-06 16:51:20 +02:00
< div class = details >
2021-04-05 11:50:02 +02:00
< a href = "#" class = "switch-font active break" font = "barlow" > Use Barlow< / a >
2021-04-06 13:21:48 +02:00
< a href = "#" class = "switch-font" font = "ibm-plex" > Use IBM Plex Sans< / a >
< a href = "#" class = "switch-font" font = "open-sans" > Use Open Sans< / a >
< a href = "#" class = "switch-font" font = "segoe-ui" > Use Segoe UI< / a >
< a href = "#" class = "switch-font" font = "maven-pro" > Use Maven Pro< / a >
2021-04-06 16:51:20 +02:00
< div class = 'f-prefs break' >
2021-04-06 13:21:48 +02:00
< table >
2021-04-06 19:32:46 +02:00
< tr class = 'ui-fs' > < td class = name > Font size:< / td > < td > < a href = # class = less > < < / a > < / td > < td class = val > < / td > < td > < a href = # class = more > > < / a > < / td > < / tr >
< tr class = 'ui-lh' > < td class = name > Line height:< / td > < td > < a href = # class = less > < < / a > < / td > < td class = val > < / td > < td > < a href = # class = more > > < / a > < / td > < / tr >
< tr class = 'ui-lw' > < td class = name > List width:< / td > < td > < a href = # class = less > < < / a > < / td > < td class = val > < / td > < td > < a href = # class = more > > < / a > < / td > < / tr >
2021-04-05 18:20:23 +02:00
< / table >
< / div >
2021-04-05 11:50:02 +02:00
< a href = "#" class = "switch-theme break" > Use < i > light< / i > < b > dark< / b > theme< / a >
< / div >
< / div >
2019-05-28 12:09:54 +02:00
< / div >
< / div >
2020-02-08 12:02:45 -05:00
< / div >
2019-05-28 12:09:54 +02:00
2020-02-08 12:02:45 -05:00
< div class = wrap >
2019-05-28 12:09:54 +02:00
< / div >
2020-02-08 12:02:45 -05:00
< div class = overlay >
2019-05-28 12:09:54 +02:00
< / div >
< tt >
<!-- templates -->
2020-02-08 12:02:45 -05:00
< 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 > ≡ < / 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 >
2020-02-08 12:02:45 -05:00
< div class = lists-scroller > < div > < / div > < / div >
< div class = lists >
2019-05-28 12:09:54 +02:00
< / div >
< / div >
2020-02-08 12:02:45 -05:00
< 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 > ≡ < / a >
< div class = bulk >
< a href = # class = 'del-list warn' > < u > < / u > List< / a >
< a href = # class = 'mov-list-l full' > < Move< / a >
< a href = # class = 'mov-list-l half' > < Mo< / a > < a href = # class = 'mov-list-r half' > ve > < / a >
< a href = # class = 'mov-list-r full' > Move > < / a >
< a href = # class = 'add-note' > + Note< / a >
2019-05-28 12:09:54 +02:00
< / div >
< / div >
< / div >
2020-02-08 12:02:45 -05:00
< div class = notes >
2019-05-28 12:09:54 +02:00
< / div >
< / div >
2020-02-08 12:02:45 -05:00
< div class = note >
< div class = ops >
< a href = # class = teaser > < / a >
< div class = bulk >
2021-04-05 11:50:02 +02:00
< a href = # class = 'del-note warn' > X< / a >
2020-02-08 12:02:45 -05:00
< a href = # class = 'raw-note' > R< / a >
< a href = # class = 'collapse' > _< / a >
2019-05-28 12:09:54 +02:00
< / div >
< / div >
2021-04-05 11:50:02 +02:00
< div class = text > < / div >
2020-02-08 12:02:45 -05:00
< textarea class = edit spellcheck = false > < / textarea >
2019-05-28 12:09:54 +02:00
< / div >
2021-04-06 23:54:46 +02:00
< a href = # class = load-board > < / a >
2019-05-28 12:09:54 +02:00
2020-02-08 12:02:45 -05:00
< textarea class = encoder > < / textarea >
2019-05-28 12:09:54 +02:00
2020-02-08 12:02:45 -05:00
< div class = about >
2019-05-28 12:09:54 +02:00
< h1 > Nullboard< / h1 >
Minimalist locally-stored kanban board
2020-02-08 12:02:45 -05:00
< a href = https://nullboard.io target = _blank > https://nullboard.io< / a >
2019-05-28 12:09:54 +02:00
< div > < / div >
< / div >
2020-02-08 12:02:45 -05:00
< div class = license >
2019-05-28 12:09:54 +02:00
< / div >
2021-04-18 13:55:59 +02:00
< div class = backup-conf >
2021-04-19 17:52:26 +02:00
< a href = # class = close > × < / a >
2021-04-18 13:55:59 +02:00
< h3 > Auto-backup< / h3 >
< div class = what >
< a href = https://nullboard.io/backups target = _blank > What is this?< / a >
< / div >
< div class = loc >
< div class = opt > Local backup< / div >
< div class = etc >
< span > Access token< / span > < input class = auth placeholder = "required" > < br >
2021-04-18 22:36:39 +02:00
< p class = status > < span > Status< / span > < input disabled > < / p >
2021-04-18 13:55:59 +02:00
< / div >
< / div >
< div class = rem >
< div class = opt > Remote backup< / div >
< div class = etc >
< span > URL< / span > < input class = base placeholder = "API entry point" > < br >
< span > Access token< / span > < input class = auth placeholder = "required" > < br >
2021-04-18 22:36:39 +02:00
< p class = status > < span > Status< / span > < input disabled > < / p >
2021-04-18 13:55:59 +02:00
< / div >
< / div >
2021-04-19 17:52:26 +02:00
< div class = save > < a href = # class = check > Check...< / a > < a href = # class = ok > Save< / a > < / div >
2021-04-18 13:55:59 +02:00
< / div >
2019-05-28 12:09:54 +02:00
< / 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
2021-04-07 18:51:52 +02:00
< script type = "text/javascript" >
Number.prototype.clamp = function(min, max)
{
return Math.min(Math.max(this, min), max);
};
< / script >
2019-05-28 12:09:54 +02:00
< script type = "text/javascript" >
2020-02-08 12:02:45 -05:00
2021-03-31 18:27:33 +02:00
function AppConfig()
2020-02-08 12:02:45 -05:00
{
2021-04-18 13:55:59 +02:00
this.verLast = null; // last used codeVersion
this.verSeen = null; // latest codeVersion they saw the changelog for
2021-04-10 14:12:32 +02:00
2021-04-18 13:55:59 +02:00
this.maxUndo = 50; // board revisions to keep
this.fontName = null; // font-family
this.fontSize = null; // font-size
this.lineHeight = null; // line-height
this.listWidth = null; // list-width
this.theme = null; // default or 'dark'
2021-04-10 14:12:32 +02:00
2021-08-04 12:40:53 +02:00
this.fileLinks = false; // mark up `foo` as < a href = file:///foo > ...< / a >
2021-04-18 13:55:59 +02:00
this.board = null; // active board
2021-04-25 14:37:57 +02:00
this.backups =
{
agents : [ ], // [ { type, id, enabled, conf } ];
nextId : 1
};
2021-04-25 16:04:11 +02:00
2021-04-25 20:33:33 +02:00
this.backupStatus = { }; // agentId => [ 'conf' ]
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 BoardMeta()
2020-02-08 12:02:45 -05:00
{
2021-03-31 18:27:33 +02:00
this.title = '';
2021-04-13 19:05:14 +02:00
this.current = 1; // revision
this.ui_spot = 0; // 0 = not set
this.history = [ ]; // revision IDs
2021-04-25 20:33:33 +02:00
this.backupStatus = { }; // agentId => [ what's backed up ]
2019-05-28 12:09:54 +02:00
}
2021-03-31 18:27:33 +02:00
class Storage
2020-02-08 12:02:45 -05:00
{
2021-03-31 22:12:53 +02:00
constructor()
{
this.type = '?';
2021-04-01 19:12:43 +02:00
this.conf = new AppConfig();
2021-03-31 22:12:53 +02:00
this.boardIndex = new Map();
2021-04-13 19:05:14 +02:00
2021-04-25 14:37:57 +02:00
this.backups =
{
status : '', // '', 'ok', 'busy', 'failed'
agents : [ ], // BackupStorage instances
};
2021-03-31 22:12:53 +02:00
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
open()
2020-02-08 12:02:45 -05:00
{
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()
2020-02-08 12:02:45 -05:00
{
2021-03-31 18:27:33 +02:00
return this.conf;
2019-05-28 12:09:54 +02:00
}
2021-04-25 14:37:57 +02:00
setVerLast()
2021-04-10 14:12:32 +02:00
{
2021-04-25 14:37:57 +02:00
if (this.conf.verLast == NB.codeVersion)
return true;
this.conf.verLast = NB.codeVersion;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
2021-04-10 14:12:32 +02:00
}
setVerSeen(ver)
{
this.conf.verSeen = ver || NB.codeVersion;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
2021-04-10 14:12:32 +02:00
}
2021-03-31 18:27:33 +02:00
setActiveBoard(board_id)
{
2021-04-25 14:37:57 +02:00
console.log('setActiveBoard [' + this.conf.board + '] -> [' + board_id + ']');
2021-03-31 18:27:33 +02:00
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-04-13 19:05:14 +02:00
if (this.conf.board == board_id)
return true;
2021-03-31 18:27:33 +02:00
this.conf.board = board_id;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
2021-03-31 18:27:33 +02:00
}
2020-02-20 01:13:00 +01:00
2021-03-31 18:27:33 +02:00
setTheme(theme)
{
2021-04-14 17:15:13 +02:00
if (this.conf.theme == theme) return;
2021-03-31 18:27:33 +02:00
this.conf.theme = theme;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
2021-03-31 18:27:33 +02:00
}
2020-02-20 01:13:00 +01:00
2021-04-06 15:29:12 +02:00
setFontName(fname)
2021-03-31 18:27:33 +02:00
{
2021-04-14 17:15:13 +02:00
if (this.conf.fontName == fname) return;
2021-04-06 19:09:14 +02:00
this.conf.fontName = fname;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
2021-03-31 18:27:33 +02:00
}
2020-02-20 01:13:00 +01:00
2021-04-06 15:29:12 +02:00
setFontSize(fs)
2021-04-05 11:50:02 +02:00
{
2021-04-14 17:15:13 +02:00
if (this.conf.fontSize == fs) return;
2021-04-06 19:09:14 +02:00
this.conf.fontSize = fs;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
2021-04-05 11:50:02 +02:00
}
2021-04-06 16:51:20 +02:00
setLineHeight(lh)
{
2021-04-14 17:15:13 +02:00
if (this.conf.lineHeight == lh) return;
2021-04-06 19:09:14 +02:00
this.conf.lineHeight = lh;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
2021-04-06 16:51:20 +02:00
}
2021-04-06 19:32:46 +02:00
setListWidth(lw)
{
2021-04-14 17:15:13 +02:00
if (this.conf.listWidth == lw) return;
2021-04-06 19:32:46 +02:00
this.conf.listWidth = lw;
2021-04-13 19:05:14 +02:00
return this.saveConfig();
}
saveConfig()
{
2021-04-14 13:54:03 +02:00
this.backupConfig();
2021-04-06 19:32:46 +02:00
return this.setJson('config', this.conf);
}
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 19:32:01 +02:00
delete board.history; // remove temporarily
2021-03-31 18:27:33 +02:00
if (! meta)
{
2021-03-31 19:32:01 +02:00
board.revision = 1;
ok_data = this.setJson('board.' + board.id + '.' + board.revision, board);
2021-03-31 18:27:33 +02:00
meta = new BoardMeta();
2021-03-31 19:32:01 +02:00
meta.title = board.title || '(Untitled board)';
meta.current = board.revision;
meta.history = [ board.revision ];
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.boardIndex.set(board.id, meta);
}
2021-03-31 19:32:01 +02:00
else
{
var rev_old = board.revision;
var rev_new = meta.history[0] + 1;
2019-05-28 12:09:54 +02:00
2021-03-31 19:32:01 +02:00
board.revision = rev_new;
2019-05-28 12:09:54 +02:00
2021-03-31 19:32:01 +02:00
ok_data = this.setJson('board.' + board.id + '.' + board.revision, board);
2019-05-28 12:09:54 +02:00
2021-03-31 19:32:01 +02:00
meta.title = board.title || '(Untitled board)';
meta.current = board.revision;
2019-05-28 12:09:54 +02:00
2021-03-31 19:32:01 +02:00
// trim revisions skipped over with undo and cap the revision count
2019-05-28 12:09:54 +02:00
2021-03-31 19:32:01 +02:00
var rebuild = [ board.revision ];
2019-05-28 12:09:54 +02:00
2021-03-31 19:32:01 +02:00
for (var rev of meta.history)
2021-03-31 18:27:33 +02:00
{
2021-04-06 19:09:14 +02:00
if ( (rev_old < rev & & rev < rev_new ) | | ( rebuild . length > = this.conf.maxUndo) )
2021-03-31 19:32:01 +02:00
{
this.delItem('board.' + board.id + '.' + rev);
console.log( `Deleted revision ${rev} of ${board.id} (${board.title})` );
}
else
{
rebuild.push(rev);
}
2021-03-31 18:27:33 +02:00
}
2019-05-28 12:09:54 +02:00
2021-03-31 19:32:01 +02:00
meta.history = rebuild;
}
2019-05-28 12:09:54 +02:00
2021-04-13 19:05:14 +02:00
/*
* save meta
*/
2021-04-02 12:37:26 +02:00
ok_meta = this.setJson('board.' + board.id + '.meta', meta) & &
2021-04-05 11:50:02 +02:00
this.setJson('board.' + board.id, meta.current); // for older versions
2019-05-28 12:09:54 +02:00
2021-04-13 19:05:14 +02:00
/*
* run backups
*/
if (ok_meta & & ok_data)
2021-04-14 13:54:03 +02:00
this.backupBoard(board.id, board, meta)
2021-04-13 19:05:14 +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)
2020-02-08 12:02:45 -05:00
{
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 19:32:01 +02:00
if (board.format != NB.blobVersion)
2021-03-31 18:27:33 +02:00
{
console.log('Board ' + board_id + '/' + revision + ' format is unsupported');
2021-03-31 19:32:01 +02:00
console.log('Have [' + board.format + '], need [' + NB.blobVersion);
2021-03-31 18:27:33 +02:00
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 20:35:46 +02:00
nukeBoard(board_id)
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 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 20:35:46 +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 20:35:46 +02:00
this.delItem('board.' + board_id + '.meta');
this.boardIndex.delete(board_id);
2021-03-30 10:18:01 +02:00
2021-04-25 14:37:57 +02:00
this.backups.agents.forEach(function(store){
2021-04-13 19:05:14 +02:00
store.nukeBoard(board_id);
});
2021-03-31 20:35:46 +02:00
console.log( `Deleted board ${board_id} (${title})` );
2021-03-31 18:27:33 +02:00
}
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;
2021-04-22 16:32:02 +02:00
this.backupBoard(board_id, null, meta);
2021-04-14 13:54:03 +02:00
2021-04-02 12:37:26 +02:00
return this.setJson('board.' + board_id + '.meta', meta) & &
this.setJson('board.' + board_id, revision); // for older versions
2019-05-28 12:09:54 +02:00
}
2021-04-02 01:59:34 +02:00
setBoardUiSpot(board_id, ui_spot)
{
var meta = this.boardIndex.get(board_id);
if (! meta)
throw `Invalid board_id in setBoardRevision(${board_id}, ${revision})`;
meta.ui_spot = ui_spot;
2021-04-25 16:04:11 +02:00
this.backupBoard(board_id, null, meta);
2021-04-02 01:59:34 +02:00
return this.setJson('board.' + board_id + '.meta', meta);
}
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
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;
}
2021-04-13 19:05:14 +02:00
2021-04-19 11:49:23 +02:00
/*
* config
*/
fixupConfig(newInstall)
{
var conf = this.conf;
var simp = (new SimpleBackup).type;
if (conf.board & & ! this.boardIndex.has(conf.board))
conf.board = null;
if (! conf & & ! newInstall) // pre-20210410 upgrade
{
conf.verLast = 20210327;
conf.verSeen = 20200220; // 20200429;
}
2021-04-25 14:37:57 +02:00
var agents = conf.backups.agents;
if (agents.length != 2 ||
agents[0].type != simp || agents[0].conf.base != 'http://127.0.0.1:10001' ||
agents[1].type != simp)
2021-04-19 11:49:23 +02:00
{
2021-04-25 14:37:57 +02:00
console.log('Unexpected backup config, will re-initialize.', agents);
conf.backups.agents = [];
2021-04-19 11:49:23 +02:00
2021-04-25 14:37:57 +02:00
conf.backups.agents.push({
2021-04-19 11:49:23 +02:00
type: simp,
2021-04-25 14:37:57 +02:00
id: simp + '-' + (conf.backups.nextId++),
2021-04-19 11:49:23 +02:00
enabled: false,
conf: { base: 'http://127.0.0.1:10001', auth: '' }
})
2021-04-25 14:37:57 +02:00
conf.backups.agents.push({
2021-04-19 11:49:23 +02:00
type: simp,
2021-04-25 14:37:57 +02:00
id: simp + '-' + (conf.backups.nextId++),
2021-04-19 11:49:23 +02:00
enabled: false,
conf: { base: '', auth: '' }
})
2021-04-25 16:30:17 +02:00
this.saveConfig();
2021-04-19 11:49:23 +02:00
}
}
2021-04-14 13:54:03 +02:00
/*
* backups
*/
2021-04-25 14:37:57 +02:00
initBackups(onBackupStatus)
2021-04-13 19:05:14 +02:00
{
var self = this;
2021-04-14 13:54:03 +02:00
var pending = 0;
var success = true;
2021-04-14 14:37:25 +02:00
var store_id = 1;
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
self.backups.agents = [];
2021-04-14 17:00:49 +02:00
2021-04-25 14:37:57 +02:00
onBackupStatus(null);
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
this.conf.backups.agents.forEach(function(b){
2021-04-18 13:55:59 +02:00
var T = NB.backupTypes.get(b.type);
2021-04-14 13:54:03 +02:00
if (! T)
{
2021-04-18 13:55:59 +02:00
console.log( `Unknown backup type "${b.type}" - skipped` );
2021-04-14 13:54:03 +02:00
return;
}
2021-04-18 13:55:59 +02:00
if (! b.enabled)
return;
2021-04-25 14:37:57 +02:00
var agent = new T(b.id, b.conf, onBackupStatus);
self.backups.agents.push(agent);
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
console.log( `Added backup agent - type '${agent.type}', id '${agent.id}'` );
2021-04-18 13:55:59 +02:00
2021-04-25 14:37:57 +02:00
agent.checkStatus(null); // will need just onBackupStatus() callbacks
2021-04-14 13:54:03 +02:00
});
}
backupBoard(board_id, board, meta)
{
var self = this;
2021-04-25 20:33:33 +02:00
var was = meta.backupStatus || {};
2021-04-25 16:04:11 +02:00
2021-04-25 20:33:33 +02:00
meta.backupStatus = {};
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
if (! this.backups.agents.length)
2021-04-14 13:54:03 +02:00
{
2021-04-25 20:33:33 +02:00
if (was['data'] || was['meta'])
2021-04-25 16:04:11 +02:00
self.setJson('board.' + board_id + '.meta', meta);
2021-04-14 13:54:03 +02:00
return;
}
2021-04-25 14:37:57 +02:00
console.log( `Backing up ${board_id}...` );
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
this.backups.agents.forEach(function(agent){
2021-04-14 13:54:03 +02:00
2021-04-25 20:33:33 +02:00
var fields = was[agent.id] || {};
2021-04-25 16:04:11 +02:00
if (board) delete fields.data;
if (meta) delete fields.meta;
2021-04-25 20:33:33 +02:00
meta.backupStatus[agent.id] = fields;
2021-04-25 16:04:11 +02:00
2021-04-25 16:37:15 +02:00
agent.saveBoard(board_id, board, meta, function(ok){
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
var what = 'Backup of ' + board_id + (board ? '' : ' (meta)');
2021-04-25 20:33:33 +02:00
console.log( `${what} to '${agent.id}' -> ${ok ? 'ok' : 'failed'}` );
2021-04-25 14:37:57 +02:00
2021-04-25 16:37:15 +02:00
if (ok)
2021-04-25 16:04:11 +02:00
{
if (board) fields.data = + new Date();
if (meta) fields.meta = + new Date();
2021-04-25 20:33:33 +02:00
meta.backupStatus[agent.id] = fields;
2021-04-25 16:04:11 +02:00
}
2021-04-14 13:54:03 +02:00
self.setJson('board.' + board_id + '.meta', meta);
});
});
}
backupConfig()
{
var self = this;
2021-04-25 20:33:33 +02:00
var was = self.conf.backupStatus || {};
2021-04-25 16:04:11 +02:00
2021-04-25 20:33:33 +02:00
self.conf.backupStatus = {};
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
if (! this.backups.agents.length)
2021-04-25 16:04:11 +02:00
{
2021-04-25 20:33:33 +02:00
if (was['conf'])
2021-04-25 16:04:11 +02:00
this.setJson('config', this.conf);
2021-04-14 13:54:03 +02:00
return;
2021-04-25 16:04:11 +02:00
}
2021-04-14 13:54:03 +02:00
2021-04-25 14:37:57 +02:00
this.backups.agents.forEach(function(agent){
2021-04-25 16:04:11 +02:00
var fields = { };
2021-04-25 20:33:33 +02:00
self.conf.backupStatus[agent.id] = fields;
2021-04-25 16:04:11 +02:00
2021-04-25 16:37:15 +02:00
agent.saveConfig(self.conf, function(ok){
2021-04-25 16:04:11 +02:00
2021-04-25 16:37:15 +02:00
if (ok)
2021-04-25 16:04:11 +02:00
{
fields.conf = + new Date()
2021-04-25 20:33:33 +02:00
self.conf.backupStatus[agent.id] = fields;
2021-04-25 16:04:11 +02:00
}
2021-04-25 16:30:17 +02:00
self.setJson('config', self.conf);
2021-04-25 16:04:11 +02:00
});
2021-04-13 19:05:14 +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
class Storage_Local extends Storage
2020-02-08 12:02:45 -05:00
{
2021-03-31 22:12:53 +02:00
constructor()
{
super();
this.type = 'LocalStorage';
}
2021-03-31 19:32:01 +02:00
2021-03-31 18:27:33 +02:00
getItem(name)
2020-02-08 12:02:45 -05:00
{
2021-03-31 18:27:33 +02:00
return localStorage.getItem('nullboard.' + name);
2020-02-08 12:02:45 -05:00
}
2021-03-31 18:27:33 +02:00
setItem(name, val)
2020-02-08 12:02:45 -05:00
{
2021-03-31 18:27:33 +02:00
localStorage.setItem('nullboard.' + name, val);
return true;
2020-02-08 12:02:45 -05:00
}
2021-03-31 18:27:33 +02:00
delItem(name)
2020-02-08 12:02:45 -05:00
{
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');
2021-04-10 14:32:14 +02:00
var newInstall = true;
2019-05-28 12:09:54 +02:00
2021-04-25 16:04:11 +02:00
// if (conf & & (conf.format != NB.confVersion))
// {
// if (! confirm('Preferences are stored in an unsupported format. Reset them?'))
// return false;
//
// conf = null;
// }
2021-03-31 19:32:01 +02:00
if (conf)
{
2021-04-13 19:05:14 +02:00
this.conf = Object.assign(new AppConfig(), conf);
2021-03-31 18:27:33 +02:00
}
else
{
this.conf.theme = this.getItem('theme');
2019-05-28 12:09:54 +02:00
2021-04-06 15:29:12 +02:00
if (this.getItem('fsize') == 'z1')
{
2021-04-06 19:09:14 +02:00
this.conf.fontSize = 13;
this.conf.lineHeight = 17;
2021-04-06 15:29:12 +02:00
}
2021-03-31 18:27:33 +02:00
if (! this.setJson('config', this.conf))
{
this.conf = null;
return false;
}
2021-04-10 14:12:32 +02:00
this.conf.board = this.getItem('last_board');
2021-03-31 18:27:33 +02:00
}
2019-06-10 22:57:14 +02:00
2021-03-31 18:27:33 +02:00
this.boardIndex = new Map();
2019-06-10 22:57:14 +02:00
2021-04-05 11:50:02 +02:00
// new format
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$/);
2019-05-28 12:09:54 +02:00
2021-04-05 11:50:02 +02:00
if (! m)
continue;
var board_id = parseInt(m[1]);
var meta = this.getJson('board.' + board_id + '.meta');
if (! meta.hasOwnProperty('history'))
2021-03-31 18:27:33 +02:00
{
2021-04-05 11:50:02 +02:00
console.log( `Invalid meta for board ${board_id}` );
continue;
}
2019-05-28 12:09:54 +02:00
2021-04-05 11:50:02 +02:00
for (var rev of meta.history)
if (! this.getJson('board.' + board_id + '.' + rev))
2021-03-31 18:27:33 +02:00
{
2021-04-05 11:50:02 +02:00
console.log( `Invalid revision ${rev} in history of ${board_id}` );
meta = this.rebuildMeta(board_id);
break;
2021-03-31 18:27:33 +02:00
}
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-04-25 14:37:57 +02:00
delete meta.backingUp; // run-time var
delete meta.needsBackup; // ditto
2021-03-31 18:27:33 +02:00
meta = Object.assign(new BoardMeta(), meta);
2021-04-05 11:50:02 +02:00
this.boardIndex.set(board_id, meta);
}
// old format
for (var i=0; i< localStorage.length ; i + + )
{
var k = localStorage.key(i);
var m = k.match(/^nullboard\.board\.(\d+)$/);
if (! m)
continue;
2021-04-10 14:32:14 +02:00
newInstall = false;
2021-04-05 11:50:02 +02:00
var board_id = parseInt(m[1]);
if (this.boardIndex.has(board_id))
continue;
var meta = this.rebuildMeta(board_id);
if (! meta)
continue;
meta = Object.assign(new BoardMeta(), meta);
this.boardIndex.set(board_id, meta);
2021-03-31 18:27:33 +02:00
}
2019-06-10 22:57:14 +02:00
2021-04-19 11:49:23 +02:00
this.fixupConfig(newInstall);
2021-04-10 14:32:14 +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
/*
* private
*/
rebuildMeta(board_id)
{
var meta = new BoardMeta();
2019-05-28 12:09:54 +02:00
2021-04-05 11:50:02 +02:00
console.log( `Rebuilding meta for ${board_id} ...` );
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)
2021-04-05 11:50:02 +02:00
{
console.log('* No revisions found');
this.delItem('board.' + board_id);
2021-03-31 18:27:33 +02:00
return false;
2021-04-05 11:50:02 +02:00
}
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-04-13 19:05:14 +02:00
/*
*
*/
class BackupStorage
{
2021-04-25 14:37:57 +02:00
constructor(id, conf, onStatusChange)
2021-04-13 19:05:14 +02:00
{
2021-04-25 14:37:57 +02:00
this.type = '?';
this.id = id;
this.conf = conf;
this.status = '';
this.lastOp = '';
this.lastXhr = { op: '', text: '', code: 0 };
this.onStatusChange = onStatusChange;
this.queue = [];
2021-04-13 19:05:14 +02:00
}
checkStatus(cb) { return false; }
saveConfig(conf, cb) { throw 'implement-me'; }
saveBoard (id, data, meta, cb) { throw 'implement-me'; }
nukeBoard (id, cb) { throw 'implement-me'; }
}
class SimpleBackup extends BackupStorage
{
2021-04-25 14:37:57 +02:00
constructor(id, conf, onStatusChange)
2021-04-13 19:05:14 +02:00
{
2021-04-25 14:37:57 +02:00
super(id, null, onStatusChange);
2021-04-18 13:55:59 +02:00
2021-04-14 14:37:25 +02:00
this.type = 'simp';
2021-04-18 13:55:59 +02:00
this.conf = { base: '', auth: '' }
2021-04-13 19:05:14 +02:00
this.conf = Object.assign(this.conf, conf);
}
checkStatus(cb)
{
2021-04-25 14:37:57 +02:00
this.queue.push({
what : 'checkStatus',
cb : cb,
args :
2021-04-22 16:32:02 +02:00
{
2021-04-25 14:37:57 +02:00
url: this.conf.base + '/config',
type: 'put',
headers: { 'X-Access-Token': this.conf.auth },
data:
{
self: document.location.href,
// conf: -- without the data --
},
dataType: 'json'
}
});
this.runQueue();
2021-04-13 19:05:14 +02:00
}
saveConfig(conf, cb)
{
2021-04-25 14:37:57 +02:00
this.queue.push({
what : 'saveConfig',
cb : cb,
args :
2021-04-22 16:32:02 +02:00
{
2021-04-25 14:37:57 +02:00
url: this.conf.base + '/config',
type: 'put',
headers: { 'X-Access-Token': this.conf.auth },
data:
{
self: document.location.href,
conf: JSON.stringify(conf)
},
dataType: 'json'
}
});
this.runQueue();
2021-04-13 19:05:14 +02:00
}
saveBoard(id, data, meta, cb)
{
2021-04-25 14:37:57 +02:00
this.queue.push({
what : 'saveBoard',
cb : cb,
args :
2021-04-13 19:05:14 +02:00
{
2021-04-25 14:37:57 +02:00
url: this.conf.base + '/board/' + id,
type: 'put',
headers: { 'X-Access-Token': this.conf.auth },
data:
{
self: document.location.href,
data: data ? JSON.stringify(data) : null,
meta: meta ? JSON.stringify(meta) : null
},
dataType: 'json'
}
});
this.runQueue();
2021-04-13 19:05:14 +02:00
}
nukeBoard(id, cb)
{
2021-04-25 14:37:57 +02:00
this.queue.push({
what : 'saveBoard',
cb : cb,
args :
{
url: this.conf.base + '/board/' + id,
type: 'delete',
headers: { 'X-Access-Token': this.conf.auth },
}
});
2021-04-13 19:05:14 +02:00
2021-04-25 14:37:57 +02:00
this.runQueue();
2021-04-19 13:09:04 +02:00
}
/*
* private
*/
2021-04-25 14:37:57 +02:00
runQueue()
2021-04-19 13:09:04 +02:00
{
2021-04-25 14:37:57 +02:00
var self = this;
if (! this.queue.length)
return;
if (this.status == 'busy')
return;
var req = this.queue.shift();
this.setStatus('busy', req.what);
$.ajax(req.args)
.done(function(d, s, x) { self.onRequestDone(req, true, x); })
.fail(function(x, s, e) { self.onRequestDone(req, false, x); })
}
onRequestDone(req, ok, xhr)
{
console.log( `Backup agent '${this.id}', ${this.lastOp}() -> ${ok ? 'ok' : 'failed'}` );
var code = xhr.status;
var text = xhr.responseText || (code ? `Response code ${code}` : 'Offline or CORS-blocked');
this.lastXhr = { text: text, code: code };
2021-04-25 16:37:15 +02:00
if (req.cb) req.cb.call(this, ok);
2021-04-25 14:37:57 +02:00
2021-04-25 16:37:15 +02:00
if (! this.queue.length)
{
this.setStatus(ok ? 'ready' : 'error', this.lastOp);
return;
}
2021-04-25 14:37:57 +02:00
2021-04-25 16:37:15 +02:00
this.status = 'pre-busy';
2021-04-25 14:37:57 +02:00
this.runQueue();
}
setStatus(status, op)
{
if (status == 'busy' & & this.status == 'busy')
throw `Backup agent ${this.id} is already busy!`;
2021-04-25 20:33:33 +02:00
console.log( `Backup agent '${this.id}' status: '${this.status}' -> '${status}'` );
2021-04-25 14:37:57 +02:00
this.status = status;
this.lastOp = op;
this.onStatusChange(this);
2021-04-13 19:05:14 +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)
2020-02-08 12:02:45 -05:00
{
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)
2020-02-08 12:02:45 -05:00
{
2021-03-31 19:32:01 +02:00
this.format = NB.blobVersion;
2021-03-31 18:27:33 +02:00
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)
2020-02-08 12:02:45 -05:00
{
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-04-01 19:12:43 +02:00
function Drag2()
2020-02-08 12:02:45 -05:00
{
2021-04-01 19:12:43 +02:00
// config
this.listSel = null;
this.itemSel = null;
2021-04-02 01:59:34 +02:00
this.dragster = null;
2021-04-01 19:12:43 +02:00
this.onDragging = function(started) { }
this.swapAnimMs = 200;
// state
this.item = null;
2021-03-31 18:27:33 +02:00
this.priming = null;
2021-04-01 19:12:43 +02:00
this.primeXY = { x: 0, y: 0 };
2021-03-31 18:27:33 +02:00
this.$drag = null;
2021-04-01 19:12:43 +02:00
this.mouseEv = null;
2021-03-31 18:27:33 +02:00
this.delta = { x: 0, y: 0 };
2021-04-01 19:12:43 +02:00
this.inSwap = 0;
2019-05-28 12:09:54 +02:00
2021-04-01 19:12:43 +02:00
// api
2021-03-31 18:27:33 +02:00
this.prime = function(item, ev)
{
var self = this;
2021-04-01 19:12:43 +02:00
2021-03-31 18:27:33 +02:00
this.item = item;
this.priming = setTimeout(function(){ self.onPrimed.call(self); }, ev.altKey ? 1 : 500);
2021-04-01 19:12:43 +02:00
this.primeXY = { x: ev.clientX, y: ev.clientY };
this.mouseEv = ev;
2021-03-31 18:27:33 +02:00
}
2019-05-28 12:09:54 +02:00
2021-03-31 18:27:33 +02:00
this.cancelPriming = function()
{
2021-04-01 19:12:43 +02:00
if (! this.item || ! this.priming)
return;
clearTimeout(this.priming);
this.priming = null;
this.item = null;
2021-03-31 18:27:33 +02:00
}
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;
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
removeTextSelection();
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
var $item = $(this.item);
$item.addClass('dragging');
2021-03-30 10:18:01 +02:00
2021-04-02 01:59:34 +02:00
$('body').append('< div class = ' + this.dragster + ' > < / div > ');
var $drag = $('body .' + this.dragster).last();
2021-03-30 10:18:01 +02:00
2021-04-05 11:50:02 +02:00
$drag.outerWidth ( $item.outerWidth() );
$drag.outerHeight( $item.outerHeight() );
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-04-01 19:12:43 +02:00
if (this.onDragging)
this.onDragging.call(this, true); // started
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-04-01 19:12:43 +02:00
var pos = $item.offset();
this.delta.x = pos.left - this.mouseEv.clientX - scroll_x;
this.delta.y = pos.top - this.mouseEv.clientY - scroll_y;
2021-04-02 01:59:34 +02:00
2021-03-31 18:27:33 +02:00
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-04-01 19:12:43 +02:00
var drag = this;
var $drag = this.$drag;
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-04-01 19:12:43 +02:00
var drag_x = drag.mouseEv.clientX + drag.delta.x + scroll_x;
var drag_y = drag.mouseEv.clientY + drag.delta.y + scroll_y;
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
$drag.offset({ left: drag_x, top: drag_y });
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (drag.inSwap)
2021-03-31 18:27:33 +02:00
return;
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
/*
* see if a swap is in order
*/
2021-04-01 19:12:43 +02:00
var pos = $drag.offset();
var x = pos.left + $drag.width()/2 - $win.scrollLeft();
var y = pos.top + $drag.height()/2 - $win.scrollTop();
2021-03-30 10:18:01 +02:00
2021-04-14 16:17:21 +02:00
var targetList = null;
var targetItem = null; // if over some item
var before = false; // if should go before targetItem
var $target;
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
$(this.listSel).each(function(){
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
var list = this;
2021-04-01 19:12:43 +02:00
var rcList = list.getBoundingClientRect();
var yTop, itemTop = null;
var yBottom, itemBottom = null;
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (x < = rcList.left || rcList.right < = x)
2021-03-31 18:27:33 +02:00
return;
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
$(list).find(drag.itemSel).each(function(){
var rcItem = this.getBoundingClientRect();
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (! itemTop || rcItem.top < yTop )
{
itemTop = this;
yTop = rcItem.top;
}
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (! itemBottom || yBottom < rcItem.bottom )
2021-03-31 18:27:33 +02:00
{
2021-04-01 19:12:43 +02:00
itemBottom = this;
yBottom = rcItem.bottom;
2021-03-31 18:27:33 +02:00
}
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (y < = rcItem.top || rcItem.bottom < = y)
2021-03-31 18:27:33 +02:00
return;
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (this == drag.item)
2021-03-31 18:27:33 +02:00
return;
2021-03-30 10:18:01 +02:00
2021-04-14 16:17:21 +02:00
targetList = list;
targetItem = this;
2021-04-01 19:12:43 +02:00
before = (y < (rcItem.top + rcItem.bottom)/2);
2021-03-31 18:27:33 +02:00
});
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (y < rcList.top )
2021-03-31 18:27:33 +02:00
{
2021-04-14 16:17:21 +02:00
targetList = list;
targetItem = itemTop;
2021-04-01 19:12:43 +02:00
before = true;
2021-03-31 18:27:33 +02:00
}
2021-04-01 19:12:43 +02:00
else
if (y >= rcList.bottom)
{
2021-04-14 16:17:21 +02:00
targetList = list;
targetItem = itemBottom;
2021-04-01 19:12:43 +02:00
before = false;
}
2021-03-31 18:27:33 +02:00
});
2021-03-30 10:18:01 +02:00
2021-04-14 16:17:21 +02:00
if (! targetList)
2021-03-31 18:27:33 +02:00
return;
2021-03-30 10:18:01 +02:00
2021-04-14 16:17:21 +02:00
if (targetItem)
{
if (targetItem == drag.item)
return;
2021-03-30 10:18:01 +02:00
2021-04-14 16:17:21 +02:00
$target = $(targetItem);
if (! before & & $target.next()[0] == drag.item ||
before & & $target.prev()[0] == drag.item)
return;
}
2021-03-30 10:18:01 +02:00
2021-03-31 18:27:33 +02:00
/*
* swap 'em
*/
2021-04-01 19:12:43 +02:00
var have = drag.item;
var $have = $(have);
2021-03-31 18:27:33 +02:00
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-04-14 16:17:21 +02:00
if (targetItem)
2021-04-01 19:12:43 +02:00
{
2021-04-14 16:17:21 +02:00
if (before)
{
$want.insertBefore($target);
$want = $target.prev();
}
else
{
$want.insertAfter($target);
$want = $target.next();
}
2021-04-01 19:12:43 +02:00
}
else
2021-03-31 18:27:33 +02:00
{
2021-04-14 16:17:21 +02:00
var $list = $(targetList);
$want = $list.append($want).find(drag.itemSel)
2021-04-01 19:12:43 +02:00
}
drag.item = $want[0];
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (! drag.swapAnimMs)
{
$have.remove();
$want.show();
return;
}
/*
* see if it's a same-list move
*/
2021-04-14 16:17:21 +02:00
if (targetList == have.parentNode)
2021-04-01 19:12:43 +02:00
{
var delta = $have.offset().top - $target.offset().top;
var d_bulk = 0;
var d_have = 0;
var $bulk = $();
if (delta < 0 ) / / item is moving down
2021-03-31 18:27:33 +02:00
{
2021-04-01 19:12:43 +02:00
for (var $i = $have.next(); $i.length & & $i[0] != $want[0]; $i = $i.next())
$bulk = $bulk.add($i);
2021-03-31 18:27:33 +02:00
}
else
{
2021-04-01 19:12:43 +02:00
for (var $i = $want.next(); $i.length & & $i[0] != $have[0]; $i = $i.next())
$bulk = $bulk.add($i);
2021-03-31 18:27:33 +02:00
}
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
d_bulk = $have.outerHeight(true);
d_have = $bulk.last().offset().top + $bulk.last().outerHeight(true) - $bulk.first().offset().top;
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
if (delta < 0 ) d_bulk = -d_bulk;
else d_have = -d_have;
2021-03-31 18:27:33 +02:00
2021-04-01 19:12:43 +02:00
$have.parent().css({ position: 'relative' });
$have.css({ position: 'relative', 'z-index': 0 });
$bulk.css({ position: 'relative', 'z-index': 1 });
2021-03-31 18:27:33 +02:00
2021-04-01 19:12:43 +02:00
drag.inSwap = 1 + $bulk.length;
2021-03-31 18:27:33 +02:00
2021-04-01 19:12:43 +02:00
$have.animate({ top: d_have }, drag.swapAnimMs, function(){ if (! --drag.inSwap) swapCleanUp(); });
$bulk.animate({ top: d_bulk }, drag.swapAnimMs, function(){ if (! --drag.inSwap) swapCleanUp(); });
2021-03-31 18:27:33 +02:00
2021-04-01 19:12:43 +02:00
function swapCleanUp()
{
$have.parent().css({ position: '' });
2021-03-31 20:45:26 +02:00
$have.remove();
2021-04-01 19:12:43 +02:00
$want.show();
$bulk.css({ position: '', 'z-index': '', top: '' });
2021-03-31 20:45:26 +02:00
drag.adjustDrag();
}
2021-04-01 19:12:43 +02:00
}
else
{
drag.inSwap = 1;
$want.slideDown(drag.swapAnimMs);
$have.slideUp(drag.swapAnimMs, function() {
$have.remove();
drag.inSwap = 0;
drag.adjustDrag();
});
}
2021-03-31 18:27:33 +02:00
}
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-04-01 19:12:43 +02:00
this.mouseEv = ev;
2021-03-31 18:27:33 +02:00
if (! this.item)
2021-03-30 10:18:01 +02:00
return;
2021-03-31 18:27:33 +02:00
if (this.priming)
{
2021-04-01 19:12:43 +02:00
var x = ev.clientX - this.primeXY.x;
var y = ev.clientY - this.primeXY.y;
2021-03-31 18:27:33 +02:00
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-04-01 19:12:43 +02:00
var $item = $(this.item);
2021-03-30 10:18:01 +02:00
2021-04-01 19:12:43 +02:00
$item.removeClass('dragging');
2021-03-31 18:27:33 +02:00
$('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-04-01 19:12:43 +02:00
removeTextSelection();
2021-03-31 18:27:33 +02:00
2021-04-01 19:12:43 +02:00
if (this.onDragging)
this.onDragging.call(this, false); // stopped
2021-03-31 18:27:33 +02:00
}
this.item = null;
}
2021-03-30 10:18:01 +02:00
}
2021-03-31 18:27:33 +02:00
< / script >
2021-04-07 18:51:52 +02:00
< script type = "text/javascript" >
function VarAdjust()
{
// state
this.onChange = null;
2021-04-14 17:15:13 +02:00
this.onFinish = null;
2021-04-07 18:51:52 +02:00
this.startY = 0;
this.used = false;
// api
2021-04-14 17:15:13 +02:00
this.start = function(ev, onChange, onFinish)
2021-04-07 18:51:52 +02:00
{
if (! onChange)
return;
this.onChange = onChange;
2021-04-14 17:15:13 +02:00
this.onFinish = onFinish;
2021-04-07 18:51:52 +02:00
this.startY = ev.clientY;
this.used = false;
var self = this;
setTimeout(function(){
2021-04-08 13:24:35 +02:00
if (! self.onChange)
return;
$('body').addClass('adjusting');
self.used = true;
}, 250);
2021-04-07 18:51:52 +02:00
}
this.onMouseMove = function(ev)
{
if (! this.onChange)
return;
$('body').addClass('adjusting');
self.used = true;
this.onChange(ev.clientY - this.startY);
}
this.end = function()
{
if (! this.onChange)
return;
$('body').removeClass('adjusting');
this.onChange = null;
2021-04-14 17:15:13 +02:00
if (this.onFinish) this.onFinish();
2021-04-07 18:51:52 +02:00
}
}
< / script >
2021-03-31 18:27:33 +02:00
< script type = "text/javascript" >
/*
* poor man's error handling -- $fixme
*/
2021-03-31 19:32:01 +02:00
var easyMartina = false;
2021-03-31 18:27:33 +02:00
window.onerror = function(message, file, line, col, e){
var cb1;
2021-03-31 19:32:01 +02:00
if (! easyMartina) alert("Error occurred: " + e.message);
2021-03-31 18:27:33 +02:00
return false;
};
window.addEventListener("error", function(e) {
var cb2;
2021-03-31 19:32:01 +02:00
if (! easyMartina) alert("Error occurred: " + e.error.message);
2021-03-31 18:27:33 +02:00
return false;
});
/*
2021-03-31 20:35:46 +02:00
* notes / lists / boards
2019-05-28 12:09:54 +02:00
*/
2021-03-31 18:27:33 +02:00
function addNote($list, $after, $before)
2020-02-08 12:02:45 -05:00
{
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-10-23 15:13:51 +02:00
if ($before & & $before.length)
2020-02-08 12:02:45 -05:00
{
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
2021-10-23 15:13:51 +02:00
if ($after & & $after.length)
2020-02-08 12:02:45 -05:00
{
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
2020-02-08 12:02:45 -05:00
{
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 20:35:46 +02:00
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-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)
{
2021-04-25 14:37:57 +02:00
if (! NB.board)
return;
2021-03-31 18:27:33 +02:00
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-04-13 19:05:14 +02:00
// updateUndoRedo();
2021-03-31 18:27:33 +02:00
updateBoardIndex();
2021-04-13 19:05:14 +02:00
updatePageTitle();
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 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 20:35:46 +02:00
function saveBoard()
{
var $board = $('.wrap .board');
2021-04-22 16:32:02 +02:00
var board = Object.assign(new Board(), NB.board); // id, revision & title
2021-03-31 20:35:46 +02:00
board.lists = [];
$board.find('.list').each(function(){
var $list = $(this);
var l = board.addList( getText($list.find('.head .text')) );
$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');
});
});
NB.storage.saveBoard(board);
NB.board = board;
updateUndoRedo();
updateBoardIndex();
}
2021-03-31 18:27:33 +02:00
function deleteBoard()
{
var $list = $('.wrap .board .list');
2021-04-13 19:05:14 +02:00
var board_id = NB.board.id;
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
closeBoard();
2021-04-13 19:05:14 +02:00
NB.storage.nukeBoard(board_id);
updateBoardIndex();
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 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)
2020-02-08 12:02:45 -05:00
{
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);
2020-02-08 12:02:45 -05:00
else
2021-03-31 18:27:33 +02:00
$wrap.html('')
.css({ opacity: 0 })
.append($b)
.animate({ opacity: 1 });
updatePageTitle();
updateUndoRedo();
updateBoardIndex();
setupListScrolling();
}
2021-03-31 20:35:46 +02:00
/*
* demo board
*/
function createDemoBoard()
{
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}]}]}';
var demo = JSON.parse(blob);
if (! demo)
return false;
demo.id = +new Date();
demo.revision = 0;
NB.storage.saveBoard(demo);
NB.storage.setActiveBoard(demo.id);
return Object.assign(new Board(), demo);
}
/*
* board export / import
*/
function exportBoard()
{
var blob, file;
if (! NB.board)
{
var index = NB.storage.getBoardIndex();
var all = [];
boards.forEach(function(meta, board_id){
all.push( NB.storage.loadBoard(board_id, null) );
})
blob = JSON.stringify(all);
file = `Nullboard.nbx`;
}
else
{
var board = NB.board;
blob = JSON.stringify(board);
file = `Nullboard-${board.id}-${board.title}.nbx`;
}
blob = encodeURIComponent(blob);
blob = "data:application/octet-stream," + blob;
return { blob: blob, file: file };
}
function checkImport(foo)
{
var props = [ 'format', 'id', 'revision', 'title', 'lists' ];
for (var i=0; i< props.length ; i + + )
if (! foo.hasOwnProperty(props[i]))
return "Required board properties are missing.";
if (! foo.id || ! foo.revision || ! Array.isArray(foo.lists))
return "Required board properties are empty.";
if (foo.format != NB.blobVersion)
return `Unsupported blob format "${board.format}", expecting "${NB.blobVersion}".`;
return null;
}
function importBoard(blob)
{
var data;
try
{
data = JSON.parse(blob);
}
catch (x)
{
alert('File is not in a valid JSON format.');
return false;
}
if (! Array.isArray(data))
data = [ data ];
var index = NB.storage.getBoardIndex();
var msg, one, all = '';
for (var i=0; i< data.length ; i + + )
{
var board = data[i];
var whoops = checkImport(board);
if (whoops)
{
alert(whoops);
return false;
}
var title = board.title || '(untitled board)';
one = `"${title}", ID ${board.id}, revision ${board.revision}`;
all += ` ID ${board.id}, revision ${board.revision} - "${title}" \n`;
}
if (data.length == 1) msg = `Import a board called ${one} ?`;
else msg = `About to import the following boards:\n\n${all}\nProceed?`;
if (! confirm(msg))
return false;
for (var i=0; i< data.length ; i + + )
{
var board = data[i];
if (index.has(board.id))
{
console.log(`Import: board ${board.id} (${board.title}) will be assigned new ID`);
board.id = +new Date();
}
board.revision--; // save will ++ it back
if (! NB.storage.saveBoard(board)) // this updates 'index'
{
alert(`Failed to save board ${board.id}. Import failed.`);
return false;
}
}
openBoard(data[0].id);
}
2021-04-13 19:05:14 +02:00
/*
*
*/
2021-04-25 14:37:57 +02:00
function findBackupAgent(which)
2021-04-18 13:55:59 +02:00
{
2021-04-25 14:37:57 +02:00
var a = null;
2021-04-18 13:55:59 +02:00
2021-04-25 14:37:57 +02:00
NB.storage.backups.agents.forEach(function(agent){
if (agent.type == which.type & &
agent.conf.auth == which.conf.auth & &
agent.conf.base == which.conf.base)
2021-04-18 13:55:59 +02:00
{
2021-04-25 14:37:57 +02:00
a = agent;
2021-04-18 13:55:59 +02:00
}
});
2021-04-25 14:37:57 +02:00
return a;
2021-04-18 13:55:59 +02:00
}
2021-04-19 17:52:26 +02:00
function setBackupConfigUi($div, backupConf)
2021-04-19 11:49:23 +02:00
{
if (! backupConf.enabled)
{
$div.addClass('off');
return;
}
var $status = $div.find('.status');
2021-04-25 14:37:57 +02:00
var b = findBackupAgent(backupConf);
2021-04-19 11:49:23 +02:00
var text = 'OK';
2021-04-25 16:30:17 +02:00
if (b & & b.status == 'error')
2021-04-19 11:49:23 +02:00
{
2021-04-25 16:30:17 +02:00
text = b.lastXhr.text;
2021-04-19 11:49:23 +02:00
$status.addClass('error');
}
$status.find('input').val(text);
$status.css({ display: 'block' });
}
2021-04-19 17:52:26 +02:00
function getBackupConfigUi()
{
var conf = NB.storage.getConfig();
2021-04-25 14:37:57 +02:00
var loc = conf.backups.agents[0];
var rem = conf.backups.agents[1];
2021-04-19 17:52:26 +02:00
var $div = $('.overlay .backup-conf');
var $loc = $div.find('.loc');
var $rem = $div.find('.rem');
2021-04-25 14:37:57 +02:00
var ret =
{
loc: jsonClone(loc),
2021-04-19 17:52:26 +02:00
rem: jsonClone(rem)
};
ret.loc.enabled = ! $loc.hasClass('off');
ret.loc.conf.auth = $loc.find('.auth').val();
ret.rem.enabled = ! $rem.hasClass('off');
ret.rem.conf.base = $rem.find('.base').val();
ret.rem.conf.auth = $rem.find('.auth').val();
//
if (ret.loc.enabled & & ! ret.loc.conf.auth)
{
shakeControl($loc.find('.auth'));
return null;
}
if (ret.rem.enabled & & ! ret.rem.conf.base)
{
shakeControl($rem.find('.base'));
return null;
}
if (ret.rem.enabled & & ! ret.rem.conf.auth)
{
shakeControl($rem.find('.auth'));
return null;
}
return ret;
}
2021-04-19 11:49:23 +02:00
function checkBackupConfig(backupConf, $div, onDone)
{
var $status = $div.find('.status');
var $text = $status.find('input');
$text.val('Checking...');
$status.removeClass('error').slideDown();
2021-04-19 13:09:04 +02:00
$div.delay(850).queue(function(){
var T = NB.backupTypes.get(backupConf.type);
2021-04-25 14:37:57 +02:00
var foo = new T(backupConf.id, backupConf.conf, function(){});
2021-04-19 13:09:04 +02:00
2021-04-25 16:37:15 +02:00
foo.checkStatus(function(ok){
2021-04-25 14:37:57 +02:00
2021-04-25 16:37:15 +02:00
if (ok)
2021-04-19 13:09:04 +02:00
{
$text.val('OK');
}
else
{
2021-04-25 14:37:57 +02:00
$text.val(foo.lastXhr.text);
2021-04-19 13:09:04 +02:00
$status.addClass('error');
}
onDone();
});
2021-04-19 11:49:23 +02:00
$(this).dequeue();
});
}
2021-04-18 13:55:59 +02:00
function configBackups()
2021-04-13 19:05:14 +02:00
{
var conf = NB.storage.getConfig();
2021-04-25 14:37:57 +02:00
if (conf.backups.agents.length != 2)
throw 'Invalid conf.backups.agents[]'; // as per fixupConfig()
2021-04-19 11:49:23 +02:00
//
2021-04-18 13:55:59 +02:00
var $div = $('tt .backup-conf').clone();
2021-04-19 17:52:26 +02:00
var div = $div[0];
2021-04-18 13:55:59 +02:00
var $loc = $div.find('.loc');
var $rem = $div.find('.rem');
var typ = (new SimpleBackup).type;
2021-04-25 14:37:57 +02:00
var loc = conf.backups.agents[0];
var rem = conf.backups.agents[1];
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
div.checking = 0;
2021-04-19 11:49:23 +02:00
//
2021-04-18 13:55:59 +02:00
$loc.find('.auth').val( loc.conf.auth );
$rem.find('.auth').val( rem.conf.auth );
$rem.find('.base').val( rem.conf.base );
2021-04-19 17:52:26 +02:00
setBackupConfigUi($loc, loc);
setBackupConfigUi($rem, rem);
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
if (! loc.enabled & & ! rem.enabled)
$div.addClass('off');
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
//
$div.find('.opt').click(function(){
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
var $opt = $(this).parent();
2021-04-19 13:09:04 +02:00
2021-04-19 17:52:26 +02:00
if ($opt.hasClass('off'))
{
$opt.find('.etc')
.css({ opacity: 0 })
.slideDown('fast')
.animate({ opacity: 1 }, 'fast')
.queue(function(){
$opt.removeClass('off');
$div.removeClass('off');
$(this).css('opacity', '').dequeue();
})
2021-04-18 22:36:39 +02:00
2021-04-19 17:52:26 +02:00
$opt.find('input').first()
.delay(800)
.queue(function(){ $(this).focus().dequeue(); });
}
else
{
$opt.find('.etc')
.animate({ opacity: 0 }, 'fast')
.slideUp('fast')
.queue(function(){
$opt.addClass('off');
if ($loc.hasClass('off') & & $rem.hasClass('off'))
$div.addClass('off');
$(this).css({ opacity: '' }).dequeue();
})
}
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
return false;
});
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
$div.find('.check').click(function(){
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
if (div.checking)
return false;
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
var foo = getBackupConfigUi();
if (! foo)
return false;
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
if (foo.loc.enabled)
2021-04-18 13:55:59 +02:00
{
2021-04-19 17:52:26 +02:00
div.checking++;
checkBackupConfig(foo.loc, $loc, function(){ div.checking--; });
2021-04-19 11:49:23 +02:00
}
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
if (foo.rem.enabled)
2021-04-19 11:49:23 +02:00
{
2021-04-19 17:52:26 +02:00
div.checking++;
checkBackupConfig(foo.rem, $rem, function(){ div.checking--; });
2021-04-18 13:55:59 +02:00
}
2021-04-19 17:52:26 +02:00
return false;
});
$div.find('.ok').click(function(){
var foo = getBackupConfigUi();
if (! foo)
return false;
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
if (foo.loc.enabled & & ! loc.enabled)
2021-04-25 14:37:57 +02:00
foo.loc.id = typ + '-' + (conf.backups.nextId++);
2021-04-18 13:55:59 +02:00
2021-04-19 17:52:26 +02:00
if (foo.rem.enabled & & ! rem.enabled)
2021-04-25 14:37:57 +02:00
foo.rem.id = typ + '-' + (conf.backups.nextId++);
2021-04-18 13:55:59 +02:00
2021-04-25 14:37:57 +02:00
conf.backups.agents[0] = foo.loc;
conf.backups.agents[1] = foo.rem;
2021-04-18 13:55:59 +02:00
2021-04-25 14:37:57 +02:00
NB.storage.initBackups(onBackupStatusChange);
2021-04-18 13:55:59 +02:00
NB.storage.saveConfig();
hideOverlay();
});
2021-04-19 17:52:26 +02:00
$div.find('a.close').click(function(){
2021-04-19 11:49:23 +02:00
hideOverlay();
});
2021-04-18 13:55:59 +02:00
showOverlay($div);
2021-04-14 13:54:03 +02:00
}
2021-04-25 14:37:57 +02:00
function onBackupStatusChange(agent)
2021-04-14 13:54:03 +02:00
{
2021-04-25 14:37:57 +02:00
var agents = NB.storage.backups.agents;
2021-04-14 13:54:03 +02:00
var $config = $('.config');
var $status = $('.config .teaser u')
2021-04-25 14:37:57 +02:00
// if (agent) console.log( `onBackupStatusChange: ${agent.id}, status ${agent.status}, op ${agent.lastOp}, xhr '${agent.lastXhr.text}' / ${agent.lastXhr.code}` );
// else console.log( `onBackupStatusChange: < generic > ` );
if (! agents.length)
2021-04-14 13:54:03 +02:00
{
2021-04-14 17:00:49 +02:00
$config.removeClass('backups-on backup-err backing-up');
2021-04-14 13:54:03 +02:00
return;
}
$config.addClass('backups-on');
2021-04-25 14:37:57 +02:00
var busy = 0;
var error = 0;
var ready = 0;
2021-04-22 16:32:02 +02:00
2021-04-25 14:37:57 +02:00
agents.forEach(function(agent){
if (agent.status == 'busy') busy++; else
if (agent.status == 'error') error++; else
if (agent.status == 'ready') ready++; else
throw `Unknown status [${agent.status}] on backup agent ${agent.id}`;
});
if (error > 0) $config.addClass('backup-err').removeClass('backing-up'); else
if (busy > 0) $config.addClass('backing-up').removeClass('backup-err'); else
$config.removeClass('backing-up backup-err');
// process all pending backups if needed
2021-04-22 16:32:02 +02:00
2021-04-25 14:37:57 +02:00
if (! error & & ! busy)
runPendingBackups();
}
2021-04-25 20:33:33 +02:00
function needsBackingUp(backupStatus, fields, agentIds)
{
var stale = false;
agentIds.forEach(function(id){
var obj = backupStatus[id];
if (obj) fields.forEach(function(f){ stale = !obj[f]; });
else stale = true;
});
return stale;
}
2021-04-25 14:37:57 +02:00
function runPendingBackups()
{
2021-04-25 20:33:33 +02:00
console.log('Checking for pending backups...');
var conf = NB.storage.getConfig();
var agentIds = [];
NB.storage.backups.agents.forEach(function(agent){
agentIds.push(agent.id);
});
if (needsBackingUp(conf.backupStatus, [ 'conf' ], agentIds))
{
console.log(" Backing up app config...");
NB.storage.backupConfig();
}
var boards = NB.storage.getBoardIndex();
boards.forEach(function(meta, id){
if (! needsBackingUp(meta.backupStatus, [ 'data', 'meta' ], agentIds))
return;
console.log(` Backing up board ${id}...`);
var board = NB.storage.loadBoard(id);
if (! board)
return;
NB.storage.backupBoard(id, board, meta)
});
2021-04-13 19:05:14 +02:00
}
2021-04-02 01:59:34 +02:00
/*
*
*/
function saveBoardOrder()
{
var $index = $('.config .load-board');
var spot = 1;
$index.each(function(){
var id = parseInt( $(this).attr('board_id') );
NB.storage.setBoardUiSpot(id, spot++);
});
}
2021-03-31 18:27:33 +02:00
/*
*
*/
function updatePageTitle()
{
var title = 'Nullboard';
if (NB.board)
2020-02-08 12:02:45 -05:00
{
2021-03-31 18:27:33 +02:00
title = NB.board.title;
2021-03-31 19:32:01 +02:00
title = 'NB - ' + (title || '(untitled 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)
2020-02-08 12:02:45 -05:00
{
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');
2021-04-14 13:54:03 +02:00
var $backup = $('.config .auto-backup');
2021-03-31 18:27:33 +02:00
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();
2021-04-02 01:59:34 +02:00
var index = [];
2019-05-28 12:09:54 +02:00
2021-04-02 01:59:34 +02:00
boards.forEach(function(meta, id){ index.push({ id: id, meta: meta }); });
index.sort(function(a, b){ return b.meta.ui_spot & & a.meta.ui_spot > b.meta.ui_spot; });
index.forEach(function(entry){
2021-03-26 10:40:42 +01:00
2021-03-31 18:27:33 +02:00
var $e = $entry.clone();
2021-04-02 01:59:34 +02:00
$e.attr('board_id', entry.id);
$e.html(entry.meta.title);
2019-05-28 12:09:54 +02:00
2021-04-02 01:59:34 +02:00
if (entry.id == id_now)
2021-03-31 18:27:33 +02:00
$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-26 10:40:42 +01:00
2021-03-31 20:20:49 +02:00
if (! empty)
{
if (id_now) $export.html('Export this board...').show();
else $export.html('Export all boards...').show();
2021-04-14 13:54:03 +02:00
$backup.show();
2021-03-31 20:20:49 +02:00
}
else
{
$export.hide();
2021-04-14 13:54:03 +02:00
$backup.hide();
2021-03-31 20:20:49 +02:00
}
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-04-10 14:12:32 +02:00
function setWhatsNew()
{
var conf = NB.storage.getConfig();
2021-04-10 14:32:14 +02:00
if (conf.verSeen & & conf.verSeen < NB.codeVersion )
2021-04-10 14:12:32 +02:00
{
$('.logo').addClass('updated');
$('.logo .alert').html("(updated)");
}
var $link = $('.logo .view-changes');
var link = $link.attr('href') + '/?have=' + NB.codeVersion;
if (conf.verSeen) link += '& seen=' + conf.verSeen;
if (conf.verLast) link += '& last=' + conf.verLast;
$link.attr('href', link);
}
2021-03-31 18:27:33 +02:00
/*
2021-03-31 20:35:46 +02:00
* generic utils
*/
2021-04-19 11:49:23 +02:00
function jsonMatch(a, b)
{
return JSON.stringify(a) == JSON.stringify(b);
}
function jsonClone(x)
{
return JSON.parse(JSON.stringify(x));
}
2021-03-31 20:35:46 +02:00
function htmlEncode(raw)
{
return $('tt .encoder').text(raw).html();
}
function setText($note, text)
{
$note.attr('_text', text);
text = htmlEncode(text);
2021-08-04 12:40:53 +02:00
var hmmm = /\b(https?:\/\/[^\s]+)/mg;
2021-03-31 20:35:46 +02:00
text = text.replace(hmmm, function(url){
return '< a href = "' + url + '" target = _blank > ' + url + '< / a > ';
});
2021-08-04 12:40:53 +02:00
if ( NB.peek('fileLinks') )
{
var xmmm = /`(.*?)`/mg;
text = text.replace(xmmm, function(full, text){
link = 'file:///' + text.replace('\\', '/');
return '`< a href = "' + link + '" target = _blank > ' + text + '< / a > `';
});
}
2021-04-05 11:50:02 +02:00
$note.html(text); // ? text : ' ');
2021-03-31 20:35:46 +02:00
}
function getText($note)
{
return $note.attr('_text');
}
2021-04-01 19:12:43 +02:00
function removeTextSelection()
{
if (window.getSelection) { window.getSelection().removeAllRanges(); }
else if (document.selection) { document.selection.empty(); }
}
2021-04-18 13:55:59 +02:00
function shakeControl($x)
{
$x
.css({ position: 'relative' })
2021-04-18 22:36:39 +02:00
.focus()
2021-04-18 13:55:59 +02:00
.animate({ left: '+4px' }, 60)
.animate({ left: '-3px' }, 60)
.animate({ left: '+2px' }, 60)
.animate({ left: '0px' }, 60)
.queue(function(){
2021-04-18 22:36:39 +02:00
$x.css({ position: '', left: '' }).dequeue();
2021-04-18 13:55:59 +02:00
});
}
2021-03-31 20:35:46 +02:00
/*
* inline editing
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() );
2021-04-05 11:50:02 +02:00
2021-03-31 18:27:33 +02:00
$edit.height( $text.height() );
$note.addClass('editing');
2021-03-26 10:29:33 +01:00
2021-03-31 18:27:33 +02:00
$edit.focus();
}
2021-03-26 10:29:33 +01:00
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-26 10:29:33 +01:00
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-04-05 11:50:02 +02:00
if (via_escape)
2020-02-08 12:02:45 -05:00
{
2021-04-05 11:50:02 +02:00
if (brand_new)
$item.closest('.note, .list, .board').remove();
2021-03-31 18:27:33 +02:00
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 20:35:46 +02:00
function handleTab(ev)
{
var $this = $(this);
var $note = $this.closest('.note');
var $sibl = ev.shiftKey ? $note.prev() : $note.next();
if ($sibl.length)
{
stopEditing($this, false, false);
$sibl.find('.text').click();
}
}
//
function setRevealState(ev)
{
var raw = ev.originalEvent;
var caps = raw.getModifierState & & raw.getModifierState( 'CapsLock' );
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 showDing()
{
$('body')
.addClass('ding')
.delay(250)
.queue(function(){ $(this).removeClass('ding').dequeue(); });
}
2019-05-28 12:09:54 +02:00
2021-03-31 20:35:46 +02:00
/*
* overlay
*/
2021-03-31 18:27:33 +02:00
function showOverlay($div)
{
$('.overlay')
.html('')
.append($div)
2021-04-18 13:55:59 +02:00
.css({ opacity: 0, display: 'flex' })
2021-03-31 18:27:33 +02:00
.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 20:35:46 +02:00
/*
* license popup
*/
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 20:35:46 +02:00
/*
* adjust this and that
*/
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-04-06 19:54:47 +02:00
var list_w = getListWidth();
2021-03-31 18:27:33 +02:00
var lists = $board.find('.list').length;
2021-04-06 19:54:47 +02:00
var lists_w = (lists < 2 ) ? list_w : ( list_w + 10 ) * lists - 10 ;
2021-03-31 18:27:33 +02:00
var body_w = $body.width();
if (lists_w + 190 < = body_w)
{
$board.css('max-width', '');
$body.removeClass('crowded');
}
else
{
2021-04-06 19:54:47 +02:00
var max = Math.floor( (body_w - 40) / (list_w + 10) );
max = (max < 2 ) ? list_w : ( list_w + 10 ) * max - 10 ;
2021-03-31 18:27:33 +02:00
$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;
2021-04-07 18:51:52 +02:00
var have = $inner.outerWidth();
2019-05-28 12:09:54 +02:00
2021-04-08 13:24:35 +02:00
if (want < = max+5)
2021-03-31 18:27:33 +02:00
{
$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;
}
2020-02-19 03:50:03 +01:00
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-04-05 11:50:02 +02:00
/*
2021-04-06 13:21:48 +02:00
* dragsters
2021-04-05 11:50:02 +02:00
*/
2021-04-06 13:21:48 +02:00
function initDragAndDrop()
{
NB.noteDrag = new Drag2();
2021-04-14 16:17:21 +02:00
NB.noteDrag.listSel = '.board .list .notes';
2021-04-06 13:21:48 +02:00
NB.noteDrag.itemSel = '.note';
NB.noteDrag.dragster = 'note-dragster';
NB.noteDrag.onDragging = function(started)
{
var drag = this;
var $note = $(drag.item);
if (started)
{
var $drag = drag.$drag;
if ($note.hasClass('collapsed'))
$drag.addClass('collapsed');
$drag.html('< div class = text > < / div > ');
$drag.find('.text').html( $note.find('.text').html() );
drag.org_loc = noteLocation($note);
if ($note.hasClass('collapsed'))
drag.$drag.addClass('collapsed');
}
else
{
2021-04-13 19:05:14 +02:00
if (this.org_loc != noteLocation($note))
2021-04-06 13:21:48 +02:00
saveBoard();
}
}
NB.loadDrag = new Drag2();
NB.loadDrag.listSel = '.config .boards';
NB.loadDrag.itemSel = 'a.load-board';
NB.loadDrag.dragster = 'load-dragster';
NB.loadDrag.onDragging = function(started)
{
var drag = this;
if (started)
{
var $drag = drag.$drag;
$('.config .teaser').css({ display: 'none' });
$('.config .bulk').css({ display: 'block', opacity: 1 });
$drag.html( $(this.item).html() );
}
else
{
$('.config .teaser').css({ display: '' });
$('.config .bulk')
.show()
.delay(250)
.queue(function(){ $(this).css({ display: '', opacity: '' }).dequeue(); });
saveBoardOrder();
}
}
}
/*
* fonts
*/
function initFonts()
{
2021-04-06 19:09:14 +02:00
var toGo = 0;
var loaded = [];
var failed = [];
2021-04-06 13:21:48 +02:00
NB.font = null; // current font
//
2021-04-06 19:09:14 +02:00
function isUsable(f)
{
return ! failed.includes(f) & & loaded.includes(f);
}
2021-04-06 13:21:48 +02:00
function onFontsLoaded()
{
var conf = NB.storage.getConfig();
2021-04-06 19:09:14 +02:00
$('.config .switch-font').each(function(){
if (! isUsable($(this).attr('font')))
$(this).remove();
});
if (conf.fontName & & ! isUsable(conf.fontName))
2021-04-06 13:21:48 +02:00
{
2021-04-06 15:29:12 +02:00
NB.storage.setFontName(null);
2021-04-06 13:21:48 +02:00
selectFont(null);
}
2021-04-06 19:09:14 +02:00
selectFont(conf.fontName || 'barlow');
2021-04-06 13:21:48 +02:00
2021-04-06 19:09:14 +02:00
if (conf.fontSize)
setFontSize(conf.fontSize);
2021-04-06 15:29:12 +02:00
2021-04-06 19:09:14 +02:00
if (conf.lineHeight)
setLineHeight(conf.lineHeight);
2021-04-06 15:29:12 +02:00
2021-04-07 18:51:52 +02:00
updateVarsAndLayout();
2021-04-06 13:21:48 +02:00
}
function onFontLoaded(f, ok)
{
2021-04-10 14:12:32 +02:00
var m = f.family.match(/["']?f-([^"']*)/);
var f_name = m ? m[1] : ''; /* ios safari will set 'family' to 'weight' on failure ! */
2021-04-06 13:21:48 +02:00
if (! ok)
{
2021-04-06 19:09:14 +02:00
console.log( `! Failed to load ${f.family} ${f.weight}` );
failed.push(f_name);
2021-04-06 13:21:48 +02:00
}
else
{
2021-04-06 19:09:14 +02:00
loaded.push(f_name);
2021-04-06 13:21:48 +02:00
}
2021-04-06 19:09:14 +02:00
if (! --toGo)
2021-04-06 13:21:48 +02:00
onFontsLoaded();
}
document.fonts.forEach(function(f){
2021-04-06 19:09:14 +02:00
2021-04-06 13:21:48 +02:00
if (f.status == 'loaded')
return;
console.log( `Loading ${f.family} ${f.weight} ...` );
2021-04-06 19:09:14 +02:00
toGo++;
f.load()
.then(function(){ onFontLoaded(f, true); })
.catch(function(){ onFontLoaded(f, false); });
2021-04-06 13:21:48 +02:00
});
}
function selectFont(font)
2021-04-05 11:50:02 +02:00
{
2021-04-06 18:37:26 +02:00
var $html = $('html');
$html.removeClass('f-' + NB.font).addClass('f-' + font);
2021-04-06 13:21:48 +02:00
NB.font = font;
2021-04-05 11:50:02 +02:00
var $list = $('.config .switch-font');
$list.removeClass('active');
$list.filter('[font="' + font + '"]').addClass('active');
2021-04-06 13:21:48 +02:00
2021-04-07 18:51:52 +02:00
updateVarsAndLayout();
2021-04-06 13:21:48 +02:00
}
//
2021-04-07 13:56:10 +02:00
function getVar(name)
{
var v = $('html').css(name);
var m = v.match(/^\s*calc\((.*)\)$/);
if (m) v = eval(m[1]);
return parseFloat( v );
}
2021-04-06 13:21:48 +02:00
function getFontSize()
{
2021-04-07 13:56:10 +02:00
return getVar('--fs');
2021-04-05 11:50:02 +02:00
}
2021-04-06 13:21:48 +02:00
function getLineHeight()
{
2021-04-07 13:56:10 +02:00
return getVar('--lh');;
2021-04-05 11:50:02 +02:00
}
2021-04-06 19:32:46 +02:00
function getListWidth()
{
2021-04-07 13:56:10 +02:00
return parseInt( getVar('--lw') );
2021-04-06 19:32:46 +02:00
}
2021-04-06 13:21:48 +02:00
//
function updateFontSize()
{
var val = getFontSize();
2021-04-06 18:37:26 +02:00
$('.config .f-prefs .ui-fs .val').html( val.toFixed(1) );
2021-04-06 15:29:12 +02:00
return val;
2021-04-06 13:21:48 +02:00
}
2021-04-01 19:12:43 +02:00
2021-04-06 13:21:48 +02:00
function updateLineHeight()
2021-04-01 19:12:43 +02:00
{
2021-04-06 13:21:48 +02:00
var val = getLineHeight();
2021-04-06 18:37:26 +02:00
$('.config .f-prefs .ui-lh .val').html( val.toFixed(1) );
2021-04-06 15:29:12 +02:00
return val;
2021-04-06 13:21:48 +02:00
}
2021-04-01 19:12:43 +02:00
2021-04-06 19:32:46 +02:00
function updateListWidth()
{
var val = getListWidth();
$('.config .f-prefs .ui-lw .val').html( val.toFixed(0) );
return val;
}
2021-04-07 18:51:52 +02:00
function updateVarsAndLayout()
2021-04-07 13:56:10 +02:00
{
updateFontSize();
updateLineHeight();
updateListWidth();
2021-04-07 18:51:52 +02:00
adjustLayout();
2021-04-07 13:56:10 +02:00
}
2021-04-06 13:21:48 +02:00
//
function setFontSize(fs)
{
2021-04-07 18:51:52 +02:00
fs = fs.clamp(9, 24);
$('html').css('--fs', fs + '').addClass('fs-set');
updateVarsAndLayout();
if (getLineHeight() < fs )
setLineHeight(fs);
2021-04-06 13:21:48 +02:00
2021-04-06 15:29:12 +02:00
return getFontSize();
2021-04-01 19:12:43 +02:00
}
2020-02-19 03:50:03 +01:00
2021-04-06 13:21:48 +02:00
function setLineHeight(lh)
2021-04-02 01:59:34 +02:00
{
2021-04-07 18:51:52 +02:00
var fs = getFontSize();
2021-04-07 13:56:10 +02:00
lh = parseInt(10*lh) / 10.; // trim to a single decimal digit
2021-04-07 18:51:52 +02:00
lh = lh.clamp(fs, 3*fs);
$('html').css('--lh', lh + '').addClass('lh-set');
updateVarsAndLayout();
2021-04-07 13:56:10 +02:00
2021-04-06 15:29:12 +02:00
return getLineHeight();
2021-04-06 13:21:48 +02:00
}
2021-04-06 19:32:46 +02:00
function setListWidth(lw)
{
2021-04-07 18:51:52 +02:00
lw = lw.clamp(200, 400);
$('html').css('--lw', lw + '').addClass('lw-set');
updateVarsAndLayout();
2021-04-06 19:32:46 +02:00
return getListWidth();
}
2021-04-06 13:21:48 +02:00
//
function resetFontSize()
{
2021-04-06 18:37:26 +02:00
$('html').css('--fs', '').removeClass('fs-set');
2021-04-07 18:51:52 +02:00
updateVarsAndLayout();
2021-04-06 15:29:12 +02:00
return updateFontSize();
2021-04-06 13:21:48 +02:00
}
function resetLineHeight()
{
2021-04-06 18:37:26 +02:00
$('html').css('--lh', '').removeClass('lh-set');
2021-04-07 18:51:52 +02:00
updateVarsAndLayout();
2021-04-06 15:29:12 +02:00
return updateLineHeight();
2021-04-02 01:59:34 +02:00
}
2021-04-06 19:32:46 +02:00
function resetListWidth()
{
$('html').css('--lw', '').removeClass('lw-set');
2021-04-07 18:51:52 +02:00
updateVarsAndLayout();
2021-04-06 19:32:46 +02:00
return updateListWidth();
}
2021-04-14 17:15:13 +02:00
//
function saveUiPrefs()
{
var $html = $('html');
NB.storage.setFontSize ( $html.hasClass('fs-set') ? getFontSize() : null );
NB.storage.setLineHeight ( $html.hasClass('lh-set') ? getLineHeight() : null );
NB.storage.setListWidth ( $html.hasClass('lw-set') ? getListWidth() : null );
}
2021-03-31 18:27:33 +02:00
/*
2021-03-31 20:35:46 +02:00
* event handlers
2021-03-31 18:27:33 +02:00
*/
$(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;
}
2020-02-08 12:02:45 -05:00
2021-04-02 01:59:34 +02:00
NB.noteDrag.cancelPriming();
2020-02-08 12:02:45 -05:00
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-10-23 15:13:51 +02:00
var $this = $(this);
var $note = $this.closest('.note');
var $list = $this.closest('.list');
var isNote = $note.length > 0;
var isList = $list.length > 0;
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-10-23 15:13:51 +02:00
stopEditing($this, true, false);
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
// 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
2021-10-23 15:13:51 +02:00
if (ev.keyCode == 13 & & ev.altKey ||
2021-03-31 18:27:33 +02:00
ev.keyCode == 13 & & ev.shiftKey & & ! ev.ctrlKey)
2021-03-31 12:35:41 +02:00
{
2021-10-23 15:13:51 +02:00
stopEditing($this, false, false);
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 + (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
stopEditing($this, false, false);
2021-03-31 12:35:41 +02:00
2021-10-23 15:13:51 +02:00
if (isNote)
{
if (ev.shiftKey) // ctrl-shift-enter
addNote($list, null, $note);
else
addNote($list, $note);
}
2021-03-31 18:27:33 +02:00
else
2021-10-23 15:13:51 +02:00
if (isList)
{
$note = $list.find('.note').last();
2021-03-31 18:27:33 +02:00
addNote($list, $note);
2021-10-23 15:13:51 +02:00
}
else
{
addList();
}
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')
{
2021-10-23 15:13:51 +02:00
var $item = $this.parent();
2021-03-31 18:27:33 +02:00
$item[0]._collapsed = true;
2021-10-23 15:13:51 +02:00
stopEditing($this, false, false);
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 + expand
if (isNote & & ev.altKey & & ev.key == 'ArrowDown')
{
2021-10-23 15:13:51 +02:00
var $item = $this.parent();
2021-03-31 18:27:33 +02:00
$item[0]._collapsed = false;
2021-10-23 15:13:51 +02:00
stopEditing($this, false, false);
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 + toggle 'raw'
if (isNote & & ev.altKey & & ev.keyCode == 82)
2021-03-31 12:35:41 +02:00
{
2021-10-23 15:13:51 +02:00
$this.parent().toggleClass('raw');
stopEditing($this, false, false);
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
// 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);
2021-10-23 15:13:51 +02:00
$this.val(want);
2021-03-31 18:27:33 +02:00
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)
2021-04-05 11:50:02 +02:00
$(this).height(this.scrollHeight - delta);
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-04-02 01:59:34 +02:00
var board_id = parseInt( $(this).attr('board_id') );
NB.loadDrag.cancelPriming();
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
2021-04-02 01:59:34 +02:00
openBoard(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){
2021-04-02 01:59:34 +02:00
NB.noteDrag.prime(this.parentNode, ev);
});
$('.config').on('mousedown', 'a.load-board', function(ev){
if ($('.config a.load-board').length > 1)
NB.loadDrag.prime(this, ev);
2021-03-31 18:27:33 +02:00
});
2021-03-31 12:35:41 +02:00
2021-04-07 18:51:52 +02:00
//
$('.config').on('mousedown', '.ui-fs .val', function(ev){
var org = getFontSize();
2021-04-14 17:15:13 +02:00
NB.varAdjust.start(ev, function(delta){ setFontSize( org + delta/50. ); }, saveUiPrefs);
2021-04-07 18:51:52 +02:00
});
$('.config').on('mousedown', '.ui-lh .val', function(ev){
var org = getLineHeight();
2021-04-14 17:15:13 +02:00
NB.varAdjust.start(ev, function(delta){ setLineHeight( org + delta/50. ); }, saveUiPrefs);
2021-04-07 18:51:52 +02:00
});
$('.config').on('mousedown', '.ui-lw .val', function(ev){
var org = getListWidth();
2021-04-14 17:15:13 +02:00
NB.varAdjust.start(ev, function(delta){ setListWidth( org + delta/5. ); }, saveUiPrefs);
2021-04-07 18:51:52 +02:00
});
//
2021-03-31 18:27:33 +02:00
$(document).on('mouseup', function(ev){
2021-04-02 01:59:34 +02:00
NB.noteDrag.end();
NB.loadDrag.end();
2021-04-07 18:51:52 +02:00
NB.varAdjust.end();
2021-03-31 18:27:33 +02:00
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
$(document).on('mousemove', function(ev){
setRevealState(ev);
2021-04-02 01:59:34 +02:00
NB.noteDrag.onMouseMove(ev);
NB.loadDrag.onMouseMove(ev);
2021-04-07 18:51:52 +02:00
NB.varAdjust.onMouseMove(ev);
2021-03-31 18:27:33 +02:00
});
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
//
2021-04-14 13:54:03 +02:00
$('.config .imp-board').on('click', function(ev){
2021-03-31 18:27:33 +02:00
$('.config .imp-board-select').click();
return false;
});
2021-03-31 12:35:41 +02:00
2021-04-14 13:54:03 +02:00
$('.config .imp-board-select').on('change' , function(){
2021-03-31 18:27:33 +02:00
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-04-14 13:54:03 +02:00
$('.config .exp-board').on('click', function(){
2021-03-31 20:20:49 +02:00
var pack = exportBoard();
$(this).attr('href', pack.blob);
$(this).attr('download', pack.file);
2021-03-31 18:27:33 +02:00
return true;
});
2021-03-31 12:35:41 +02:00
2021-04-14 13:54:03 +02:00
$('.config .auto-backup').on('click', function(){
2021-04-18 13:55:59 +02:00
configBackups();
2021-04-14 13:54:03 +02:00
});
2021-04-06 16:51:20 +02:00
//
$('.config .section .title').on('click', function(){
$(this).closest('.section').toggleClass('open');
2021-03-31 18:27:33 +02:00
return false;
});
2021-03-31 12:35:41 +02:00
2021-04-05 18:20:23 +02:00
$('.config').on('click', '.switch-font', function(){
2021-04-05 11:50:02 +02:00
var font = $(this).attr('font');
selectFont(font);
2021-04-06 15:29:12 +02:00
NB.storage.setFontName(font);
2021-03-31 18:27:33 +02:00
return false;
});
2021-03-31 12:35:41 +02:00
2021-04-06 13:21:48 +02:00
//
$('.config .f-prefs .ui-fs .less').on('click', function(){
2021-04-14 17:15:13 +02:00
setFontSize( parseInt(10*getFontSize()) / 10. - 0.5 );
saveUiPrefs();
2021-04-06 13:21:48 +02:00
return false;
2021-04-05 18:20:23 +02:00
});
2021-04-06 13:21:48 +02:00
$('.config .f-prefs .ui-fs .val').on('click', function(){
2021-04-07 18:51:52 +02:00
if (NB.varAdjust.used) return false;
2021-04-06 13:21:48 +02:00
var fs = resetFontSize();
2021-04-07 18:51:52 +02:00
if (getLineHeight() < fs ) setLineHeight ( fs ) ;
2021-04-14 17:15:13 +02:00
saveUiPrefs();
2021-04-06 13:21:48 +02:00
return false;
2021-04-05 18:20:23 +02:00
});
2021-04-06 13:21:48 +02:00
$('.config .f-prefs .ui-fs .more').on('click', function(){
2021-04-14 17:15:13 +02:00
setFontSize( parseInt(10*getFontSize()) / 10. + 0.5 );
saveUiPrefs();
2021-04-06 13:21:48 +02:00
return false;
2021-04-05 18:20:23 +02:00
});
2021-04-06 15:29:12 +02:00
//
2021-04-06 13:21:48 +02:00
$('.config .f-prefs .ui-lh .less').on('click', function(){
2021-04-14 17:15:13 +02:00
setLineHeight( parseInt(10*getLineHeight()) / 10. - 0.1 );
saveUiPrefs();
2021-04-06 13:21:48 +02:00
return false;
2021-04-05 18:20:23 +02:00
});
2021-04-06 13:21:48 +02:00
$('.config .f-prefs .ui-lh .val').on('click', function(){
2021-04-07 18:51:52 +02:00
if (NB.varAdjust.used) return false;
2021-04-06 13:21:48 +02:00
var lh = resetLineHeight();
2021-04-07 18:51:52 +02:00
if (lh < getFontSize ( ) ) setFontSize ( lh ) ;
2021-04-14 17:15:13 +02:00
saveUiPrefs();
2021-04-06 13:21:48 +02:00
return false;
});
$('.config .f-prefs .ui-lh .more').on('click', function(){
2021-04-14 17:15:13 +02:00
setLineHeight( parseInt(10*getLineHeight()) / 10. + 0.1 );
saveUiPrefs();
2021-04-06 13:21:48 +02:00
return false;
});
2021-04-05 18:20:23 +02:00
2021-04-06 19:32:46 +02:00
//
$('.config .f-prefs .ui-lw .less').on('click', function(){
2021-04-14 17:15:13 +02:00
setListWidth( getListWidth() - 5 );
saveUiPrefs();
2021-04-06 19:32:46 +02:00
return false;
});
$('.config .f-prefs .ui-lw .val').on('click', function(){
2021-04-07 18:51:52 +02:00
if (NB.varAdjust.used) return false;
2021-04-06 19:32:46 +02:00
resetListWidth();
2021-04-14 17:15:13 +02:00
saveUiPrefs();
2021-04-06 19:32:46 +02:00
return false;
});
$('.config .f-prefs .ui-lw .more').on('click', function(){
2021-04-14 17:15:13 +02:00
setListWidth( getListWidth() + 5 );
saveUiPrefs();
2021-04-06 19:32:46 +02:00
return false;
});
2021-04-06 16:51:20 +02:00
//
2021-04-14 17:00:49 +02:00
$('.config .switch-theme').on('click', function() {
2021-04-06 16:51:20 +02:00
var $html = $('html');
$html.toggleClass('theme-dark');
NB.storage.setTheme($html.hasClass('theme-dark') ? 'dark' : '');
return false;
});
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();
2021-03-31 20:49:29 +02:00
$div.find('div').html(`Version ${NB.codeVersion}`);
2021-03-31 18:27:33 +02:00
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-04-10 14:12:32 +02:00
$('.view-changes').click(function(){
if (! $('.logo').hasClass('updated'))
return;
NB.storage.setVerSeen();
$('.logo').removeClass('updated');
});
2021-04-18 13:55:59 +02:00
/***/
2021-03-31 18:27:33 +02:00
$(window).resize(adjustLayout);
2021-03-31 12:35:41 +02:00
2021-04-06 23:54:46 +02:00
$('body').on('dragstart', function(){ return false; });
2021-03-31 18:27:33 +02:00
/*
* the init()
*/
2021-04-06 13:21:48 +02:00
var NB =
{
2021-10-23 15:13:51 +02:00
codeVersion: 20211023,
2021-04-07 18:51:52 +02:00
blobVersion: 20190412, // board blob format in Storage
2021-04-06 13:21:48 +02:00
board: null,
2021-08-04 12:40:53 +02:00
storage: null,
peek: function(name)
{
return this.storage.getConfig()[name];
},
poke: function(name, val)
{
var conf = this.storage.getConfig();
conf[name] = val;
return this.storage.saveConfig();
}
2021-04-06 13:21:48 +02:00
};
NB.storage = new Storage_Local();
2021-03-31 18:27:33 +02:00
if (! NB.storage.open())
{
2021-03-31 19:32:01 +02:00
easyMartina = true;
throw new Error();
2021-03-31 18:27:33 +02:00
}
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 20:35:46 +02:00
boards.forEach( function(meta, board_id) {
2021-03-31 18:27:33 +02:00
var hist = meta.history.join(', ');
2021-04-25 20:33:33 +02:00
console.log( `Found board ${board_id} - "${meta.title}", revision ${meta.current}, history [${hist}], backup ${JSON.stringify(meta.backupStatus)}` );
2021-03-31 18:27:33 +02:00
});
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-08-04 12:40:53 +02:00
console.log( `Active: [${conf.board}]` );
console.log( `Theme: [${conf.theme}]` );
console.log( `Font: [${conf.fontName}], size [${conf.fontSize || '-'}], line-height [${conf.lineHeight || '-'}]` );
console.log( `FileLinks: [${conf.fileLinks}]` );
console.log( 'Backups: ', conf.backups);
2021-04-05 11:50:02 +02:00
2021-04-14 13:54:03 +02:00
/*
* backups
*/
NB.backupTypes = new Map();
2021-04-14 14:37:25 +02:00
NB.backupTypes.set( (new SimpleBackup).type, SimpleBackup );
2021-04-13 19:05:14 +02:00
2021-04-25 14:37:57 +02:00
NB.storage.initBackups(onBackupStatusChange);
2021-04-14 13:54:03 +02:00
/*
* the ui
*/
2021-04-06 13:21:48 +02:00
initFonts();
2021-04-05 18:20:23 +02:00
2021-04-06 13:21:48 +02:00
initDragAndDrop();
2021-04-05 18:20:23 +02:00
2021-04-07 18:51:52 +02:00
NB.varAdjust = new VarAdjust()
2021-04-05 11:50:02 +02:00
//
2021-03-31 18:27:33 +02:00
if (conf.theme)
2021-04-05 11:50:02 +02:00
$('html').addClass('theme-' + conf.theme);
2021-03-31 12:35:41 +02:00
2021-03-31 18:27:33 +02:00
if (conf.board)
2021-04-10 14:12:32 +02:00
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-04-10 14:12:32 +02:00
setWhatsNew();
NB.storage.setVerLast();
2021-03-31 20:35:46 +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 >