diff --git a/site/changelog.html b/site/changelog.html
index d1ed243..a8a56d6 100644
--- a/site/changelog.html
+++ b/site/changelog.html
@@ -48,7 +48,7 @@
Two channels
The released installer is SQLBI Whiteboard. The pre-release installer is SQLBI Whiteboard (Dev). They are separate products: different folders, different settings, different upgrade codes. Only the released channel registers .wboard and .wimport, so uninstalling Dev cannot break those associations.
Published builds
- Until a stable release exists, the list below is pre-release builds. Without scripting, use All releases.
+ Without scripting, use All releases.
@@ -95,6 +95,7 @@ Published builds
(function () {
var list = document.getElementById('releases');
var note = document.getElementById('release-note');
+ var ISSUES = 'https://github.com/sql-bi/SQLBI-Whiteboard/issues/';
fetch('https://api.github.com/repos/sql-bi/SQLBI-Whiteboard/releases?per_page=20')
.then(function (response) {
@@ -120,13 +121,133 @@ Published builds
});
}
- function firstLine(text) {
+ function stripTrailers(text) {
var lines = String(text).replace(/\r\n/g, '\n').split('\n');
- for (var i = 0; i < lines.length; i++) {
- var line = lines[i].replace(/^#+\s*/, '').trim();
- if (line) return line;
+ while (lines.length) {
+ var t = lines[lines.length - 1].trim();
+ if (!t || /^Co-authored-by:/i.test(t) || /^Signed-off-by:/i.test(t) || /^-{3,}$/.test(t)) {
+ lines.pop();
+ continue;
+ }
+ break;
+ }
+ return lines.join('\n').trim();
+ }
+
+ function splitNotes(text) {
+ var lines = String(text).replace(/\r\n/g, '\n').split('\n');
+ var i = 0;
+ while (i < lines.length && !lines[i].trim()) i++;
+ var sum = (lines[i] || '').replace(/^#+\s*/, '').trim();
+ i++;
+ while (i < lines.length && !lines[i].trim()) i++;
+ return { sum: sum, rest: stripTrailers(lines.slice(i).join('\n')) };
+ }
+
+ function inline(s) {
+ var slots = [];
+ function keep(html) {
+ slots.push(html);
+ return '\0' + (slots.length - 1) + '\0';
+ }
+ s = s.replace(/`([^`]+)`/g, function (_, c) {
+ return keep('' + c + '');
+ });
+ s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, function (_, t, u) {
+ return keep('' + t + '');
+ });
+ s = s.replace(/(^|[\s(])(https?:\/\/[^\s<]+[^.\s<),])/g, function (_, p, u) {
+ return p + keep('' + u + '');
+ });
+ s = s.replace(/\*\*([^*]+)\*\*/g, '$1');
+ s = s.replace(/(^|[\s(])\*([^*\s][^*]*)\*/g, '$1$2');
+ s = s.replace(/(^|[\s(])#(\d+)\b/g, function (_, p, n) {
+ return p + '#' + n + '';
+ });
+ return s.replace(/\0(\d+)\0/g, function (_, i) { return slots[+i]; });
+ }
+
+ function md(src) {
+ var lines = esc(src).split('\n');
+ var out = [];
+ var i = 0;
+
+ function peek() { return i < lines.length ? lines[i] : null; }
+ function blank(line) { return !line || !String(line).trim(); }
+ function isHeading(line) { return /^(#{1,6})\s+\S/.test(line); }
+ function isHr(line) { return /^(-{3,}|_{3,}|\*{3,})\s*$/.test(String(line || '').trim()); }
+ function isUl(line) { return /^[-*+]\s+\S/.test(line); }
+ function isOl(line) { return /^\d+[.)]\s+\S/.test(line); }
+ function isFence(line) { return /^```/.test(line); }
+
+ function takeFence() {
+ i++;
+ var body = [];
+ while (i < lines.length && !/^```/.test(lines[i])) {
+ body.push(lines[i]);
+ i++;
+ }
+ if (i < lines.length) i++;
+ return '' + body.join('\n') + '
';
+ }
+
+ function takeList(ordered) {
+ var tag = ordered ? 'ol' : 'ul';
+ var re = ordered ? /^\d+[.)]\s+/ : /^[-*+]\s+/;
+ var items = [];
+ while (i < lines.length) {
+ if (blank(peek())) {
+ var j = i + 1;
+ while (j < lines.length && blank(lines[j])) j++;
+ if (j < lines.length && re.test(lines[j])) { i = j; continue; }
+ break;
+ }
+ if (isFence(peek()) || isHeading(peek()) || isHr(peek())) break;
+ if (re.test(peek())) {
+ items.push(peek().replace(re, ''));
+ i++;
+ continue;
+ }
+ if (items.length) {
+ items[items.length - 1] += ' ' + peek().trim();
+ i++;
+ continue;
+ }
+ break;
+ }
+ return '<' + tag + '>' + items.map(function (it) {
+ return '' + inline(it.trim()) + '';
+ }).join('') + '' + tag + '>';
+ }
+
+ function takeParagraph() {
+ var buf = [];
+ while (i < lines.length && !blank(peek())) {
+ if (isFence(peek()) || isHeading(peek()) || isHr(peek()) ||
+ isUl(peek()) || isOl(peek())) break;
+ buf.push(peek().trim());
+ i++;
+ }
+ return '' + inline(buf.join(' ')) + '
';
+ }
+
+ while (i < lines.length) {
+ var line = peek();
+ if (blank(line)) { i++; continue; }
+ if (isFence(line)) { out.push(takeFence()); continue; }
+ if (isHeading(line)) {
+ var m = line.match(/^(#{1,6})\s+(.*)$/);
+ var lvl = Math.min(m[1].length, 3);
+ out.push('' + inline(m[2].trim()) + '');
+ i++;
+ continue;
+ }
+ if (isHr(line)) { out.push('
'); i++; continue; }
+ if (isUl(line)) { out.push(takeList(false)); continue; }
+ if (isOl(line)) { out.push(takeList(true)); continue; }
+ out.push(takeParagraph());
}
- return '';
+ return out.join('');
}
function notesFor(release) {
@@ -151,17 +272,16 @@ Published builds
var when = new Date(r.published_at).toLocaleDateString(undefined, {
year: 'numeric', month: 'long', day: 'numeric'
});
- var full = notes[i] || '';
- var sum = firstLine(full);
+ var parts = splitNotes(notes[i] || '');
html += '';
- if (sum) {
- html += '' + esc(sum) + '
';
+ if (parts.sum) {
+ html += '' + esc(parts.sum) + '
';
}
- if (full && full.replace(/\r\n/g, '\n').trim() !== sum) {
- html += 'Notes
' +
- esc(full.trim()) + '
';
+ if (parts.rest) {
+ html += 'Notes
' +
+ md(parts.rest) + '
';
}
html += '';
}
diff --git a/site/faq.html b/site/faq.html
index 1d8da81..2dd56dd 100644
--- a/site/faq.html
+++ b/site/faq.html
@@ -63,13 +63,13 @@ Mac, iPad, or a browser?
No. It is a Windows application. Boards are files you can keep next to the material they document; they do not open in a browser.
Does it need an account, a tenant, or the internet?
- No account, no tenant. Boards and preferences stay on this machine. A copy that did not come from the Microsoft Store may ask GitHub once a day whether a newer released build exists; a bar under the tabs offers the download site if one does. Turn that off in Help → Preferences → Updates. A Microsoft Store install never makes that request. Details are on Privacy.
+ No account, no tenant. Boards and preferences stay on this machine. A copy that did not come from the Microsoft Store may check this site once a day for a newer released build; a bar under the tabs offers the download site if one does. Turn that off in Help → Preferences → Updates. A Microsoft Store install never makes that request. Details are on Privacy.
Where do boards live? What is a .wboard?
A board is a file on disk. Save writes a versioned ZIP with an embedded preview.png. Explorer shows that picture as the thumbnail in the released install. The VS Code extension shows the same picture.
Can Released and Dev both be installed?
- Yes. The pre-release installs as SQLBI Whiteboard (Dev), keeps its own settings, and does not register .wboard or .wimport, so it can sit beside a released copy without stealing the file association. Until a stable release exists, the download on the home page is the latest pre-release and says so.
+ Yes. The pre-release installs as SQLBI Whiteboard (Dev), keeps its own settings, and does not register .wboard or .wimport, so it can sit beside a released copy without stealing the file association.
What is LiveView, and why Reconnect after open?
LiveView captures a window or a display onto the board so you can draw over it. Windows cannot save the capture permission. After you reload a board the last frame appears immediately; Reconnect restores the live feed. The guide has the session story.
diff --git a/site/privacy.html b/site/privacy.html
index 21669b5..362cfcb 100644
--- a/site/privacy.html
+++ b/site/privacy.html
@@ -37,7 +37,7 @@
Project
Privacy
-
The application does not create an account or send telemetry. A Microsoft Store install never phones home. Any other copy may ask GitHub once a day whether a newer released build exists; that check can be turned off.
+
The application does not send telemetry. This page is what the website and the installer actually do.
@@ -46,16 +46,21 @@ Privacy
The application
SQLBI Whiteboard does not create an account, does not join a tenant, and does not send telemetry. There is no Application Insights, no crash reporter, and no outbound product analytics in the source.
- A copy that did not come from the Microsoft Store, with Check for new versions on (the default), makes at most one GET a day to whiteboard.sqlbi.com for stable.json, the same short file this site publishes for its own download links. A pre-release copy reads dev.json instead. If that request fails, it makes one to github.com to read the tag of the newest release. Either way it reads a version number and nothing else.
- The request identifies the product version in the User-Agent; it does not send a machine identifier, a board, or preferences. A Microsoft Store install never makes that request; the Store updates it. Turn the check off under Help → Preferences → Updates.
+ A Microsoft Store install never phones home. The Store updates it. Any other copy may ask this site once a day whether a newer build exists.
+
+ - Check for new versions is on by default; turn it off under Help → Preferences → Updates.
+ - A GET of
stable.json, or dev.json for a pre-release copy, the same file the download page uses.
+ - If that fails, a request to
github.com for the newest release tag.
+ - The User-Agent carries the product version. Nothing else is sent.
+
Boards are local .wboard files. Preferences live under %APPDATA%\SQLBI\Whiteboard (or Whiteboard Dev for the pre-release channel).
LiveView uses Windows Graphics Capture on this machine. The capture permission cannot be saved with the file. After you reload a board, the last frame is shown from the file; Reconnect asks Windows again.
This website
The site is static HTML on GitHub Pages. It does not set a first-party cookie and it has no consent banner.
- - The download button on the home page calls
api.github.com so the installer link can carry the current version. Without scripting, the same links go to the GitHub releases page.
- - The changelog page makes the same kind of request, to list published releases.
+ - The download button on the home page reads
stable.json from this site so the installer link can carry the current version. If that file is missing, it asks api.github.com. Without scripting, the same links go to the GitHub releases page.
+ - The changelog page asks
api.github.com to list published releases.
- The home page embeds a player from
player.vimeo.com, with dnt=1. Vimeo is a third party and may set its own cookies. There is no Vimeo JavaScript API on this site.
diff --git a/site/styles.css b/site/styles.css
index 8da2ed3..f61ad3c 100644
--- a/site/styles.css
+++ b/site/styles.css
@@ -772,7 +772,7 @@ footer a:hover { color: var(--ink); text-decoration: underline; }
background: var(--surface);
box-shadow: var(--shadow-sm);
}
-.releases li { border-bottom: 1px solid var(--line); padding: 14px 18px; }
+.releases li { border-bottom: 1px solid var(--line); padding: 16px 18px 18px; }
.releases li:last-child { border-bottom: 0; }
.reltop {
display: flex;
@@ -792,7 +792,7 @@ footer a:hover { color: var(--ink); text-decoration: underline; }
overflow: hidden;
text-overflow: ellipsis;
}
-.releases details { margin-top: 8px; }
+.releases details { margin: 10px 0 0 12px; }
.releases summary {
cursor: pointer;
color: var(--brand-link);
@@ -804,11 +804,40 @@ footer a:hover { color: var(--ink); text-decoration: underline; }
.releases summary::after { content: " +"; font-weight: 500; }
.releases details[open] summary::after { content: " \2212"; }
.relbody {
- margin: 10px 0 0;
- font: inherit;
- font-size: 14px;
+ margin: 8px 0 0;
+ padding: 12px 16px;
+ background: var(--surface-2);
+ border-radius: 10px;
+ font-size: 14.5px;
line-height: 1.55;
+ color: var(--muted);
+}
+.relbody > *:first-child { margin-top: 0; }
+.relbody > *:last-child { margin-bottom: 0; }
+.relbody h1, .relbody h2, .relbody h3 {
+ font-size: 14.5px;
+ font-weight: 650;
+ color: var(--ink);
+ margin: 1.05em 0 .35em;
+}
+.relbody p { margin: 0 0 .7em; }
+.relbody ul, .relbody ol { margin: 0 0 .7em; padding-left: 1.25em; }
+.relbody li { margin: .2em 0; }
+.relbody a { color: var(--brand-link); text-underline-offset: 2px; }
+.relbody hr {
+ border: 0;
+ border-top: 1px solid var(--line);
+ margin: .9em 0;
+}
+.relbody pre {
+ margin: 0 0 .7em;
+ padding: 10px 12px;
+ font-size: 13px;
+ line-height: 1.45;
white-space: pre-wrap;
+ background: var(--surface);
+ border: 1px solid var(--line);
+ border-radius: 10px;
color: var(--ink);
}