diff --git a/.env b/.env index a7c0978..c92e397 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -PLAKKEN_LISTEN=:3000 +PLAKKEN_LISTEN=:5000 PLAKKEN_REDIS_ADDRESS=localhost:6379 PLAKKEN_REDIS_USER= PLAKKEN_REDIS_PASSWORD= diff --git a/front/_colors.scss b/front/_colors.scss new file mode 100644 index 0000000..573d933 --- /dev/null +++ b/front/_colors.scss @@ -0,0 +1,5 @@ +$accent: #d30f45; +$background: #0f0f0f; +$border: #363636; +$text: #e5e5e7; +$placeholder: #888; \ No newline at end of file diff --git a/front/_inputs.scss b/front/_inputs.scss new file mode 100644 index 0000000..e69de29 diff --git a/front/_misc.scss b/front/_misc.scss new file mode 100644 index 0000000..5770e2c --- /dev/null +++ b/front/_misc.scss @@ -0,0 +1,23 @@ +@use 'colors' as *; + +::selection { + background-color: $accent; + color: $text; +} + +::-webkit-scrollbar { + width: 9px; +} + +::-webkit-scrollbar-track { + background-color: $background; +} + +::-webkit-scrollbar-thumb { + background-color: #444; + border-radius: 9px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: $text; +} \ No newline at end of file diff --git a/front/_recents.scss b/front/_recents.scss new file mode 100644 index 0000000..70cabde --- /dev/null +++ b/front/_recents.scss @@ -0,0 +1,65 @@ +@use 'colors' as *; + +section#recent-plaks { + bottom: 1rem; + display: flex; + flex-flow: column wrap; + gap: 9px; + position: absolute; + right: 1rem; + transition: .15s ease-in-out; + user-select: none; + + .title { + align-items: center; + gap: 18px; + transition: .15s ease-in-out; + + &:hover { + border-color: #555; + } + } + + .recent-plak { + align-items: center; + border: 3px solid $border; + font-weight: 500; + justify-content: space-between; + outline: none; + padding: 8px 18px; + text-decoration: none; + transition: .15s ease-in-out; + width: 12rem; + + &:focus-visible { + border: 2px solid #bbb; + } + + svg { + border-radius: 15px; + cursor: pointer; + height: 22px; + padding: 5px; + stroke: $text; + stroke-width: 2.25; + width: 22px; + transition: .1s ease-in-out; + + &:hover { + background-color: $border; + } + } + } + + a { + color: inherit; + text-decoration: none; + } +} + +h3 { + font-size: 1.5rem; + margin: 0; + transition: .15s ease-in-out; + width: fit-content; +} \ No newline at end of file diff --git a/front/index.html b/front/index.html new file mode 100644 index 0000000..5f1347d --- /dev/null +++ b/front/index.html @@ -0,0 +1,60 @@ + + + + + + + + + + + New paste + + + + + +
+
1
+ + + +
+
+
+

Recent plaks

+ + + +
+
+ + + \ No newline at end of file diff --git a/front/style.css b/front/style.css new file mode 100644 index 0000000..b316714 --- /dev/null +++ b/front/style.css @@ -0,0 +1,202 @@ +section#recent-plaks { + bottom: 1rem; + display: flex; + flex-flow: column wrap; + gap: 9px; + position: absolute; + right: 1rem; + transition: 0.15s ease-in-out; + user-select: none; +} +section#recent-plaks .title { + align-items: center; + gap: 18px; + transition: 0.15s ease-in-out; +} +section#recent-plaks .title:hover { + border-color: #555; +} +section#recent-plaks .recent-plak { + align-items: center; + border: 3px solid #363636; + font-weight: 500; + justify-content: space-between; + outline: none; + padding: 8px 18px; + text-decoration: none; + transition: 0.15s ease-in-out; + width: 12rem; +} +section#recent-plaks .recent-plak:focus-visible { + border: 2px solid #bbb; +} +section#recent-plaks .recent-plak svg { + border-radius: 15px; + cursor: pointer; + height: 22px; + padding: 5px; + stroke: #e5e5e7; + stroke-width: 2.25; + width: 22px; + transition: 0.1s ease-in-out; +} +section#recent-plaks .recent-plak svg:hover { + background-color: #363636; +} +section#recent-plaks a { + color: inherit; + text-decoration: none; +} + +h3 { + font-size: 1.5rem; + margin: 0; + transition: 0.15s ease-in-out; + width: fit-content; +} + +::selection { + background-color: #d30f45; + color: #e5e5e7; +} + +::-webkit-scrollbar { + width: 9px; +} + +::-webkit-scrollbar-track { + background-color: #0f0f0f; +} + +::-webkit-scrollbar-thumb { + background-color: #444; + border-radius: 9px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #e5e5e7; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url(inter.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(jetbrainsmono.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +body { + background-color: #0f0f0f; + color: #e5e5e7; + font: 400 16px/2 "Inter", "system-ui", sans-serif; + margin: 0; + overflow-x: hidden; +} + +.fr { + display: flex; + flex-flow: row wrap; +} + +#line-numbers, +#content { + font: 400 14px/1.6 "JetBrains Mono", monospace; +} + +#line-numbers { + color: #888; + padding: 16px 1px; + text-align: center; + white-space: pre; + width: 30px; +} + +#content { + color: #e5e5e7; + height: 100%; + min-height: 90vh; + padding: 16px 16px 0 16px; + width: calc(100vw - 65px); +} + +.menu { + gap: 18px; + position: fixed; + right: 16px; + top: 16px; +} +.menu svg { + cursor: pointer; + height: 24px; + fill: none; + margin-bottom: -6px; + stroke: #e5e5e7; + stroke-width: 2; + width: 24px; +} + +button, +input, +select { + background-color: #0f0f0f; + border: 2px solid #363636; + color: #e5e5e7; + font: 500 14px/2 "Inter", "system-ui", sans-serif; + max-width: 145px; + outline: none; + padding: 3px 10px; + transition: border 0.15s ease; + width: min-content; +} +button::placeholder, +input::placeholder, +select::placeholder { + color: #888; +} +button:hover, +input:hover, +select:hover { + border-color: #777; +} +button:focus-visible, +input:focus-visible, +select:focus-visible { + border: 2px solid #bbb; +} + +select { + padding: 8px 10px; + width: fit-content; +} + +select:focus { + background-color: #0f0f0f; + transition: none; +} + +option { + background-color: #0f0f0f; + color: #e5e5e7; +} +option:focus { + background-color: #d30f45; + color: #0f0f0f; +} + +textarea { + background-color: inherit; + border: none; + margin: 0; + outline: none; + resize: none; +} + +/*# sourceMappingURL=style.css.map */ diff --git a/front/style.scss b/front/style.scss new file mode 100644 index 0000000..3826ef7 --- /dev/null +++ b/front/style.scss @@ -0,0 +1,130 @@ +@use 'colors' as *; + +@use 'inputs'; +@use 'recents'; +@use 'misc'; + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url(inter.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(jetbrainsmono.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + + +body { + background-color: $background; + color: $text; + font: 400 16px/2 "Inter", "system-ui", sans-serif; + margin: 0; + overflow-x: hidden; +} + +.fr { + display: flex; + flex-flow: row wrap; +} + +#line-numbers, +#content { + font: 400 14px/1.6 "JetBrains Mono", monospace; +} + +#line-numbers { + color: $placeholder; + padding: 16px 1px; + text-align: center; + white-space: pre; + width: 30px; +} + +#content { + color: $text; + height: 100%; + min-height: 90vh; + padding: 16px 16px 0 16px; + width: calc(100vw - 65px); +} + +.menu { + gap: 18px; + position: fixed; + right: 16px; + top: 16px; + + svg { + cursor: pointer; + height: 24px; + fill: none; + margin-bottom: -6px; + stroke: $text; + stroke-width: 2; + width: 24px; + } +} + +button, +input, +select { + background-color: $background; + border: 2px solid $border; + color: $text; + font: 500 14px/2 "Inter", "system-ui", sans-serif; + max-width: 145px; + outline: none; + padding: 3px 10px; + transition: border .15s ease; + width: min-content; + + + &::placeholder { + color: $placeholder; + } + + &:hover { + border-color: #777; + } + + &:focus-visible { + border: 2px solid #bbb; + } +} + +select { + padding: 8px 10px; + width: fit-content; +} + +select:focus { + background-color: $background; + transition: none; +} + +option { + background-color: $background; + color: $text; + + &:focus { + background-color: $accent; + color: $background; + } +} + +textarea { + background-color: inherit; + border: none; + margin: 0; + outline: none; + resize: none; +} \ No newline at end of file diff --git a/static/app.js b/static/app.js index bef2c56..1e7da49 100644 --- a/static/app.js +++ b/static/app.js @@ -1,31 +1,71 @@ -const codeEditor = document.getElementById('content'); -const lineCounter = document.getElementById('lines'); +const editor = document.getElementById('content'); +const filenameSelector = document.getElementById('filename'); +const recentPlaksDiv = document.getElementById('recent-plaks'); +const lineNumbersDiv = document.getElementById('line-numbers'); -let lineCountCache = 0; +function updateLn() { + const lines = editor.value.split('\n').length; + const lineNumbers = Array.from({ length: lines }, (_, i) => i + 1).join('\n'); -// Update line counter -function updateLineCounter() { - const lineCount = codeEditor.value.split('\n').length; - - if (lineCountCache !== lineCount) { - const outarr = Array.from({length: lineCount}, (_, index) => index + 1); - lineCounter.value = outarr.join('\n'); - } - - lineCountCache = lineCount; + lineNumbersDiv.textContent = lineNumbers; } -codeEditor.addEventListener('input', updateLineCounter); +function updateTitle() { + document.title = filenameSelector.value == '' ? 'New paste' : ` ${filenameSelector.value} (Unsaved) - Plakken`; +} -codeEditor.addEventListener('keydown', (e) => { - if (e.key === 'Tab') { - e.preventDefault(); +function getRecentPlaksFromStorage() { + return new Set(JSON.parse(localStorage.getItem('recentPlaks')) || []); +} - const {value, selectionStart, selectionEnd} = codeEditor; - codeEditor.value = `${value.slice(0, selectionStart)}\t${value.slice(selectionEnd)}`; - codeEditor.setSelectionRange(selectionStart + 1, selectionStart + 1); - updateLineCounter(); - } -}); +function updateLocalStorage() { + localStorage.setItem('recentPlaks', JSON.stringify(Array.from(recentPlaks))); +} -updateLineCounter(); +function addRecentPlak(plakId) { + recentPlaks.add(plakId); + updateLocalStorage(); +} + +function deleteRecentPlak(plakId) { + const plak = document.querySelector(`[href="/${plakId}/"]`).parentElement; + + plak.style.transform = 'translateX(150%)'; + setTimeout(() => plak.remove(), 250); + + recentPlaks.delete(plakId); + updateLocalStorage(); +} + +function createRecentPlakComponent(plak) { + const div = document.createElement('div'); + div.classList.add('recent-plak', 'fr'); + + const a = document.createElement('a'); + a.href = `/${plak}/`; + a.textContent = plak; + + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.innerHTML = ``; + svg.id = 'cross'; + svg.setAttribute('viewBox', '0 0 24 24'); + svg.onclick = () => deleteRecentPlak(plak); + + div.appendChild(a); + div.appendChild(svg); + + return div; +} + +filenameSelector.addEventListener('input', updateTitle); +editor.addEventListener('input', updateLn); + +let recentPlaks = getRecentPlaksFromStorage(); + +if (recentPlaks === null) { + recentPlaks = []; + localStorage.setItem('recentPlaks', JSON.stringify(recentPlaks)); +} else { + for (const plak of recentPlaks) + recentPlaksDiv.appendChild(createRecentPlakComponent(plak)); +} diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..1f47bd4 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..76a4e48 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/index.html b/static/index.html index 8459130..82763a0 100644 --- a/static/index.html +++ b/static/index.html @@ -1,67 +1,60 @@ + - - + - New plak • Plakken + + New paste + + -
-
- - -
-
- - - + +
1
+ + + +
+
+

Recent plaks

+ + + +
+
+ \ No newline at end of file diff --git a/static/inter.woff2 b/static/inter.woff2 new file mode 100644 index 0000000..4025543 Binary files /dev/null and b/static/inter.woff2 differ diff --git a/static/jetbrainsmono.woff2 b/static/jetbrainsmono.woff2 new file mode 100644 index 0000000..c3f046f Binary files /dev/null and b/static/jetbrainsmono.woff2 differ diff --git a/static/style.css b/static/style.css index cf339b3..7706b3c 100644 --- a/static/style.css +++ b/static/style.css @@ -1,98 +1,200 @@ -:root { - --accent: #be0560; - --background: #121212; - --border: #333; - --text: #e6e6e6; - --placeholder: #666; +section#recent-plaks { + bottom: 1rem; + display: flex; + flex-flow: column wrap; + gap: 9px; + position: absolute; + right: 1rem; + transition: 0.15s ease-in-out; + user-select: none; +} +section#recent-plaks .title { + align-items: center; + gap: 18px; + transition: 0.15s ease-in-out; +} +section#recent-plaks .title:hover { + border-color: #555; +} +section#recent-plaks .recent-plak { + align-items: center; + border: 3px solid #363636; + font-weight: 500; + justify-content: space-between; + outline: none; + padding: 8px 18px; + text-decoration: none; + transition: 0.15s ease-in-out; + width: 12rem; +} +section#recent-plaks .recent-plak:focus-visible { + border: 2px solid #bbb; +} +section#recent-plaks .recent-plak svg { + border-radius: 15px; + cursor: pointer; + height: 22px; + padding: 5px; + stroke: #e5e5e7; + stroke-width: 2.25; + width: 22px; + transition: 0.1s ease-in-out; +} +section#recent-plaks .recent-plak svg:hover { + background-color: #363636; +} +section#recent-plaks a { + color: inherit; + text-decoration: none; } -body { - background-color: var(--background); - color: var(--text); - font: 400 15px/2 "system-ui", monospace; - margin: 0; -} - -form { - display: flex; - flex-flow: row wrap; -} - -#lines { - color: var(--placeholder); - font: 400 14px/1.6 "JetBrains Mono", monospace; - height: calc(100vh - 29px); - overflow-y: hidden; - padding: 8px 0; - text-align: center; - user-select: none; - width: 26px; -} - -input, select { - background-color: var(--background); - border: 2px solid var(--border); - border-radius: 2px; - color: var(--text); - font-size: 13px; - outline: none; - padding: 6px 8px; - transition: border .15s ease; -} - -input:hover, select:hover { - border-color: var(--text); -} - -button, textarea { - background-color: inherit; - border: none; - outline: none; - resize: none; -} - -#content { - color: var(--text); - font: 400 14px/1.6 "JetBrains Mono", monospace; - height: calc(100vh - 29px); - padding: 8px; - width: calc(100vw - 45px); -} - -nav { - top: 1rem; - display: flex; - flex-flow: row wrap; - position: absolute; - right: 12px; -} - -ul { - display: flex; - flex-flow: row wrap; - gap: 36px; - list-style: none; - margin: 0; - padding: 0 1.9rem; -} - -svg { - cursor: pointer; - height: 24px; - fill: var(--text); - transition: .15s ease; - width: 24px; -} - -svg:hover { - stroke: #fff; -} - -input:focus-visible, select:focus-visible { - border: 2px solid var(--text); +h3 { + font-size: 1.5rem; + margin: 0; + transition: 0.15s ease-in-out; + width: fit-content; } ::selection { - background-color: var(--accent); - color: #fff; -} \ No newline at end of file + background-color: #d30f45; + color: #e5e5e7; +} + +::-webkit-scrollbar { + width: 9px; +} + +::-webkit-scrollbar-track { + background-color: #0f0f0f; +} + +::-webkit-scrollbar-thumb { + background-color: #444; + border-radius: 9px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #e5e5e7; +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url(inter.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(jetbrainsmono.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +body { + background-color: #0f0f0f; + color: #e5e5e7; + font: 400 16px/2 "Inter", "system-ui", sans-serif; + margin: 0; + overflow-x: hidden; +} + +.fr { + display: flex; + flex-flow: row wrap; +} + +#line-numbers, +#content { + font: 400 14px/1.6 "JetBrains Mono", monospace; +} + +#line-numbers { + color: #888; + padding: 16px 1px; + text-align: center; + white-space: pre; + width: 30px; +} + +#content { + color: #e5e5e7; + height: 100%; + min-height: 90vh; + padding: 16px 16px 0 16px; + width: calc(100vw - 65px); +} + +.menu { + gap: 18px; + position: fixed; + right: 16px; + top: 16px; +} +.menu svg { + cursor: pointer; + height: 24px; + fill: none; + margin-bottom: -6px; + stroke: #e5e5e7; + stroke-width: 2; + width: 24px; +} + +button, +input, +select { + background-color: #0f0f0f; + border: 2px solid #363636; + color: #e5e5e7; + font: 500 14px/2 "Inter", "system-ui", sans-serif; + max-width: 145px; + outline: none; + padding: 3px 10px; + transition: border 0.15s ease; + width: min-content; +} +button::placeholder, +input::placeholder, +select::placeholder { + color: #888; +} +button:hover, +input:hover, +select:hover { + border-color: #777; +} +button:focus-visible, +input:focus-visible, +select:focus-visible { + border: 2px solid #bbb; +} + +select { + padding: 8px 10px; + width: fit-content; +} + +select:focus { + background-color: #0f0f0f; + transition: none; +} + +option { + background-color: #0f0f0f; + color: #e5e5e7; +} +option:focus { + background-color: #d30f45; + color: #0f0f0f; +} + +textarea { + background-color: inherit; + border: none; + margin: 0; + outline: none; + resize: none; +}