redesign: white left sidebar, no top bar, inline rename, floating share
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
abf25b34ae
commit
c8be1d679e
1 changed files with 320 additions and 262 deletions
538
index.html
538
index.html
|
|
@ -18,31 +18,27 @@
|
||||||
--accent: #FFD700;
|
--accent: #FFD700;
|
||||||
--accent-hover: #FFA500;
|
--accent-hover: #FFA500;
|
||||||
--controls-bg: #f1f1f1;
|
--controls-bg: #f1f1f1;
|
||||||
--border: #ddd;
|
--border: #e0e0e0;
|
||||||
--sidebar-bg: #f8f8f8;
|
--sidebar-bg: #fff;
|
||||||
--sidebar-hover: #efefef;
|
--sidebar-hover: #f5f5f5;
|
||||||
--sidebar-active-bg: #FFD700;
|
--sidebar-active-bg: #FFD700;
|
||||||
--sidebar-active-fg: #000;
|
--sidebar-active-fg: #000;
|
||||||
--input-bg: #fff;
|
--input-bg: #fff;
|
||||||
--muted: #888;
|
--muted: #999;
|
||||||
--header-bg: #FFD700;
|
|
||||||
--header-fg: #000;
|
|
||||||
}
|
}
|
||||||
[data-dark] {
|
[data-dark] {
|
||||||
--bg: #1a1a1a;
|
--bg: #1a1a1a;
|
||||||
--fg: #e0e0e0;
|
--fg: #e0e0e0;
|
||||||
--accent: #c9a800;
|
--accent: #c9a800;
|
||||||
--accent-hover: #a88800;
|
--accent-hover: #a88800;
|
||||||
--controls-bg: #252525;
|
--controls-bg: #222;
|
||||||
--border: #3a3a3a;
|
--border: #333;
|
||||||
--sidebar-bg: #1f1f1f;
|
--sidebar-bg: #1e1e1e;
|
||||||
--sidebar-hover: #2a2a2a;
|
--sidebar-hover: #272727;
|
||||||
--sidebar-active-bg: #3a2e00;
|
--sidebar-active-bg: #3a2e00;
|
||||||
--sidebar-active-fg: #FFD700;
|
--sidebar-active-fg: #FFD700;
|
||||||
--input-bg: #2a2a2a;
|
--input-bg: #2a2a2a;
|
||||||
--muted: #666;
|
--muted: #666;
|
||||||
--header-bg: #2a2200;
|
|
||||||
--header-fg: #c9a800;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
@ -50,148 +46,209 @@
|
||||||
body {
|
body {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
font-family: 'Roboto Mono', monospace;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Header ── */
|
/* ── Left sidebar ── */
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 44px;
|
|
||||||
padding: 0 10px;
|
|
||||||
gap: 8px;
|
|
||||||
background: var(--header-bg);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.header-title {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 18px;
|
|
||||||
flex: 1;
|
|
||||||
color: var(--header-fg);
|
|
||||||
}
|
|
||||||
.header-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
.save-indicator {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--header-fg);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.4s;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.save-indicator.visible { opacity: 0.7; }
|
|
||||||
|
|
||||||
/* ── Buttons ── */
|
|
||||||
button {
|
|
||||||
padding: 5px 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: var(--accent);
|
|
||||||
color: #000;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
button:hover { background: var(--accent-hover); }
|
|
||||||
button.on { background: var(--accent-hover); box-shadow: inset 0 2px 3px rgba(0,0,0,0.2); }
|
|
||||||
.btn-ghost {
|
|
||||||
background: transparent;
|
|
||||||
padding: 4px 7px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
.btn-ghost:hover { background: rgba(0,0,0,0.1); }
|
|
||||||
[data-dark] .btn-ghost:hover { background: rgba(255,255,255,0.1); }
|
|
||||||
|
|
||||||
/* ── Layout ── */
|
|
||||||
.body {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Sidebar ── */
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 190px;
|
width: 220px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--sidebar-bg);
|
background: var(--sidebar-bg);
|
||||||
border-right: 1px solid var(--border);
|
border-right: 1px solid var(--border);
|
||||||
overflow: hidden;
|
|
||||||
transition: width 0.2s;
|
transition: width 0.2s;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.sidebar.closed { width: 0; }
|
.sidebar.closed { width: 42px; }
|
||||||
|
|
||||||
.sidebar-top {
|
.sidebar-top {
|
||||||
padding: 8px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 8px;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
height: 44px;
|
||||||
}
|
}
|
||||||
.sidebar-top button { width: 100%; text-align: left; }
|
.sidebar.closed .sidebar-top { justify-content: center; }
|
||||||
.notes-list { flex: 1; overflow-y: auto; }
|
|
||||||
|
.sidebar-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
.sidebar.closed .sidebar-title { opacity: 0; width: 0; }
|
||||||
|
|
||||||
|
.notes-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.note-item {
|
.note-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 9px 10px;
|
padding: 9px 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
font-size: 12px;
|
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
min-height: 38px;
|
||||||
}
|
}
|
||||||
.note-item:hover { background: var(--sidebar-hover); }
|
.note-item:hover { background: var(--sidebar-hover); }
|
||||||
.note-item.active { background: var(--sidebar-active-bg); color: var(--sidebar-active-fg); font-weight: 700; }
|
.note-item.active {
|
||||||
|
background: var(--sidebar-active-bg);
|
||||||
|
color: var(--sidebar-active-fg);
|
||||||
|
}
|
||||||
|
|
||||||
.note-item-name {
|
.note-item-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
.sidebar.closed .note-item-name { display: none; }
|
||||||
|
|
||||||
|
/* inline rename input */
|
||||||
|
.note-rename-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
border: none;
|
||||||
|
outline: 1px solid var(--accent);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px 4px;
|
||||||
|
background: var(--input-bg);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
.note-del {
|
.note-del {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 0 3px;
|
border: none;
|
||||||
|
padding: 0 2px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
color: inherit;
|
color: var(--fg);
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.note-item:hover .note-del { opacity: 0.5; }
|
.note-item:hover .note-del { opacity: 0.4; }
|
||||||
.note-del:hover { opacity: 1 !important; background: transparent; }
|
.note-del:hover { opacity: 1 !important; }
|
||||||
|
.sidebar.closed .note-del { display: none; }
|
||||||
|
|
||||||
/* ── Editor ── */
|
.sidebar-bottom {
|
||||||
.editor {
|
padding: 8px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Icon buttons ── */
|
||||||
|
.icon-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--fg);
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.icon-btn:hover { background: var(--sidebar-hover); }
|
||||||
|
|
||||||
|
.sidebar-bottom-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 7px;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.sidebar-bottom-btn:hover { background: var(--sidebar-hover); }
|
||||||
|
.sidebar.closed .btn-label { display: none; }
|
||||||
|
|
||||||
|
/* ── Main area ── */
|
||||||
|
.main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
.note-title-bar {
|
|
||||||
|
/* floating share + dark toggle */
|
||||||
|
.float-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 6px;
|
||||||
border-bottom: 1px solid var(--border);
|
z-index: 10;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
#note-title {
|
.float-btn {
|
||||||
flex: 1;
|
padding: 5px 12px;
|
||||||
padding: 10px 14px;
|
background: var(--accent);
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 700;
|
|
||||||
font-family: 'Roboto Mono', monospace;
|
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
border-radius: 4px;
|
||||||
background: var(--bg);
|
cursor: pointer;
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.float-btn:hover { background: var(--accent-hover); }
|
||||||
|
.float-icon-btn {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 15px;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
#note-title::placeholder { color: var(--muted); }
|
.float-icon-btn:hover { background: var(--sidebar-hover); }
|
||||||
|
|
||||||
|
/* save flash */
|
||||||
|
.save-flash {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--muted);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.save-flash.show { opacity: 1; }
|
||||||
|
|
||||||
|
/* ── Editor ── */
|
||||||
.notepad {
|
.notepad {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 14px;
|
padding: 40px 20px 14px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
@ -203,7 +260,7 @@
|
||||||
}
|
}
|
||||||
.md-view {
|
.md-view {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 14px 18px;
|
padding: 40px 20px 14px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
|
|
@ -242,7 +299,7 @@
|
||||||
}
|
}
|
||||||
.draw-controls.visible { display: flex; }
|
.draw-controls.visible { display: flex; }
|
||||||
|
|
||||||
/* ── Main controls ── */
|
/* ── Bottom controls ── */
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -257,6 +314,19 @@
|
||||||
.cgroup label { font-size: 11px; color: var(--muted); }
|
.cgroup label { font-size: 11px; color: var(--muted); }
|
||||||
.controls-right { margin-left: auto; display: flex; gap: 5px; flex-wrap: wrap; }
|
.controls-right { margin-left: auto; display: flex; gap: 5px; flex-wrap: wrap; }
|
||||||
|
|
||||||
|
button.ctrl {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: var(--accent);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: 'Roboto Mono', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
button.ctrl:hover { background: var(--accent-hover); }
|
||||||
|
button.ctrl.on { background: var(--accent-hover); box-shadow: inset 0 2px 3px rgba(0,0,0,0.2); }
|
||||||
|
|
||||||
select {
|
select {
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -267,13 +337,9 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
input[type="color"] {
|
input[type="color"] {
|
||||||
width: 36px;
|
width: 36px; height: 28px; padding: 2px;
|
||||||
height: 28px;
|
border-radius: 4px; border: 1px solid var(--border);
|
||||||
padding: 2px;
|
background: var(--input-bg); cursor: pointer;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--input-bg);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
input[type="range"] { width: 70px; }
|
input[type="range"] { width: 70px; }
|
||||||
|
|
||||||
|
|
@ -291,45 +357,28 @@
|
||||||
|
|
||||||
/* ── Share modal ── */
|
/* ── Share modal ── */
|
||||||
.overlay {
|
.overlay {
|
||||||
position: fixed;
|
position: fixed; inset: 0;
|
||||||
inset: 0;
|
background: rgba(0,0,0,0.5);
|
||||||
background: rgba(0,0,0,0.55);
|
display: flex; align-items: center; justify-content: center;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
.modal {
|
.modal {
|
||||||
background: var(--bg);
|
background: var(--bg); color: var(--fg);
|
||||||
color: var(--fg);
|
border-radius: 8px; padding: 22px;
|
||||||
border-radius: 8px;
|
width: 380px; max-width: 92vw;
|
||||||
padding: 22px;
|
|
||||||
width: 380px;
|
|
||||||
max-width: 92vw;
|
|
||||||
}
|
}
|
||||||
.modal h3 { margin-bottom: 10px; }
|
.modal h3 { margin-bottom: 10px; }
|
||||||
.modal p { font-size: 12px; color: var(--muted); margin-bottom: 10px; }
|
.modal p { font-size: 12px; color: var(--muted); margin-bottom: 10px; }
|
||||||
.modal input[type="text"] {
|
.modal input[type="text"] {
|
||||||
width: 100%;
|
width: 100%; padding: 7px;
|
||||||
padding: 7px;
|
border: 1px solid var(--border); border-radius: 4px;
|
||||||
border: 1px solid var(--border);
|
font-family: 'Roboto Mono', monospace; font-size: 12px;
|
||||||
border-radius: 4px;
|
background: var(--input-bg); color: var(--fg); margin-bottom: 12px;
|
||||||
font-family: 'Roboto Mono', monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
background: var(--input-bg);
|
|
||||||
color: var(--fg);
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
}
|
||||||
.modal-btns { display: flex; gap: 8px; justify-content: flex-end; }
|
.modal-btns { display: flex; gap: 8px; justify-content: flex-end; }
|
||||||
|
|
||||||
/* ── Share read-only view ── */
|
/* ── Share read-only ── */
|
||||||
.share-page {
|
.share-page { padding: 40px; max-width: 760px; margin: 0 auto; overflow-y: auto; height: 100vh; }
|
||||||
padding: 40px;
|
|
||||||
max-width: 760px;
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
.share-page h1 { font-size: 26px; margin-bottom: 20px; }
|
.share-page h1 { font-size: 26px; margin-bottom: 20px; }
|
||||||
.share-page .content { font-family: 'Roboto', sans-serif; font-size: 16px; line-height: 1.7; }
|
.share-page .content { font-family: 'Roboto', sans-serif; font-size: 16px; line-height: 1.7; }
|
||||||
.share-page .content h1,.share-page .content h2,.share-page .content h3 { margin: 16px 0 8px; }
|
.share-page .content h1,.share-page .content h2,.share-page .content h3 { margin: 16px 0 8px; }
|
||||||
|
|
@ -340,36 +389,41 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="header">
|
|
||||||
<button class="btn-ghost" id="sidebar-toggle">☰</button>
|
|
||||||
<span class="header-title">noted.</span>
|
|
||||||
<div class="header-right">
|
|
||||||
<span class="save-indicator" id="save-indicator">✓ saved</span>
|
|
||||||
<button class="btn-ghost" id="dark-toggle" title="Toggle dark mode">🌙</button>
|
|
||||||
<button id="share-btn">Share</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="body">
|
<!-- Left sidebar -->
|
||||||
<div class="sidebar" id="sidebar">
|
<div class="sidebar" id="sidebar">
|
||||||
<div class="sidebar-top">
|
<div class="sidebar-top">
|
||||||
<button id="new-note-btn">+ New Note</button>
|
<button class="icon-btn" id="sidebar-toggle" title="Toggle sidebar">☰</button>
|
||||||
|
<span class="sidebar-title">noted.</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="notes-list" id="notes-list"></div>
|
<div class="notes-list" id="notes-list"></div>
|
||||||
|
<div class="sidebar-bottom">
|
||||||
|
<button class="sidebar-bottom-btn" id="new-note-btn" title="New note">
|
||||||
|
<span>+</span>
|
||||||
|
</button>
|
||||||
|
<button class="sidebar-bottom-btn" id="dark-toggle" title="Toggle dark mode">
|
||||||
|
<span id="dark-icon">🌙</span>
|
||||||
|
<span class="btn-label" id="dark-label">Dark mode</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="editor">
|
<!-- Main editor -->
|
||||||
<div class="note-title-bar">
|
<div class="main" id="main">
|
||||||
<input type="text" id="note-title" placeholder="Untitled">
|
<div class="float-actions">
|
||||||
|
<span class="save-flash" id="save-flash">✓ saved</span>
|
||||||
|
<button class="float-icon-btn" id="dark-toggle-alt" title="Dark mode" style="display:none">🌙</button>
|
||||||
|
<button class="float-btn" id="share-btn">Share</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea class="notepad" id="notepad" placeholder="Start typing..."></textarea>
|
<textarea class="notepad" id="notepad" placeholder="Start typing..."></textarea>
|
||||||
<div class="md-view" id="md-view"></div>
|
<div class="md-view" id="md-view"></div>
|
||||||
<div class="canvas-wrap" id="canvas-wrap">
|
<div class="canvas-wrap" id="canvas-wrap">
|
||||||
<canvas id="drawing-canvas"></canvas>
|
<canvas id="drawing-canvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- Drawing controls -->
|
||||||
<div class="draw-controls" id="draw-controls">
|
<div class="draw-controls" id="draw-controls">
|
||||||
<div class="cgroup">
|
<div class="cgroup">
|
||||||
<label>Size</label>
|
<label>Size</label>
|
||||||
|
|
@ -380,10 +434,10 @@
|
||||||
<label>Color</label>
|
<label>Color</label>
|
||||||
<input type="color" id="pen-color" value="#000000">
|
<input type="color" id="pen-color" value="#000000">
|
||||||
</div>
|
</div>
|
||||||
<button id="eraser-btn">Eraser</button>
|
<button class="ctrl" id="eraser-btn">Eraser</button>
|
||||||
<button id="undo-btn">↩ Undo</button>
|
<button class="ctrl" id="undo-btn">↩ Undo</button>
|
||||||
<button id="redo-btn">↪ Redo</button>
|
<button class="ctrl" id="redo-btn">↪ Redo</button>
|
||||||
<button id="clear-canvas-btn">Clear</button>
|
<button class="ctrl" id="clear-canvas-btn">Clear</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
|
@ -400,20 +454,20 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="cgroup">
|
<div class="cgroup">
|
||||||
<label>Size</label>
|
<label>Size</label>
|
||||||
<button id="dec-font">A-</button>
|
<button class="ctrl" id="dec-font">A-</button>
|
||||||
<span id="font-size-label">24px</span>
|
<span id="font-size-label" style="font-size:12px">24px</span>
|
||||||
<button id="inc-font">A+</button>
|
<button class="ctrl" id="inc-font">A+</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="cgroup">
|
<div class="cgroup">
|
||||||
<label>Color</label>
|
<label>Color</label>
|
||||||
<input type="color" id="text-color" value="#000000">
|
<input type="color" id="text-color" value="#000000">
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-right">
|
<div class="controls-right">
|
||||||
<button id="md-btn" title="Toggle Markdown preview (Ctrl+M)">MD</button>
|
<button class="ctrl" id="md-btn" title="Markdown preview (Ctrl+M)">MD</button>
|
||||||
<button id="draw-btn" title="Toggle drawing mode (Ctrl+D)">Draw</button>
|
<button class="ctrl" id="draw-btn" title="Draw mode (Ctrl+D)">Draw</button>
|
||||||
<button id="export-btn">Export</button>
|
<button class="ctrl" id="export-btn">Export</button>
|
||||||
<button id="save-btn" title="Save (Ctrl+S)">Save</button>
|
<button class="ctrl" id="save-btn" title="Save (Ctrl+S)">Save</button>
|
||||||
<button id="clear-btn">Clear</button>
|
<button class="ctrl" id="clear-btn">Clear</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -424,7 +478,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
// ── State ────────────────────────────────────────────────────────────────
|
|
||||||
const USER_ID = (() => {
|
const USER_ID = (() => {
|
||||||
let id = localStorage.getItem('noted_uid');
|
let id = localStorage.getItem('noted_uid');
|
||||||
if (!id) { id = crypto.randomUUID(); localStorage.setItem('noted_uid', id); }
|
if (!id) { id = crypto.randomUUID(); localStorage.setItem('noted_uid', id); }
|
||||||
|
|
@ -433,25 +486,21 @@
|
||||||
|
|
||||||
let notes = JSON.parse(localStorage.getItem('noted_notes') || '{}');
|
let notes = JSON.parse(localStorage.getItem('noted_notes') || '{}');
|
||||||
let currentId = null;
|
let currentId = null;
|
||||||
let dark = document.documentElement.hasAttribute('data-dark');
|
let dark = localStorage.getItem('noted_dark') === 'true';
|
||||||
let isMd = false;
|
let isMd = false, isDraw = false, isEraser = false;
|
||||||
let isDraw = false;
|
|
||||||
let isEraser = false;
|
|
||||||
let ctx = null;
|
let ctx = null;
|
||||||
let undoStack = [], redoStack = [];
|
let undoStack = [], redoStack = [];
|
||||||
let autoSaveTimer = null, syncTimer = null, saveFlashTimer = null;
|
let autoSaveTimer = null, syncTimer = null, flashTimer = null;
|
||||||
|
|
||||||
// ── DOM ──────────────────────────────────────────────────────────────────
|
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
const notepad = $('notepad');
|
const notepad = $('notepad');
|
||||||
const noteTitle = $('note-title');
|
|
||||||
const mdView = $('md-view');
|
const mdView = $('md-view');
|
||||||
const canvasWrap = $('canvas-wrap');
|
const canvasWrap = $('canvas-wrap');
|
||||||
const canvas = $('drawing-canvas');
|
const canvas = $('drawing-canvas');
|
||||||
const drawCtrls = $('draw-controls');
|
const drawCtrls = $('draw-controls');
|
||||||
const sidebar = $('sidebar');
|
const sidebar = $('sidebar');
|
||||||
const notesList = $('notes-list');
|
const notesList = $('notes-list');
|
||||||
const saveInd = $('save-indicator');
|
const saveFlash = $('save-flash');
|
||||||
const wordCount = $('word-count');
|
const wordCount = $('word-count');
|
||||||
const syncStatus = $('sync-status');
|
const syncStatus = $('sync-status');
|
||||||
|
|
||||||
|
|
@ -459,9 +508,9 @@
|
||||||
function persist() { localStorage.setItem('noted_notes', JSON.stringify(notes)); }
|
function persist() { localStorage.setItem('noted_notes', JSON.stringify(notes)); }
|
||||||
|
|
||||||
function flashSaved() {
|
function flashSaved() {
|
||||||
saveInd.classList.add('visible');
|
saveFlash.classList.add('show');
|
||||||
clearTimeout(saveFlashTimer);
|
clearTimeout(flashTimer);
|
||||||
saveFlashTimer = setTimeout(() => saveInd.classList.remove('visible'), 2000);
|
flashTimer = setTimeout(() => saveFlash.classList.remove('show'), 1800);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWordCount() {
|
function updateWordCount() {
|
||||||
|
|
@ -473,14 +522,12 @@
|
||||||
// ── Dark mode ─────────────────────────────────────────────────────────────
|
// ── Dark mode ─────────────────────────────────────────────────────────────
|
||||||
function applyDark() {
|
function applyDark() {
|
||||||
document.documentElement.toggleAttribute('data-dark', dark);
|
document.documentElement.toggleAttribute('data-dark', dark);
|
||||||
$('dark-toggle').textContent = dark ? '☀️' : '🌙';
|
const icon = dark ? '☀️' : '🌙';
|
||||||
|
$('dark-icon').textContent = icon;
|
||||||
|
$('dark-label').textContent = dark ? 'Light mode' : 'Dark mode';
|
||||||
}
|
}
|
||||||
|
function toggleDark() { dark = !dark; localStorage.setItem('noted_dark', dark); applyDark(); }
|
||||||
$('dark-toggle').addEventListener('click', () => {
|
$('dark-toggle').addEventListener('click', toggleDark);
|
||||||
dark = !dark;
|
|
||||||
localStorage.setItem('noted_dark', dark);
|
|
||||||
applyDark();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Sidebar ──────────────────────────────────────────────────────────────
|
// ── Sidebar ──────────────────────────────────────────────────────────────
|
||||||
let sidebarOpen = localStorage.getItem('noted_sidebar') !== 'false';
|
let sidebarOpen = localStorage.getItem('noted_sidebar') !== 'false';
|
||||||
|
|
@ -498,20 +545,62 @@
|
||||||
for (const n of sorted) {
|
for (const n of sorted) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = 'note-item' + (n.id === currentId ? ' active' : '');
|
el.className = 'note-item' + (n.id === currentId ? ' active' : '');
|
||||||
el.innerHTML = `<span class="note-item-name">${n.title || 'Untitled'}</span><button class="note-del" title="Delete">✕</button>`;
|
el.dataset.id = n.id;
|
||||||
el.querySelector('.note-item-name').addEventListener('click', () => openNote(n.id));
|
|
||||||
el.querySelector('.note-del').addEventListener('click', e => { e.stopPropagation(); deleteNote(n.id); });
|
const nameSpan = document.createElement('span');
|
||||||
|
nameSpan.className = 'note-item-name';
|
||||||
|
nameSpan.textContent = n.title || 'Untitled';
|
||||||
|
|
||||||
|
const delBtn = document.createElement('button');
|
||||||
|
delBtn.className = 'note-del';
|
||||||
|
delBtn.textContent = '✕';
|
||||||
|
delBtn.title = 'Delete';
|
||||||
|
delBtn.addEventListener('click', e => { e.stopPropagation(); deleteNote(n.id); });
|
||||||
|
|
||||||
|
el.appendChild(nameSpan);
|
||||||
|
el.appendChild(delBtn);
|
||||||
|
|
||||||
|
// single click = open, double click = rename
|
||||||
|
el.addEventListener('click', () => openNote(n.id));
|
||||||
|
el.addEventListener('dblclick', e => { e.stopPropagation(); startRename(el, n.id); });
|
||||||
|
|
||||||
notesList.appendChild(el);
|
notesList.appendChild(el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startRename(el, id) {
|
||||||
|
const nameSpan = el.querySelector('.note-item-name');
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.className = 'note-rename-input';
|
||||||
|
input.value = notes[id]?.title || '';
|
||||||
|
el.replaceChild(input, nameSpan);
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
|
||||||
|
function commit() {
|
||||||
|
const val = input.value.trim();
|
||||||
|
if (notes[id]) { notes[id].title = val; notes[id].updatedAt = Date.now(); persist(); scheduleSync(); }
|
||||||
|
renderList();
|
||||||
|
}
|
||||||
|
input.addEventListener('blur', commit);
|
||||||
|
input.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Enter') { e.preventDefault(); input.blur(); }
|
||||||
|
if (e.key === 'Escape') { renderList(); }
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ── Note ops ─────────────────────────────────────────────────────────────
|
// ── Note ops ─────────────────────────────────────────────────────────────
|
||||||
function createNote() {
|
function createNote() {
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
notes[id] = { id, title: '', content: '', drawing: null, fontSize: '24px', fontFamily: "'Roboto Mono', monospace", textColor: '#000000', createdAt: Date.now(), updatedAt: Date.now() };
|
notes[id] = { id, title: '', content: '', drawing: null, fontSize: '24px', fontFamily: "'Roboto Mono', monospace", textColor: '#000000', createdAt: Date.now(), updatedAt: Date.now() };
|
||||||
persist();
|
persist();
|
||||||
openNote(id);
|
openNote(id);
|
||||||
noteTitle.focus();
|
// immediately enter rename on the new item
|
||||||
|
setTimeout(() => {
|
||||||
|
const el = notesList.querySelector(`[data-id="${id}"]`);
|
||||||
|
if (el) startRename(el, id);
|
||||||
|
}, 50);
|
||||||
scheduleSync();
|
scheduleSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -520,7 +609,6 @@
|
||||||
if (currentId) saveNote(true);
|
if (currentId) saveNote(true);
|
||||||
currentId = id;
|
currentId = id;
|
||||||
const n = notes[id];
|
const n = notes[id];
|
||||||
noteTitle.value = n.title || '';
|
|
||||||
notepad.value = n.content || '';
|
notepad.value = n.content || '';
|
||||||
notepad.style.fontSize = n.fontSize || '24px';
|
notepad.style.fontSize = n.fontSize || '24px';
|
||||||
notepad.style.fontFamily = n.fontFamily || "'Roboto Mono', monospace";
|
notepad.style.fontFamily = n.fontFamily || "'Roboto Mono', monospace";
|
||||||
|
|
@ -537,12 +625,12 @@
|
||||||
}
|
}
|
||||||
localStorage.setItem('noted_last', id);
|
localStorage.setItem('noted_last', id);
|
||||||
renderList();
|
renderList();
|
||||||
|
if (!sidebarOpen) notepad.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveNote(silent = false) {
|
function saveNote(silent = false) {
|
||||||
if (!currentId || !notes[currentId]) return;
|
if (!currentId || !notes[currentId]) return;
|
||||||
const n = notes[currentId];
|
const n = notes[currentId];
|
||||||
n.title = noteTitle.value;
|
|
||||||
n.content = notepad.value;
|
n.content = notepad.value;
|
||||||
n.fontSize = notepad.style.fontSize || '24px';
|
n.fontSize = notepad.style.fontSize || '24px';
|
||||||
n.fontFamily = notepad.style.fontFamily || "'Roboto Mono', monospace";
|
n.fontFamily = notepad.style.fontFamily || "'Roboto Mono', monospace";
|
||||||
|
|
@ -562,9 +650,7 @@
|
||||||
currentId = null;
|
currentId = null;
|
||||||
const ids = Object.keys(notes);
|
const ids = Object.keys(notes);
|
||||||
if (ids.length) openNote(ids[0]); else createNote();
|
if (ids.length) openNote(ids[0]); else createNote();
|
||||||
} else {
|
} else renderList();
|
||||||
renderList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$('new-note-btn').addEventListener('click', createNote);
|
$('new-note-btn').addEventListener('click', createNote);
|
||||||
|
|
@ -574,9 +660,7 @@
|
||||||
clearTimeout(autoSaveTimer);
|
clearTimeout(autoSaveTimer);
|
||||||
autoSaveTimer = setTimeout(() => saveNote(), 3000);
|
autoSaveTimer = setTimeout(() => saveNote(), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
notepad.addEventListener('input', () => { updateWordCount(); renderMd(); scheduleAutoSave(); });
|
notepad.addEventListener('input', () => { updateWordCount(); renderMd(); scheduleAutoSave(); });
|
||||||
noteTitle.addEventListener('input', () => { if (notes[currentId]) notes[currentId].title = noteTitle.value; renderList(); scheduleAutoSave(); });
|
|
||||||
|
|
||||||
// ── Markdown ──────────────────────────────────────────────────────────────
|
// ── Markdown ──────────────────────────────────────────────────────────────
|
||||||
function renderMd() { if (isMd) mdView.innerHTML = marked.parse(notepad.value || ''); }
|
function renderMd() { if (isMd) mdView.innerHTML = marked.parse(notepad.value || ''); }
|
||||||
|
|
@ -603,15 +687,12 @@
|
||||||
|
|
||||||
// ── Save / Clear / Export ─────────────────────────────────────────────────
|
// ── Save / Clear / Export ─────────────────────────────────────────────────
|
||||||
$('save-btn').addEventListener('click', () => saveNote());
|
$('save-btn').addEventListener('click', () => saveNote());
|
||||||
|
|
||||||
$('clear-btn').addEventListener('click', () => {
|
$('clear-btn').addEventListener('click', () => {
|
||||||
if (!confirm('Clear this note?')) return;
|
if (!confirm('Clear this note?')) return;
|
||||||
notepad.value = ''; renderMd(); updateWordCount(); saveNote();
|
notepad.value = ''; renderMd(); updateWordCount(); saveNote();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('export-btn').addEventListener('click', () => {
|
$('export-btn').addEventListener('click', () => {
|
||||||
const n = notes[currentId];
|
const n = notes[currentId]; if (!n) return;
|
||||||
if (!n) return;
|
|
||||||
const text = (n.title ? n.title + '\n' + '='.repeat(n.title.length) + '\n\n' : '') + n.content;
|
const text = (n.title ? n.title + '\n' + '='.repeat(n.title.length) + '\n\n' : '') + n.content;
|
||||||
const a = Object.assign(document.createElement('a'), {
|
const a = Object.assign(document.createElement('a'), {
|
||||||
href: URL.createObjectURL(new Blob([text], { type: 'text/plain' })),
|
href: URL.createObjectURL(new Blob([text], { type: 'text/plain' })),
|
||||||
|
|
@ -626,7 +707,7 @@
|
||||||
const data = ctx ? canvas.toDataURL() : null;
|
const data = ctx ? canvas.toDataURL() : null;
|
||||||
canvas.width = canvasWrap.clientWidth;
|
canvas.width = canvasWrap.clientWidth;
|
||||||
canvas.height = canvasWrap.clientHeight;
|
canvas.height = canvasWrap.clientHeight;
|
||||||
if (data) { const img = new Image(); img.onload = () => ctx.drawImage(img, 0, 0); img.src = data; }
|
if (data && ctx) { const img = new Image(); img.onload = () => ctx.drawImage(img, 0, 0); img.src = data; }
|
||||||
};
|
};
|
||||||
resize();
|
resize();
|
||||||
ctx = canvas.getContext('2d');
|
ctx = canvas.getContext('2d');
|
||||||
|
|
@ -641,10 +722,7 @@
|
||||||
canvas.addEventListener('mousemove', move); canvas.addEventListener('touchmove', move, { passive: false });
|
canvas.addEventListener('mousemove', move); canvas.addEventListener('touchmove', move, { passive: false });
|
||||||
window.addEventListener('mouseup', stop); window.addEventListener('touchend', stop);
|
window.addEventListener('mouseup', stop); window.addEventListener('touchend', stop);
|
||||||
|
|
||||||
function start(e) {
|
function start(e) { drawing = true; pushUndo(); ctx.beginPath(); const [x,y] = pt(e); ctx.moveTo(x,y); e.preventDefault?.(); }
|
||||||
drawing = true; pushUndo(); ctx.beginPath();
|
|
||||||
const [x, y] = pt(e); ctx.moveTo(x, y); e.preventDefault?.();
|
|
||||||
}
|
|
||||||
function move(e) {
|
function move(e) {
|
||||||
if (!drawing) return;
|
if (!drawing) return;
|
||||||
ctx.lineWidth = $('pen-size').value; ctx.lineCap = 'round';
|
ctx.lineWidth = $('pen-size').value; ctx.lineCap = 'round';
|
||||||
|
|
@ -672,25 +750,21 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
$('pen-size').addEventListener('input', () => { $('pen-size-val').textContent = $('pen-size').value + 'px'; });
|
$('pen-size').addEventListener('input', () => { $('pen-size-val').textContent = $('pen-size').value + 'px'; });
|
||||||
|
|
||||||
$('eraser-btn').addEventListener('click', () => {
|
$('eraser-btn').addEventListener('click', () => {
|
||||||
isEraser = !isEraser;
|
isEraser = !isEraser;
|
||||||
$('eraser-btn').classList.toggle('on', isEraser);
|
$('eraser-btn').classList.toggle('on', isEraser);
|
||||||
$('eraser-btn').textContent = isEraser ? 'Pen' : 'Eraser';
|
$('eraser-btn').textContent = isEraser ? 'Pen' : 'Eraser';
|
||||||
});
|
});
|
||||||
|
|
||||||
$('undo-btn').addEventListener('click', () => {
|
$('undo-btn').addEventListener('click', () => {
|
||||||
if (!undoStack.length) return;
|
if (!undoStack.length) return;
|
||||||
redoStack.push(canvas.toDataURL());
|
redoStack.push(canvas.toDataURL());
|
||||||
const img = new Image(); img.onload = () => { ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(img,0,0); }; img.src = undoStack.pop();
|
const img = new Image(); img.onload = () => { ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(img,0,0); }; img.src = undoStack.pop();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('redo-btn').addEventListener('click', () => {
|
$('redo-btn').addEventListener('click', () => {
|
||||||
if (!redoStack.length) return;
|
if (!redoStack.length) return;
|
||||||
undoStack.push(canvas.toDataURL());
|
undoStack.push(canvas.toDataURL());
|
||||||
const img = new Image(); img.onload = () => { ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(img,0,0); }; img.src = redoStack.pop();
|
const img = new Image(); img.onload = () => { ctx.clearRect(0,0,canvas.width,canvas.height); ctx.drawImage(img,0,0); }; img.src = redoStack.pop();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('clear-canvas-btn').addEventListener('click', () => {
|
$('clear-canvas-btn').addEventListener('click', () => {
|
||||||
if (!confirm('Clear canvas?')) return;
|
if (!confirm('Clear canvas?')) return;
|
||||||
pushUndo(); ctx.clearRect(0,0,canvas.width,canvas.height);
|
pushUndo(); ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||||
|
|
@ -709,8 +783,7 @@
|
||||||
// ── Share ─────────────────────────────────────────────────────────────────
|
// ── Share ─────────────────────────────────────────────────────────────────
|
||||||
$('share-btn').addEventListener('click', async () => {
|
$('share-btn').addEventListener('click', async () => {
|
||||||
saveNote(true);
|
saveNote(true);
|
||||||
const n = notes[currentId];
|
const n = notes[currentId]; if (!n) return;
|
||||||
if (!n) return;
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/notes/' + n.id + '/share', {
|
const res = await fetch('/api/notes/' + n.id + '/share', {
|
||||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -718,7 +791,7 @@
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.token) showShareModal(location.origin + '/share/' + data.token);
|
if (data.token) showShareModal(location.origin + '/share/' + data.token);
|
||||||
else alert('Share failed: ' + (data.error || 'unknown error'));
|
else alert('Share failed: ' + (data.error || 'unknown'));
|
||||||
} catch { alert('Share failed — are you online?'); }
|
} catch { alert('Share failed — are you online?'); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -730,14 +803,12 @@
|
||||||
<p>Anyone with this link can view a read-only copy:</p>
|
<p>Anyone with this link can view a read-only copy:</p>
|
||||||
<input type="text" value="${url}" readonly onclick="this.select()">
|
<input type="text" value="${url}" readonly onclick="this.select()">
|
||||||
<div class="modal-btns">
|
<div class="modal-btns">
|
||||||
<button id="_copy">Copy Link</button>
|
<button class="ctrl" id="_copy">Copy Link</button>
|
||||||
<button id="_close">Close</button>
|
<button class="ctrl" id="_close">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
document.body.appendChild(ov);
|
document.body.appendChild(ov);
|
||||||
ov.querySelector('#_copy').addEventListener('click', async function() {
|
ov.querySelector('#_copy').addEventListener('click', async function() { await navigator.clipboard.writeText(url); this.textContent = 'Copied!'; });
|
||||||
await navigator.clipboard.writeText(url); this.textContent = 'Copied!';
|
|
||||||
});
|
|
||||||
ov.querySelector('#_close').addEventListener('click', () => ov.remove());
|
ov.querySelector('#_close').addEventListener('click', () => ov.remove());
|
||||||
ov.addEventListener('click', e => { if (e.target === ov) ov.remove(); });
|
ov.addEventListener('click', e => { if (e.target === ov) ov.remove(); });
|
||||||
}
|
}
|
||||||
|
|
@ -746,8 +817,7 @@
|
||||||
function scheduleSync() { clearTimeout(syncTimer); syncTimer = setTimeout(syncCurrent, 5000); }
|
function scheduleSync() { clearTimeout(syncTimer); syncTimer = setTimeout(syncCurrent, 5000); }
|
||||||
|
|
||||||
async function syncCurrent() {
|
async function syncCurrent() {
|
||||||
const n = notes[currentId];
|
const n = notes[currentId]; if (!n) return;
|
||||||
if (!n) return;
|
|
||||||
try {
|
try {
|
||||||
syncStatus.textContent = 'syncing…';
|
syncStatus.textContent = 'syncing…';
|
||||||
await fetch('/api/notes/' + n.id, {
|
await fetch('/api/notes/' + n.id, {
|
||||||
|
|
@ -759,12 +829,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncDelete(id) {
|
async function syncDelete(id) {
|
||||||
try {
|
try { await fetch('/api/notes/' + id, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: USER_ID }) }); }
|
||||||
await fetch('/api/notes/' + id, {
|
catch { /* offline */ }
|
||||||
method: 'DELETE', headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ user_id: USER_ID })
|
|
||||||
});
|
|
||||||
} catch { /* offline */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullNotes() {
|
async function pullNotes() {
|
||||||
|
|
@ -784,17 +850,14 @@
|
||||||
|
|
||||||
// ── Share read-only view ──────────────────────────────────────────────────
|
// ── Share read-only view ──────────────────────────────────────────────────
|
||||||
async function loadShareView(token) {
|
async function loadShareView(token) {
|
||||||
document.querySelector('.body').remove();
|
document.body.innerHTML = '';
|
||||||
document.querySelector('.draw-controls').remove();
|
document.body.style.background = 'var(--bg)';
|
||||||
document.querySelector('.controls').remove();
|
|
||||||
document.querySelector('.status-bar').remove();
|
|
||||||
document.querySelector('.header').innerHTML = '<span class="header-title" style="padding:0 12px">noted.</span>';
|
|
||||||
const page = document.createElement('div');
|
const page = document.createElement('div');
|
||||||
page.className = 'share-page';
|
page.className = 'share-page';
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/share/' + token);
|
const res = await fetch('/api/share/' + token);
|
||||||
const { note, error } = await res.json();
|
const { note, error } = await res.json();
|
||||||
if (error || !note) { page.innerHTML = '<p style="color:var(--muted)">Note not found or link expired.</p>'; }
|
if (error || !note) { page.innerHTML = '<p style="color:var(--muted)">Note not found.</p>'; }
|
||||||
else {
|
else {
|
||||||
document.title = (note.title || 'noted.') + ' — noted.';
|
document.title = (note.title || 'noted.') + ' — noted.';
|
||||||
page.innerHTML = `<div class="share-banner">shared via noted.</div><h1>${note.title || 'Untitled'}</h1><div class="content">${marked.parse(note.content || '')}</div>`;
|
page.innerHTML = `<div class="share-banner">shared via noted.</div><h1>${note.title || 'Untitled'}</h1><div class="content">${marked.parse(note.content || '')}</div>`;
|
||||||
|
|
@ -807,7 +870,6 @@
|
||||||
const shareMatch = location.pathname.match(/^\/share\/([a-zA-Z0-9]+)$/);
|
const shareMatch = location.pathname.match(/^\/share\/([a-zA-Z0-9]+)$/);
|
||||||
if (shareMatch) { loadShareView(shareMatch[1]); return; }
|
if (shareMatch) { loadShareView(shareMatch[1]); return; }
|
||||||
|
|
||||||
dark = localStorage.getItem('noted_dark') === 'true';
|
|
||||||
applyDark();
|
applyDark();
|
||||||
applySidebar();
|
applySidebar();
|
||||||
|
|
||||||
|
|
@ -817,15 +879,11 @@
|
||||||
const lastId = localStorage.getItem('noted_last');
|
const lastId = localStorage.getItem('noted_last');
|
||||||
openNote(notes[lastId] ? lastId : ids[0]);
|
openNote(notes[lastId] ? lastId : ids[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderList();
|
renderList();
|
||||||
pullNotes();
|
pullNotes();
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => { saveNote(true); });
|
window.addEventListener('beforeunload', () => saveNote(true));
|
||||||
|
if ('serviceWorker' in navigator) navigator.serviceWorker.register('service-worker.js').catch(() => {});
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.register('service-worker.js').catch(() => {});
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue