diff --git a/front/_colors.scss b/front/_colors.scss
new file mode 100644
index 0000000..6a09684
--- /dev/null
+++ b/front/_colors.scss
@@ -0,0 +1,5 @@
+$accent: #d30f45;
+$background: #09090b;
+$border: #363636;
+$text: #f2f2f7;
+$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..13413ad
--- /dev/null
+++ b/front/index.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+ New paste
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front/style.scss b/front/style.scss
new file mode 100644
index 0000000..9397f15
--- /dev/null
+++ b/front/style.scss
@@ -0,0 +1,111 @@
+@use 'colors' as *;
+
+@use 'inputs';
+@use 'recents';
+@use 'misc';
+
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=JetBrains+Mono&display=swap');
+
+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 15px/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;
+ }
+}
+
+input,
+select {
+ background-color: $background;
+ border: 2px solid $border;
+ color: $text;
+ font: 500 14px/2 "Inter", "system-ui", sans-serif;
+ outline: none;
+ padding: 3px 10px;
+ width: 150px;
+ transition: .15s ease;
+
+ &::placeholder {
+ color: $placeholder;
+ }
+
+ &:hover {
+ border-color: #555;
+ }
+
+ &:focus-visible {
+ border: 2px solid #bbb;
+ }
+}
+
+select {
+ padding: 8px 10px;
+}
+
+select:focus {
+ background-color: $background;
+ transition: none;
+}
+
+option {
+ background-color: $background;
+ color: $text;
+
+ &:focus {
+ background-color: $accent;
+ color: $background;
+ }
+}
+
+button,
+textarea {
+ background-color: inherit;
+ border: none;
+ margin: 0;
+ outline: none;
+ resize: none;
+}
+
diff --git a/static/app.js b/static/app.js
index bef2c56..0e89f6a 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} - 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
deleted file mode 100644
index 5061a27..0000000
--- a/static/index.html
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
- New plak • Plakken
-
-
-
-
-
-
\ No newline at end of file
diff --git a/static/style.css b/static/style.css
index cf339b3..41531dd 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1,98 +1,180 @@
-:root {
- --accent: #be0560;
- --background: #121212;
- --border: #333;
- --text: #e6e6e6;
- --placeholder: #666;
+@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=JetBrains+Mono&display=swap");
+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: #f2f2f7;
+ 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: #f2f2f7;
+}
+
+::-webkit-scrollbar {
+ width: 9px;
+}
+
+::-webkit-scrollbar-track {
+ background-color: #09090b;
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: #444;
+ border-radius: 9px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background-color: #f2f2f7;
+}
+
+body {
+ background-color: #09090b;
+ color: #f2f2f7;
+ 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 15px/1.6 "JetBrains Mono", monospace;
+}
+
+#line-numbers {
+ color: #888;
+ padding: 16px 1px;
+ text-align: center;
+ white-space: pre;
+ width: 30px;
+}
+
+#content {
+ color: #f2f2f7;
+ 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: #f2f2f7;
+ stroke-width: 2;
+ width: 24px;
+}
+
+input,
+select {
+ background-color: #09090b;
+ border: 2px solid #363636;
+ color: #f2f2f7;
+ font: 500 14px/2 "Inter", "system-ui", sans-serif;
+ outline: none;
+ padding: 3px 10px;
+ width: 150px;
+ transition: 0.15s ease;
+}
+input::placeholder,
+select::placeholder {
+ color: #888;
+}
+input:hover,
+select:hover {
+ border-color: #555;
+}
+input:focus-visible,
+select:focus-visible {
+ border: 2px solid #bbb;
+}
+
+select {
+ padding: 8px 10px;
+}
+
+select:focus {
+ background-color: #09090b;
+ transition: none;
+}
+
+option {
+ background-color: #09090b;
+ color: #f2f2f7;
+}
+option:focus {
+ background-color: #d30f45;
+ color: #09090b;
+}
+
+button,
+textarea {
+ background-color: inherit;
+ border: none;
+ margin: 0;
+ outline: none;
+ resize: none;
+}