Compare commits
6 Commits
v0.1.7
...
059329c696
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
059329c696 | ||
|
|
5c826e6b93 | ||
|
|
6f76c46299 | ||
|
|
4feb6a880c | ||
|
|
45a6952c60 | ||
|
|
85c6c44393 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,6 +14,7 @@ dist
|
|||||||
dist-ssr
|
dist-ssr
|
||||||
coverage
|
coverage
|
||||||
*.local
|
*.local
|
||||||
|
out
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
@@ -68,12 +68,14 @@ class Config {
|
|||||||
}
|
}
|
||||||
parsed = {
|
parsed = {
|
||||||
...parsed ? parsed : {},
|
...parsed ? parsed : {},
|
||||||
activeAdapter: this.defaultPlugin.id
|
activeAdapter: this.defaultPlugin.id,
|
||||||
|
adapters: {}
|
||||||
};
|
};
|
||||||
parsed.adapters[this.defaultPlugin.id] = defaultConfig;
|
parsed.adapters[this.defaultPlugin.id] = defaultConfig;
|
||||||
|
parsed[theme] = "dark";
|
||||||
await this.write(parsed);
|
await this.write(parsed);
|
||||||
} else {
|
} else {
|
||||||
parsed.adapterConfig = this._resolveDefaults(parsed.adapterConfig);
|
parsed.adapters = this._resolveDefaults(parsed.adapters);
|
||||||
}
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
@@ -82,7 +84,7 @@ class Config {
|
|||||||
await fs.mkdir(dir, { recursive: true });
|
await fs.mkdir(dir, { recursive: true });
|
||||||
const resolvedConfig = {
|
const resolvedConfig = {
|
||||||
...configObject,
|
...configObject,
|
||||||
adapterConfig: this._resolveDefaults(configObject.adapterConfig)
|
adapters: this._resolveDefaults(configObject.adapters)
|
||||||
};
|
};
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
this.configPath,
|
this.configPath,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,907 +0,0 @@
|
|||||||
@charset "UTF-8";
|
|
||||||
:root {
|
|
||||||
--black: #181818;
|
|
||||||
--white: #D5D5D5;
|
|
||||||
--grey-100: #747474;
|
|
||||||
--green: #87FF5B;
|
|
||||||
--blue: #5B92FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root .theme-dark {
|
|
||||||
--theme-bg: #181818;
|
|
||||||
--theme-fg: #D5D5D5;
|
|
||||||
--theme-accent: #87FF5B;
|
|
||||||
--theme-link: #5B92FF;
|
|
||||||
}
|
|
||||||
:root .theme-light {
|
|
||||||
--theme-bg: #D5D5D5;
|
|
||||||
--theme-fg: #181818;
|
|
||||||
--theme-accent: #87FF5B;
|
|
||||||
--theme-link: #5B92FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
|
|
||||||
--ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
|
||||||
--ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
|
|
||||||
--ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
|
||||||
--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
|
||||||
--ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
|
|
||||||
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
||||||
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
||||||
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
|
|
||||||
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
|
|
||||||
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
|
|
||||||
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
|
|
||||||
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
|
||||||
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
||||||
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
|
|
||||||
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
|
|
||||||
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
|
|
||||||
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
|
|
||||||
--ease-custom: cubic-bezier(0.315, 0.365, 0.23, 0.985);
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
The new CSS reset - version 1.9 (last updated 19.6.2023)
|
|
||||||
GitHub page: https://github.com/elad2412/the-new-css-reset
|
|
||||||
***/
|
|
||||||
/*
|
|
||||||
Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
|
|
||||||
- The "symbol *" part is to solve Firefox SVG sprite bug
|
|
||||||
- The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
|
|
||||||
*/
|
|
||||||
*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
|
|
||||||
all: unset;
|
|
||||||
display: revert;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Preferred box-sizing value */
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reapply the pointer cursor for anchor tags */
|
|
||||||
a,
|
|
||||||
button {
|
|
||||||
cursor: revert;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For images to not be able to exceed their container */
|
|
||||||
img {
|
|
||||||
max-inline-size: 100%;
|
|
||||||
max-block-size: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* removes spacing between cells in tables */
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
|
|
||||||
input,
|
|
||||||
textarea {
|
|
||||||
-webkit-user-select: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* revert the 'white-space' property for textarea elements on Safari */
|
|
||||||
textarea {
|
|
||||||
white-space: revert;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* minimum style to allow to style meter element */
|
|
||||||
meter {
|
|
||||||
-webkit-appearance: revert;
|
|
||||||
appearance: revert;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* preformatted text - use only for this feature */
|
|
||||||
:where(pre) {
|
|
||||||
all: revert;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* reset default text opacity of input placeholder */
|
|
||||||
::placeholder {
|
|
||||||
color: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* remove default dot (•) sign */
|
|
||||||
::marker {
|
|
||||||
content: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix the feature of 'hidden' attribute.
|
|
||||||
display:revert; revert to element instead of attribute */
|
|
||||||
:where([hidden]) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* revert for bug in Chromium browsers
|
|
||||||
- fix for the content editable attribute will work properly.
|
|
||||||
- webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/
|
|
||||||
:where([contenteditable]:not([contenteditable=false])) {
|
|
||||||
-moz-user-modify: read-write;
|
|
||||||
-webkit-user-modify: read-write;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
-webkit-line-break: after-white-space;
|
|
||||||
-webkit-user-select: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* apply back the draggable feature - exist only in Chromium and Safari */
|
|
||||||
:where([draggable=true]) {
|
|
||||||
-webkit-user-drag: element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Revert Modal native behavior */
|
|
||||||
:where(dialog:modal) {
|
|
||||||
all: revert;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-width {
|
|
||||||
width: 100vw;
|
|
||||||
position: relative;
|
|
||||||
left: 50%;
|
|
||||||
right: 50%;
|
|
||||||
margin-left: -50vw;
|
|
||||||
margin-right: -50vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overflow-hidden {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
html:not(.has-scroll-smooth) .hide-on-native-scroll {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Font Weights:
|
|
||||||
100 - Thin
|
|
||||||
200 - Extra Light (Ultra Light)
|
|
||||||
300 - Light
|
|
||||||
400 - Normal
|
|
||||||
500 - Medium
|
|
||||||
600 - Semi Bold (Demi Bold)
|
|
||||||
700 - Bold
|
|
||||||
800 - Extra Bold (Ultra Bold)
|
|
||||||
900 - Black (Heavy)
|
|
||||||
*/
|
|
||||||
/* Leibniz Fraktur */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Leibniz Fraktur";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url("./neuefraktur-A4S1ACH2.woff2") format("woff2"), url("./neuefraktur-CwjUIZ0G.woff") format("woff");
|
|
||||||
}
|
|
||||||
/* Geist Mono */
|
|
||||||
@font-face {
|
|
||||||
font-family: "Geist Mono";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: url("./geist-mono-bold-CTLtpKvJ.woff2") format("woff2"), url("./geist-mono-bold-Bz_UliG4.woff") format("woff");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Geist Mono";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url("./geist-mono-BzrJhchg.woff2") format("woff2"), url("./geist-mono-OFKGen7b.woff") format("woff");
|
|
||||||
}
|
|
||||||
:root {
|
|
||||||
--font-display: 'Leibniz Fraktur', serif;
|
|
||||||
--font-mono: 'Geist Mono', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--layout-column-count: 6;
|
|
||||||
--layout-column-gap: 10px;
|
|
||||||
--layout-margin: 20px;
|
|
||||||
--layout-width: calc(100vw - (2 * var(--layout-margin)));
|
|
||||||
--layout-column-width: calc(
|
|
||||||
(
|
|
||||||
var(--layout-width) -
|
|
||||||
(
|
|
||||||
(var(--layout-column-count) - 1) *
|
|
||||||
var(--layout-column-gap)
|
|
||||||
)
|
|
||||||
) /
|
|
||||||
var(--layout-column-count)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-block, .layout-grid {
|
|
||||||
padding-left: var(--layout-margin);
|
|
||||||
padding-right: var(--layout-margin);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(var(--layout-column-count), minmax(0, 1fr));
|
|
||||||
grid-gap: var(--layout-column-gap);
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
html * {
|
|
||||||
scrollbar-width: none !important;
|
|
||||||
-ms-overflow-style: none !important;
|
|
||||||
}
|
|
||||||
html::-webkit-scrollbar,
|
|
||||||
html *::-webkit-scrollbar {
|
|
||||||
width: 0 !important;
|
|
||||||
height: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 400ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-fade-enter-active,
|
|
||||||
.quick-fade-leave-active {
|
|
||||||
transition: opacity 100ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-fade-enter-from,
|
|
||||||
.quick-fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slow-fade-enter-active,
|
|
||||||
.slow-fade-leave-active {
|
|
||||||
transition: opacity 600ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slow-fade-enter-from,
|
|
||||||
.slow-fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-up-enter-active,
|
|
||||||
.slide-up-leave-active {
|
|
||||||
transition: transform 400ms var(--ease-out-quad);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-up-enter-from,
|
|
||||||
.slide-up-leave-to {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-left-enter-active,
|
|
||||||
.slide-left-leave-active {
|
|
||||||
transition: transform 400ms var(--ease-out-quad);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-left-enter-from,
|
|
||||||
.slide-left-leave-to {
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
background: var(--black);
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h1,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-weight: 400;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
line-height: 1.3;
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
.h1.mono,
|
|
||||||
h1.mono,
|
|
||||||
h2.mono,
|
|
||||||
h3.mono,
|
|
||||||
h4.mono,
|
|
||||||
h5.mono,
|
|
||||||
h6.mono {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1;
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p,
|
|
||||||
p,
|
|
||||||
a,
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
pre,
|
|
||||||
span,
|
|
||||||
label,
|
|
||||||
li {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
color: var(--theme-bg);
|
|
||||||
background: var(--theme-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-selection {
|
|
||||||
color: var(--theme-bg);
|
|
||||||
background: var(--theme-fg);
|
|
||||||
}.theme-switcher {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.theme-switcher button {
|
|
||||||
background: var(--theme-bg);
|
|
||||||
display: block;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.theme-switcher button.active {
|
|
||||||
border: 1px solid var(--theme-fg);
|
|
||||||
}
|
|
||||||
.theme-switcher:hover {
|
|
||||||
color: var(--theme-fg) !important;
|
|
||||||
}.nav {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding-top: 9px;
|
|
||||||
color: var(--grey-100);
|
|
||||||
}.menu {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: var(--theme-bg);
|
|
||||||
border-bottom: 1px solid var(--grey-100);
|
|
||||||
}
|
|
||||||
.menu .menu-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-top: 3px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
.menu .menu-wrap .menu-item {
|
|
||||||
padding: 16px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.menu .menu-wrap .menu-item:not(:last-child) {
|
|
||||||
border-bottom: 1px dashed currentColor;
|
|
||||||
}
|
|
||||||
.menu .menu-wrap .menu-item:hover {
|
|
||||||
color: var(--theme-accent);
|
|
||||||
}
|
|
||||||
.menu.menu-enter-active, .menu.menu-leave-active {
|
|
||||||
transition: transform 300ms var(--ease-out-expo);
|
|
||||||
}
|
|
||||||
.menu.menu-enter-from, .menu.menu-leave-to {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
}.scroll-bar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 8px;
|
|
||||||
will-change: transform;
|
|
||||||
border-left: 1px solid var(--grey-100);
|
|
||||||
}
|
|
||||||
.scroll-bar .inner {
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.scroll-bar .inner .handle {
|
|
||||||
width: 100%;
|
|
||||||
height: 388px;
|
|
||||||
background: var(--grey-100);
|
|
||||||
border-radius: 20px;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
will-change: transform;
|
|
||||||
}.container {
|
|
||||||
min-height: calc(100 * var(--vh));
|
|
||||||
max-width: 100vw;
|
|
||||||
overflow-x: clip;
|
|
||||||
background: var(--theme-bg);
|
|
||||||
color: var(--theme-fg);
|
|
||||||
transition: opacity 400ms;
|
|
||||||
}
|
|
||||||
.container:not(.fonts-ready) {
|
|
||||||
opacity: 0;
|
|
||||||
}.category-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 26px 1fr auto;
|
|
||||||
align-items: flex-start;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
padding: 8px 0 6px;
|
|
||||||
border-bottom: 1px dashed currentColor;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.category-row .index {
|
|
||||||
margin-top: 19px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.category-row .title {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
display: -webkit-box;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
}
|
|
||||||
.category-row .category-input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-weight: 400;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
line-height: 1.3;
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
.category-row .category-input:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.category-row .edit-button,
|
|
||||||
.category-row .save-button {
|
|
||||||
color: var(--grey-100);
|
|
||||||
cursor: pointer;
|
|
||||||
padding-right: 0.5em;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
margin-top: 1.5em;
|
|
||||||
}
|
|
||||||
.category-row .edit-button {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.category-row.editable:hover .edit-button {
|
|
||||||
opacity: 1;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.category-row.router-link-exact-active, .category-row.editable {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.category-row:hover:not(.router-link-exact-active):not(.editable) {
|
|
||||||
color: var(--theme-accent);
|
|
||||||
}.note-row {
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
display: grid;
|
|
||||||
width: 100%;
|
|
||||||
gap: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.note-row .title {
|
|
||||||
width: calc(100% - 43.2px);
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.note-row .title::after {
|
|
||||||
content: "(open)";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
font-weight: 700;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.note-row:hover {
|
|
||||||
color: var(--theme-accent);
|
|
||||||
}
|
|
||||||
.note-row:hover .title::after {
|
|
||||||
opacity: 1;
|
|
||||||
}main.directory {
|
|
||||||
padding-top: 18px;
|
|
||||||
padding-bottom: 30px;
|
|
||||||
}
|
|
||||||
main.directory .label {
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin: 17px 0 24px;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
main.directory .notes {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
}.note-menu {
|
|
||||||
display: flex;
|
|
||||||
gap: 5px;
|
|
||||||
border: 1px solid var(--grey-100);
|
|
||||||
color: var(--grey-100);
|
|
||||||
border-radius: 0.2em;
|
|
||||||
background: var(--theme-bg);
|
|
||||||
}
|
|
||||||
.note-menu button {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
}
|
|
||||||
.note-menu button:hover {
|
|
||||||
background: var(--grey-100);
|
|
||||||
color: var(--theme-bg);
|
|
||||||
}
|
|
||||||
.note-menu button.active {
|
|
||||||
background: var(--theme-fg);
|
|
||||||
color: var(--theme-bg);
|
|
||||||
}
|
|
||||||
.note-editor h1 {
|
|
||||||
font-weight: 700 !important;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.note-editor h1:first-child {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400 !important;
|
|
||||||
}
|
|
||||||
.note-editor h1:first-child:first-child::first-letter {
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-size: 42px;
|
|
||||||
}
|
|
||||||
.note-editor h1.is-editor-empty:first-child::before {
|
|
||||||
color: var(--grey-100);
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
pointer-events: none;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400 !important;
|
|
||||||
}
|
|
||||||
.note-editor h1.is-editor-empty:first-child::before:first-child::first-letter {
|
|
||||||
font-family: var(--font-display);
|
|
||||||
font-size: 42px;
|
|
||||||
}
|
|
||||||
.note-editor p strong {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.note-editor p em {
|
|
||||||
/* font-style: italic; */
|
|
||||||
color: var(--grey-100);
|
|
||||||
}
|
|
||||||
.note-editor hr {
|
|
||||||
border: 1px dashed currentColor;
|
|
||||||
}
|
|
||||||
.note-editor ul {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
.note-editor ul li {
|
|
||||||
display: list-item;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
.note-editor ul li *:not(:last-child) {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
.note-editor ol {
|
|
||||||
list-style-type: decimal;
|
|
||||||
}
|
|
||||||
.note-editor ol li {
|
|
||||||
display: list-item;
|
|
||||||
margin-left: 1.5em;
|
|
||||||
}
|
|
||||||
.note-editor ol li::marker {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.note-editor li:not(:last-child) {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
.note-editor a {
|
|
||||||
color: var(--theme-link);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.note-editor code {
|
|
||||||
border: 1px solid var(--grey-100);
|
|
||||||
color: var(--theme-accent);
|
|
||||||
padding: 0 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
}
|
|
||||||
.note-editor pre code {
|
|
||||||
display: block;
|
|
||||||
color: inherit;
|
|
||||||
padding: 1em;
|
|
||||||
/* Code styling */
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-comment,
|
|
||||||
.note-editor pre code .hljs-quote {
|
|
||||||
color: #616161;
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-variable,
|
|
||||||
.note-editor pre code .hljs-template-variable,
|
|
||||||
.note-editor pre code .hljs-attribute,
|
|
||||||
.note-editor pre code .hljs-tag,
|
|
||||||
.note-editor pre code .hljs-name,
|
|
||||||
.note-editor pre code .hljs-regexp,
|
|
||||||
.note-editor pre code .hljs-link,
|
|
||||||
.note-editor pre code .hljs-name,
|
|
||||||
.note-editor pre code .hljs-selector-id,
|
|
||||||
.note-editor pre code .hljs-selector-class {
|
|
||||||
color: #f98181;
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-number,
|
|
||||||
.note-editor pre code .hljs-meta,
|
|
||||||
.note-editor pre code .hljs-built_in,
|
|
||||||
.note-editor pre code .hljs-builtin-name,
|
|
||||||
.note-editor pre code .hljs-literal,
|
|
||||||
.note-editor pre code .hljs-type,
|
|
||||||
.note-editor pre code .hljs-params {
|
|
||||||
color: #fbbc88;
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-string,
|
|
||||||
.note-editor pre code .hljs-symbol,
|
|
||||||
.note-editor pre code .hljs-bullet {
|
|
||||||
color: #b9f18d;
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-title,
|
|
||||||
.note-editor pre code .hljs-section {
|
|
||||||
color: #faf594;
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-keyword,
|
|
||||||
.note-editor pre code .hljs-selector-tag {
|
|
||||||
color: #70cff8;
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-emphasis {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.note-editor pre code .hljs-strong {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.note-editor blockquote {
|
|
||||||
border-left: 4px solid var(--grey-100);
|
|
||||||
padding-left: 0.5em;
|
|
||||||
}
|
|
||||||
.note-editor s {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.note-editor s::after {
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 1px;
|
|
||||||
background: currentColor;
|
|
||||||
}
|
|
||||||
.note-editor mark {
|
|
||||||
background: var(--theme-accent);
|
|
||||||
color: var(--theme-bg);
|
|
||||||
padding: 0 0.2em;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] {
|
|
||||||
list-style: none;
|
|
||||||
margin-left: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] li {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] li > label {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
user-select: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] li > label input {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] li > div {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] input[type=checkbox] {
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
width: 1.5em;
|
|
||||||
height: 1.5em;
|
|
||||||
border: 1px solid var(--grey-100);
|
|
||||||
border-radius: 0.2em;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] input[type=checkbox]::after {
|
|
||||||
content: "✓";
|
|
||||||
font-size: 1.5em;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] input[type=checkbox]:checked::after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.note-editor ul[data-type=taskList] ul[data-type=taskList] {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.note-editor .editor-wrap > div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
.note-editor .editor-wrap > div:focus {
|
|
||||||
outline: none;
|
|
||||||
}main.note {
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}main.category .back {
|
|
||||||
display: block;
|
|
||||||
opacity: 0.25;
|
|
||||||
margin-top: 9px;
|
|
||||||
}
|
|
||||||
main.category .category-row {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
main.category .notes {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
margin-top: 9px;
|
|
||||||
}
|
|
||||||
main.category .new-note {
|
|
||||||
display: block;
|
|
||||||
margin: 50px auto 0;
|
|
||||||
}main.instructions .back-link {
|
|
||||||
opacity: 0.25;
|
|
||||||
display: block;
|
|
||||||
margin-top: 9px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
main.instructions .content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
main.instructions .content hr {
|
|
||||||
border-bottom: 1px dashed currentColor;
|
|
||||||
}main.search .back {
|
|
||||||
display: block;
|
|
||||||
opacity: 0.25;
|
|
||||||
margin-top: 9px;
|
|
||||||
}
|
|
||||||
main.search .input-wrap {
|
|
||||||
margin-top: 19px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
main.search .input-wrap input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
padding: 5px 15px 6px;
|
|
||||||
background: var(--theme-bg);
|
|
||||||
--clip-start: 16px;
|
|
||||||
clip-path: polygon(var(--clip-start) 1px, calc(100% - var(--clip-start)) 1px, calc(100% - 1.5px) 50%, calc(100% - var(--clip-start)) calc(100% - 1px), var(--clip-start) calc(100% - 1px), 1.5px 50%);
|
|
||||||
}
|
|
||||||
main.search .input-wrap::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--theme-fg);
|
|
||||||
--clip-start: 15px;
|
|
||||||
clip-path: polygon(var(--clip-start) 0, calc(100% - var(--clip-start)) 0, 100% 50%, calc(100% - var(--clip-start)) 100%, var(--clip-start) 100%, 0% 50%);
|
|
||||||
}
|
|
||||||
main.search .results {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
}.preferences {
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 60px;
|
|
||||||
}
|
|
||||||
.preferences .back {
|
|
||||||
opacity: 0.25;
|
|
||||||
display: block;
|
|
||||||
margin-top: 9px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
.preferences h1 {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.preferences .plugin {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.preferences input[type=radio] {
|
|
||||||
display: block;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
border: 1px solid white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.preferences input[type=radio]:checked {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
.preferences .info .description {
|
|
||||||
color: var(--grey-100);
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
.preferences .config {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
.preferences .config-field {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
.preferences .config-field input {
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid var(--grey-100);
|
|
||||||
border-radius: 0.2em;
|
|
||||||
padding: 0.2em 0.5em;
|
|
||||||
}
|
|
||||||
.preferences .error {
|
|
||||||
color: red;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
.preferences .save-btn {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 16px 0;
|
|
||||||
text-align: center;
|
|
||||||
border-top: 1px dashed currentColor;
|
|
||||||
background: var(--theme-bg);
|
|
||||||
}
|
|
||||||
.preferences .save-btn .svg-spinner {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
.preferences .save-btn:hover {
|
|
||||||
color: var(--theme-accent);
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||||
/>
|
/>
|
||||||
<script type="module" crossorigin src="./assets/index-CzxWU9vx.js"></script>
|
<script type="module" crossorigin src="./assets/index-D2TWwJ08.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-NYhAwsHy.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-BFwBEQYI.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
44
package-lock.json
generated
44
package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"@tiptap/extension-document": "^3.19.0",
|
"@tiptap/extension-document": "^3.19.0",
|
||||||
"@tiptap/extension-highlight": "^3.20.0",
|
"@tiptap/extension-highlight": "^3.20.0",
|
||||||
"@tiptap/extension-list": "^3.20.0",
|
"@tiptap/extension-list": "^3.20.0",
|
||||||
|
"@tiptap/markdown": "^3.20.4",
|
||||||
"@tiptap/starter-kit": "^3.19.0",
|
"@tiptap/starter-kit": "^3.19.0",
|
||||||
"@tiptap/vue-3": "^3.19.0",
|
"@tiptap/vue-3": "^3.19.0",
|
||||||
"@vueuse/core": "^14.2.1",
|
"@vueuse/core": "^14.2.1",
|
||||||
@@ -2382,16 +2383,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/core": {
|
"node_modules/@tiptap/core": {
|
||||||
"version": "3.20.0",
|
"version": "3.20.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.4.tgz",
|
||||||
"integrity": "sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ==",
|
"integrity": "sha512-3i/DG89TFY/b34T5P+j35UcjYuB5d3+9K8u6qID+iUqNPiza015HPIZLuPfE5elNwVdV3EXIoPo0LLeBLgXXAg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ueberdosis"
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@tiptap/pm": "^3.20.0"
|
"@tiptap/pm": "^3.20.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tiptap/extension-blockquote": {
|
"node_modules/@tiptap/extension-blockquote": {
|
||||||
@@ -2752,10 +2753,27 @@
|
|||||||
"@tiptap/pm": "^3.19.0"
|
"@tiptap/pm": "^3.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tiptap/markdown": {
|
||||||
|
"version": "3.20.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tiptap/markdown/-/markdown-3.20.4.tgz",
|
||||||
|
"integrity": "sha512-1ARtZzJ1skQCZi4LyVSmImgg6JIIMP5dEs0FvHXS3a7M3O+uMOUvY1sWeggVZExg8DXoVyHd7BjRIpm7uNRuSw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"marked": "^17.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ueberdosis"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tiptap/core": "^3.20.4",
|
||||||
|
"@tiptap/pm": "^3.20.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tiptap/pm": {
|
"node_modules/@tiptap/pm": {
|
||||||
"version": "3.20.0",
|
"version": "3.20.4",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.4.tgz",
|
||||||
"integrity": "sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A==",
|
"integrity": "sha512-rCHYSBToilBEuI6PtjziHDdRkABH/XqwJ7dG4Amn/SD3yGiZKYCiEApQlTUS2zZeo8DsLeuqqqB4vEOeD4OEPg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"prosemirror-changeset": "^2.3.0",
|
"prosemirror-changeset": "^2.3.0",
|
||||||
@@ -6310,6 +6328,18 @@
|
|||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "17.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz",
|
||||||
|
"integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/matcher": {
|
"node_modules/matcher": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"@tiptap/extension-document": "^3.19.0",
|
"@tiptap/extension-document": "^3.19.0",
|
||||||
"@tiptap/extension-highlight": "^3.20.0",
|
"@tiptap/extension-highlight": "^3.20.0",
|
||||||
"@tiptap/extension-list": "^3.20.0",
|
"@tiptap/extension-list": "^3.20.0",
|
||||||
|
"@tiptap/markdown": "^3.20.4",
|
||||||
"@tiptap/starter-kit": "^3.19.0",
|
"@tiptap/starter-kit": "^3.19.0",
|
||||||
"@tiptap/vue-3": "^3.19.0",
|
"@tiptap/vue-3": "^3.19.0",
|
||||||
"@vueuse/core": "^14.2.1",
|
"@vueuse/core": "^14.2.1",
|
||||||
|
|||||||
@@ -50,13 +50,16 @@ export default class Config {
|
|||||||
parsed = {
|
parsed = {
|
||||||
...(parsed ? parsed : {}),
|
...(parsed ? parsed : {}),
|
||||||
activeAdapter: this.defaultPlugin.id,
|
activeAdapter: this.defaultPlugin.id,
|
||||||
|
adapters: {},
|
||||||
}
|
}
|
||||||
parsed.adapters[this.defaultPlugin.id] = defaultConfig
|
parsed.adapters[this.defaultPlugin.id] = defaultConfig
|
||||||
|
|
||||||
|
parsed[theme] = 'dark'
|
||||||
|
|
||||||
await this.write(parsed)
|
await this.write(parsed)
|
||||||
} else {
|
} else {
|
||||||
// Ensure any "__DEFAULT_USER_DATA__" values are resolved on load
|
// Ensure any "__DEFAULT_USER_DATA__" values are resolved on load
|
||||||
parsed.adapterConfig = this._resolveDefaults(parsed.adapterConfig)
|
parsed.adapters = this._resolveDefaults(parsed.adapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed
|
return parsed
|
||||||
@@ -71,7 +74,7 @@ export default class Config {
|
|||||||
// Resolve defaults before writing
|
// Resolve defaults before writing
|
||||||
const resolvedConfig = {
|
const resolvedConfig = {
|
||||||
...configObject,
|
...configObject,
|
||||||
adapterConfig: this._resolveDefaults(configObject.adapterConfig),
|
adapters: this._resolveDefaults(configObject.adapters),
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="classes" :style="styles">
|
<div :class="classes" :style="styles">
|
||||||
<Nav />
|
<div class="layout">
|
||||||
|
<div class="page">
|
||||||
|
<Nav v-if="$route.name !== 'note'" ref="nav" />
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<div class="layout-container">
|
|
||||||
<router-view :key="$route.fullPath" />
|
<router-view :key="$route.fullPath" />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MoveMenu />
|
<MoveMenu />
|
||||||
</div>
|
</div>
|
||||||
</Suspense>
|
|
||||||
|
|
||||||
<Menu />
|
<Menu />
|
||||||
|
|
||||||
@@ -17,18 +18,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import loadFonts from '@fuzzco/font-loader'
|
import loadFonts from '@fuzzco/font-loader'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize, useElementBounding } from '@vueuse/core'
|
||||||
import Menu from '@/components/Menu.vue'
|
import Menu from '@/components/Menu.vue'
|
||||||
import Nav from '@/components/Nav.vue'
|
import Nav from '@/components/Nav.vue'
|
||||||
import MoveMenu from '@/components/MoveMenu.vue'
|
import MoveMenu from '@/components/MoveMenu.vue'
|
||||||
import ScrollBar from '@/components/ScrollBar.vue'
|
import ScrollBar from '@/components/ScrollBar.vue'
|
||||||
import useConfig from '@/composables/useConfig'
|
import useConfig from '@/composables/useConfig'
|
||||||
|
|
||||||
const { height } = useWindowSize()
|
const nav = ref()
|
||||||
|
|
||||||
// Theme state
|
const { height } = useWindowSize()
|
||||||
|
const { height: navHeight } = useElementBounding(nav)
|
||||||
const { config } = useConfig()
|
const { config } = useConfig()
|
||||||
|
|
||||||
const classes = computed(() => [
|
const classes = computed(() => [
|
||||||
@@ -60,6 +62,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
const styles = computed(() => ({
|
const styles = computed(() => ({
|
||||||
'--vh': height.value ? height.value / 100 + 'px' : '100vh',
|
'--vh': height.value ? height.value / 100 + 'px' : '100vh',
|
||||||
|
'--nav-height': navHeight.value + 'px',
|
||||||
}))
|
}))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -72,10 +75,14 @@ const styles = computed(() => ({
|
|||||||
color: var(--theme-fg);
|
color: var(--theme-fg);
|
||||||
transition: opacity 400ms;
|
transition: opacity 400ms;
|
||||||
|
|
||||||
.layout-container {
|
.layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
min-height: calc(100 * var(--vh));
|
min-height: calc(100 * var(--vh));
|
||||||
|
|
||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.fonts-ready) {
|
&:not(.fonts-ready) {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const openNewCategory = () => {}
|
|||||||
.menu-wrap {
|
.menu-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 1.2em;
|
padding-top: var(--nav-height);
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="open" class="move-menu layout-block">
|
<div v-if="open" class="move-menu layout-block">
|
||||||
|
<button class="cancel-button" @click="close">Cancel</button>
|
||||||
|
|
||||||
<template v-for="(category, i) in categories">
|
<template v-for="(category, i) in categories">
|
||||||
<category-row
|
<category-row
|
||||||
v-if="category !== fromCategory"
|
v-if="category !== fromCategory"
|
||||||
@@ -53,5 +55,10 @@ watch(open, async () => {
|
|||||||
width: 50vw;
|
width: 50vw;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-left: 1px solid var(--grey-100);
|
border-left: 1px solid var(--grey-100);
|
||||||
|
|
||||||
|
.cancel-button {
|
||||||
|
color: var(--grey-100);
|
||||||
|
padding: 9px 0 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="nav layout-block">
|
<nav class="nav layout-block">
|
||||||
|
<div class="left">
|
||||||
|
<router-link v-if="HAS_BACK_BUTTON.includes($route.name)" to="/"
|
||||||
|
><- Back</router-link
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right">
|
||||||
<button @click="toggleMenu">Menu</button>
|
<button @click="toggleMenu">Menu</button>
|
||||||
|
|
||||||
<router-link to="/search">Search</router-link>
|
<router-link to="/search">Search</router-link>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import useMenu from '@/composables/useMenu'
|
import useMenu from '@/composables/useMenu'
|
||||||
|
|
||||||
|
const HAS_BACK_BUTTON = [
|
||||||
|
'category',
|
||||||
|
'create-category',
|
||||||
|
'instructions',
|
||||||
|
'search',
|
||||||
|
'preferences',
|
||||||
|
]
|
||||||
|
|
||||||
const { menuOpen, closeMenu, openMenu } = useMenu()
|
const { menuOpen, closeMenu, openMenu } = useMenu()
|
||||||
|
|
||||||
const toggleMenu = () => {
|
const toggleMenu = () => {
|
||||||
@@ -23,10 +39,17 @@ const toggleMenu = () => {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.nav {
|
.nav {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 9px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
color: var(--grey-100);
|
||||||
|
|
||||||
|
.left,
|
||||||
|
.right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding-top: 9px;
|
}
|
||||||
color: var(--grey-100);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
24
src/renderer/src/components/PageLoading.vue
Normal file
24
src/renderer/src/components/PageLoading.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-loading">
|
||||||
|
<svg-spinner />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import SvgSpinner from '@/components/svg/Spinner.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.page-loading {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
74
src/renderer/src/components/SearchInput.vue
Normal file
74
src/renderer/src/components/SearchInput.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-input">
|
||||||
|
<input
|
||||||
|
v-model="model"
|
||||||
|
type="text"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
ref="input"
|
||||||
|
@input="emit('input', model.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
placeholder: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
const model = defineModel()
|
||||||
|
|
||||||
|
const emit = defineEmits(['input'])
|
||||||
|
|
||||||
|
const input = ref()
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus: () => input.value.focus(),
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.search-input {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
padding: 5px 15px 6px;
|
||||||
|
background: var(--theme-bg);
|
||||||
|
--clip-start: 16px;
|
||||||
|
clip-path: polygon(
|
||||||
|
var(--clip-start) 1px,
|
||||||
|
calc(100% - var(--clip-start)) 1px,
|
||||||
|
calc(100% - 1.5px) 50%,
|
||||||
|
calc(100% - var(--clip-start)) calc(100% - 1px),
|
||||||
|
var(--clip-start) calc(100% - 1px),
|
||||||
|
1.5px 50%
|
||||||
|
);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--grey-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--theme-fg);
|
||||||
|
--clip-start: 15px;
|
||||||
|
clip-path: polygon(
|
||||||
|
var(--clip-start) 0,
|
||||||
|
calc(100% - var(--clip-start)) 0,
|
||||||
|
100% 50%,
|
||||||
|
calc(100% - var(--clip-start)) 100%,
|
||||||
|
var(--clip-start) 100%,
|
||||||
|
0% 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
61
src/renderer/src/components/note/Download.vue
Normal file
61
src/renderer/src/components/note/Download.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<div class="note-download">
|
||||||
|
<button @click="download">{{ noteTitle }}.md ↓</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import _kebabCase from 'lodash/kebabCase'
|
||||||
|
|
||||||
|
const DEFAULT_TITLE = 'Untitled'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
editor: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const noteTitle = computed(() => {
|
||||||
|
if (!props.editor) return DEFAULT_TITLE
|
||||||
|
|
||||||
|
let title
|
||||||
|
const doc = props.editor.state.doc
|
||||||
|
|
||||||
|
const firstNode = doc.firstChild
|
||||||
|
if (!firstNode || firstNode.type.name !== 'heading') title = DEFAULT_TITLE
|
||||||
|
|
||||||
|
title = firstNode.textContent.trim() || DEFAULT_TITLE
|
||||||
|
|
||||||
|
return _kebabCase(title)
|
||||||
|
})
|
||||||
|
|
||||||
|
const download = () => {
|
||||||
|
if (!props.editor) return
|
||||||
|
|
||||||
|
const content = props.editor.getMarkdown()
|
||||||
|
const blob = new Blob([content], { type: 'text/plain' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = `${noteTitle.value}.md`
|
||||||
|
a.click()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.note-download {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: var(--grey-100);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
&:hover {
|
||||||
|
color: var(--theme-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
<editor-menu :editor="editor" />
|
<editor-menu :editor="editor" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<page-loading v-else />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -11,6 +13,7 @@ import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
|||||||
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
||||||
import { TaskList, TaskItem } from '@tiptap/extension-list'
|
import { TaskList, TaskItem } from '@tiptap/extension-list'
|
||||||
import { Highlight } from '@tiptap/extension-highlight'
|
import { Highlight } from '@tiptap/extension-highlight'
|
||||||
|
import PageLoading from '@/components/PageLoading.vue'
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
import Document from '@tiptap/extension-document'
|
import Document from '@tiptap/extension-document'
|
||||||
import { Placeholder } from '@tiptap/extensions'
|
import { Placeholder } from '@tiptap/extensions'
|
||||||
@@ -18,6 +21,7 @@ import { all, createLowlight } from 'lowlight'
|
|||||||
import useNotes from '@/composables/useNotes'
|
import useNotes from '@/composables/useNotes'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import _debounce from 'lodash/debounce'
|
import _debounce from 'lodash/debounce'
|
||||||
|
import { Markdown } from '@tiptap/markdown'
|
||||||
import EditorMenu from './Menu.vue'
|
import EditorMenu from './Menu.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -82,6 +86,7 @@ onMounted(async () => {
|
|||||||
TaskList,
|
TaskList,
|
||||||
TaskItem,
|
TaskItem,
|
||||||
Highlight,
|
Highlight,
|
||||||
|
Markdown,
|
||||||
CodeBlockLowlight.configure({
|
CodeBlockLowlight.configure({
|
||||||
lowlight,
|
lowlight,
|
||||||
enableTabIndentation: true,
|
enableTabIndentation: true,
|
||||||
@@ -94,6 +99,10 @@ onMounted(async () => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
editor.value?.destroy?.()
|
editor.value?.destroy?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
editor,
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -139,7 +148,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
li {
|
li {
|
||||||
display: list-item;
|
display: list-item;
|
||||||
margin-left: 1.5em;
|
margin-left: 1.75em;
|
||||||
|
|
||||||
&::marker {
|
&::marker {
|
||||||
@include p;
|
@include p;
|
||||||
@@ -163,59 +172,6 @@ onBeforeUnmount(() => {
|
|||||||
display: block;
|
display: block;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
|
||||||
/* Code styling */
|
|
||||||
.hljs-comment,
|
|
||||||
.hljs-quote {
|
|
||||||
color: #616161;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-variable,
|
|
||||||
.hljs-template-variable,
|
|
||||||
.hljs-attribute,
|
|
||||||
.hljs-tag,
|
|
||||||
.hljs-name,
|
|
||||||
.hljs-regexp,
|
|
||||||
.hljs-link,
|
|
||||||
.hljs-name,
|
|
||||||
.hljs-selector-id,
|
|
||||||
.hljs-selector-class {
|
|
||||||
color: #f98181;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-number,
|
|
||||||
.hljs-meta,
|
|
||||||
.hljs-built_in,
|
|
||||||
.hljs-builtin-name,
|
|
||||||
.hljs-literal,
|
|
||||||
.hljs-type,
|
|
||||||
.hljs-params {
|
|
||||||
color: #fbbc88;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-string,
|
|
||||||
.hljs-symbol,
|
|
||||||
.hljs-bullet {
|
|
||||||
color: #b9f18d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-title,
|
|
||||||
.hljs-section {
|
|
||||||
color: #faf594;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-keyword,
|
|
||||||
.hljs-selector-tag {
|
|
||||||
color: #70cff8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-emphasis {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-strong {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 4px solid var(--grey-100);
|
border-left: 4px solid var(--grey-100);
|
||||||
|
|||||||
@@ -1,12 +1,242 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="note-find"></div>
|
<div v-if="visible" class="note-find">
|
||||||
|
<div class="find-bar">
|
||||||
|
<!-- <input
|
||||||
|
@keydown.enter="findNext"
|
||||||
|
@keydown.escape="close"
|
||||||
|
ref="inputRef"
|
||||||
|
/> -->
|
||||||
|
<search-input
|
||||||
|
v-model="searchQuery"
|
||||||
|
placeholder="Find..."
|
||||||
|
ref="inputRef"
|
||||||
|
/>
|
||||||
|
<span class="match-count">
|
||||||
|
{{ currentMatch + 1 }} / {{ matches.length }}
|
||||||
|
</span>
|
||||||
|
<button @click="findPrev" :disabled="matches.length === 0">
|
||||||
|
↑
|
||||||
|
</button>
|
||||||
|
<button @click="findNext" :disabled="matches.length === 0">
|
||||||
|
↓
|
||||||
|
</button>
|
||||||
|
<button @click="close">✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({})
|
import { ref, watch, nextTick } from 'vue'
|
||||||
|
import SearchInput from '@/components/SearchInput.vue'
|
||||||
|
import { Highlight } from '@tiptap/extension-highlight'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
editor: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const matches = ref([])
|
||||||
|
const currentMatch = ref(0)
|
||||||
|
const inputRef = ref(null)
|
||||||
|
|
||||||
|
const findInDocument = () => {
|
||||||
|
if (!props.editor || !searchQuery.value) {
|
||||||
|
clearHighlights()
|
||||||
|
matches.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clearHighlights()
|
||||||
|
|
||||||
|
const query = searchQuery.value.toLowerCase()
|
||||||
|
const doc = props.editor.state.doc
|
||||||
|
const foundMatches = []
|
||||||
|
|
||||||
|
doc.descendants((node, pos) => {
|
||||||
|
if (!node.isText || !node.text) return
|
||||||
|
|
||||||
|
const text = node.text.toLowerCase()
|
||||||
|
let start = 0
|
||||||
|
let index
|
||||||
|
|
||||||
|
while ((index = text.indexOf(query, start)) !== -1) {
|
||||||
|
foundMatches.push({
|
||||||
|
from: pos + index,
|
||||||
|
to: pos + index + query.length,
|
||||||
|
})
|
||||||
|
start = index + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
matches.value = foundMatches
|
||||||
|
currentMatch.value = 0
|
||||||
|
|
||||||
|
if (foundMatches.length > 0) {
|
||||||
|
highlightMatch(0)
|
||||||
|
scrollToMatch(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlightMatch = (index) => {
|
||||||
|
if (!props.editor || !matches.value[index]) return
|
||||||
|
|
||||||
|
const { from, to } = matches.value[index]
|
||||||
|
|
||||||
|
props.editor
|
||||||
|
.chain()
|
||||||
|
.setTextSelection({ from, to })
|
||||||
|
.setHighlight({ color: 'var(--theme-accent)' })
|
||||||
|
.run()
|
||||||
|
|
||||||
|
inputRef.value?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearHighlights = () => {
|
||||||
|
if (!props.editor) return
|
||||||
|
|
||||||
|
const { state } = props.editor
|
||||||
|
const { doc } = state
|
||||||
|
|
||||||
|
doc.descendants((node, pos) => {
|
||||||
|
if (node.marks && node.marks.length > 0) {
|
||||||
|
node.marks.forEach((mark) => {
|
||||||
|
if (mark.type.name === 'highlight') {
|
||||||
|
props.editor.chain().focus().unsetHighlight().run()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
props.editor.commands.unsetHighlight()
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToMatch = (index) => {
|
||||||
|
if (!props.editor || !matches.value[index]) return
|
||||||
|
|
||||||
|
const { from } = matches.value[index]
|
||||||
|
props.editor.commands.setTextSelection({ from, to: from })
|
||||||
|
|
||||||
|
const dom = props.editor.view.dom
|
||||||
|
const selection = window.getSelection()
|
||||||
|
|
||||||
|
if (selection && selection.rangeCount > 0) {
|
||||||
|
const range = selection.getRangeAt(0)
|
||||||
|
range.commonAncestorContainer.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findNext = () => {
|
||||||
|
if (matches.value.length === 0) return
|
||||||
|
|
||||||
|
clearHighlights()
|
||||||
|
currentMatch.value = (currentMatch.value + 1) % matches.value.length
|
||||||
|
highlightMatch(currentMatch.value)
|
||||||
|
scrollToMatch(currentMatch.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const findPrev = () => {
|
||||||
|
if (matches.value.length === 0) return
|
||||||
|
|
||||||
|
clearHighlights()
|
||||||
|
currentMatch.value =
|
||||||
|
(currentMatch.value - 1 + matches.value.length) % matches.value.length
|
||||||
|
highlightMatch(currentMatch.value)
|
||||||
|
scrollToMatch(currentMatch.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
clearHighlights()
|
||||||
|
searchQuery.value = ''
|
||||||
|
matches.value = []
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
async (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
await nextTick()
|
||||||
|
inputRef.value?.focus()
|
||||||
|
if (props.editor && searchQuery.value) {
|
||||||
|
console.log('visible change')
|
||||||
|
findInDocument()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.editor,
|
||||||
|
(newEditor) => {
|
||||||
|
if (newEditor && props.visible && searchQuery.value) {
|
||||||
|
console.log('editor change')
|
||||||
|
findInDocument()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(searchQuery, () => {
|
||||||
|
console.log('search change')
|
||||||
|
findInDocument()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.note-find {
|
.note-find {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
.find-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
background: var(--theme-bg);
|
||||||
|
border-bottom: 1px solid var(--grey-100);
|
||||||
|
padding: 12px 10px;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.match-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--grey-100);
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--grey-100);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--theme-fg);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: var(--grey-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const colors = {
|
|||||||
'grey-100': '#747474',
|
'grey-100': '#747474',
|
||||||
green: '#87FF5B',
|
green: '#87FF5B',
|
||||||
blue: '#5B92FF',
|
blue: '#5B92FF',
|
||||||
|
purple: '#94079E',
|
||||||
}
|
}
|
||||||
|
|
||||||
const themes = {
|
const themes = {
|
||||||
@@ -16,7 +17,7 @@ const themes = {
|
|||||||
light: {
|
light: {
|
||||||
bg: colors.white,
|
bg: colors.white,
|
||||||
fg: colors.black,
|
fg: colors.black,
|
||||||
accent: colors.green,
|
accent: colors.purple,
|
||||||
link: colors.blue,
|
link: colors.blue,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
52
src/renderer/src/styles/_syntax.scss
Normal file
52
src/renderer/src/styles/_syntax.scss
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/* Code styling */
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: var(--grey-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-tag,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class {
|
||||||
|
color: #ff5b69;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-number,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-builtin-name,
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-params {
|
||||||
|
color: #ffa55b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet {
|
||||||
|
color: var(--theme-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-section {
|
||||||
|
color: #fcff5b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag {
|
||||||
|
color: #5b9aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
@use 'font-style' as *;
|
@use 'font-style' as *;
|
||||||
@use 'layout' as *;
|
@use 'layout' as *;
|
||||||
@use 'scroll' as *;
|
@use 'scroll' as *;
|
||||||
|
@use 'syntax' as *;
|
||||||
@use 'transitions' as *;
|
@use 'transitions' as *;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="category layout-block">
|
<main class="category layout-block">
|
||||||
<router-link class="back" to="/"><- Go Back</router-link>
|
|
||||||
|
|
||||||
<category-row
|
<category-row
|
||||||
:index="categoryIndex"
|
:index="categoryIndex"
|
||||||
:category="id"
|
:category="id"
|
||||||
@@ -61,13 +59,8 @@ const categoryIndex = computed(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.category {
|
main.category {
|
||||||
padding-top: 1.24em;
|
padding-top: var(--nav-height);
|
||||||
|
|
||||||
.back {
|
|
||||||
display: block;
|
|
||||||
opacity: 0.25;
|
|
||||||
margin-top: 9px;
|
|
||||||
}
|
|
||||||
.category-row {
|
.category-row {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ const onCategoryEdited = (name) => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.create-category {
|
.create-category {
|
||||||
padding-top: 1.2em;
|
padding-top: var(--nav-height);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="directory layout-block">
|
<main v-if="loaded" class="directory layout-block">
|
||||||
<category-row
|
<category-row
|
||||||
v-for="(category, i) in categories"
|
v-for="(category, i) in categories"
|
||||||
:index="i"
|
:index="i"
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
<note-row v-for="note in notes" :note="note" :key="note.id" />
|
<note-row v-for="note in notes" :note="note" :key="note.id" />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<page-loading v-else />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -22,6 +24,7 @@ import useConfig from '@/composables/useConfig'
|
|||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
import CategoryRow from '@/components/CategoryRow.vue'
|
import CategoryRow from '@/components/CategoryRow.vue'
|
||||||
import NoteRow from '@/components/NoteRow.vue'
|
import NoteRow from '@/components/NoteRow.vue'
|
||||||
|
import PageLoading from '@/components/PageLoading.vue'
|
||||||
|
|
||||||
const { categories, loadCategories, loadCategoryNotes, notesChangeCount } =
|
const { categories, loadCategories, loadCategoryNotes, notesChangeCount } =
|
||||||
useNotes()
|
useNotes()
|
||||||
@@ -29,10 +32,13 @@ const { categories, loadCategories, loadCategoryNotes, notesChangeCount } =
|
|||||||
const { config } = useConfig()
|
const { config } = useConfig()
|
||||||
|
|
||||||
const notes = ref()
|
const notes = ref()
|
||||||
|
const loaded = ref(false)
|
||||||
|
|
||||||
async function refreshNotes() {
|
const refreshNotes = async () => {
|
||||||
|
loaded.value = false
|
||||||
await loadCategories()
|
await loadCategories()
|
||||||
notes.value = await loadCategoryNotes()
|
notes.value = await loadCategoryNotes()
|
||||||
|
loaded.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -46,7 +52,7 @@ watch(notesChangeCount, async () => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.directory {
|
main.directory {
|
||||||
padding-top: 26px;
|
padding-top: var(--nav-height);
|
||||||
padding-bottom: 30px;
|
padding-bottom: 30px;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="instructions layout-block">
|
<main class="instructions layout-block">
|
||||||
<router-link class="back-link" to="/"><- Go Back</router-link>
|
|
||||||
|
|
||||||
<div class="content" v-html="renderedContent" />
|
<div class="content" v-html="renderedContent" />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
@@ -16,14 +14,7 @@ const renderedContent = md.render(content)
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.instructions {
|
main.instructions {
|
||||||
padding-top: 1.2em;
|
padding-top: var(--nav-height);
|
||||||
|
|
||||||
.back-link {
|
|
||||||
opacity: 0.25;
|
|
||||||
display: block;
|
|
||||||
margin-top: 9px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,29 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="note layout-block">
|
<main class="note layout-block">
|
||||||
<note-editor :id="id" />
|
<note-download :editor="editorRef?.editor" />
|
||||||
|
|
||||||
|
<note-find
|
||||||
|
:editor="editorRef?.editor"
|
||||||
|
:visible="findVisible"
|
||||||
|
@close="findVisible = false"
|
||||||
|
/>
|
||||||
|
<note-editor ref="editorRef" :id="id" />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { watchEffect } from 'vue'
|
import { ref, watchEffect } from 'vue'
|
||||||
import { useMagicKeys } from '@vueuse/core'
|
import { useMagicKeys } from '@vueuse/core'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import NoteEditor from '@/components/note/Editor.vue'
|
import NoteEditor from '@/components/note/Editor.vue'
|
||||||
|
import NoteFind from '@/components/note/Find.vue'
|
||||||
|
import NoteDownload from '@/components/note/Download.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const id = route.params.id
|
const id = route.params.id
|
||||||
|
|
||||||
|
const editorRef = ref(null)
|
||||||
|
const findVisible = ref(false)
|
||||||
|
|
||||||
const { ctrl, f } = useMagicKeys()
|
const { ctrl, f } = useMagicKeys()
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (ctrl.value && f.value) {
|
if (ctrl.value && f.value) {
|
||||||
console.log('find')
|
findVisible.value = !findVisible.value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.note {
|
main.note {
|
||||||
padding-top: 2.2em;
|
padding-top: 8px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="preferences layout-block">
|
<main class="preferences layout-block">
|
||||||
<router-link to="/" class="back"><- Back</router-link>
|
|
||||||
|
|
||||||
<h1 class="mono">Storage Plugin</h1>
|
<h1 class="mono">Storage Plugin</h1>
|
||||||
|
|
||||||
<div v-for="plugin in plugins" class="plugin" :key="plugin.id">
|
<div v-for="plugin in plugins" class="plugin" :key="plugin.id">
|
||||||
@@ -57,6 +55,26 @@ const { plugins, setActivePlugin } = await usePlugins()
|
|||||||
const { config, ensureConfig } = useConfig()
|
const { config, ensureConfig } = useConfig()
|
||||||
await ensureConfig()
|
await ensureConfig()
|
||||||
|
|
||||||
|
const normalizeConfig = () => {
|
||||||
|
if (!config.value.adapters) {
|
||||||
|
config.value.adapters = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const plugin of plugins.value) {
|
||||||
|
if (!config.value.adapters[plugin.id]) {
|
||||||
|
config.value.adapters[plugin.id] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of plugin.configSchema) {
|
||||||
|
if (config.value.adapters[plugin.id][field.key] === undefined) {
|
||||||
|
config.value.adapters[plugin.id][field.key] =
|
||||||
|
field.default ?? ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normalizeConfig()
|
||||||
|
|
||||||
const selectedPluginId = ref(config.value.activeAdapter)
|
const selectedPluginId = ref(config.value.activeAdapter)
|
||||||
const validationError = ref('')
|
const validationError = ref('')
|
||||||
|
|
||||||
@@ -92,16 +110,9 @@ const save = async () => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.preferences {
|
.preferences {
|
||||||
padding-top: 1.2em;
|
padding-top: var(--nav-height);
|
||||||
padding-bottom: 60px;
|
padding-bottom: 60px;
|
||||||
|
|
||||||
.back {
|
|
||||||
opacity: 0.25;
|
|
||||||
display: block;
|
|
||||||
margin-top: 9px;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
@@ -117,11 +128,11 @@ const save = async () => {
|
|||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
border: 1px solid white;
|
border: 1px solid var(--theme-fg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:checked {
|
&:checked {
|
||||||
background-color: white;
|
background-color: var(--theme-fg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="search layout-block">
|
<main class="search layout-block">
|
||||||
<router-link class="back" to="/"><- Back</router-link>
|
|
||||||
|
|
||||||
<form @submit.prevent="onSearch">
|
<form @submit.prevent="onSearch">
|
||||||
<div class="input-wrap">
|
<search-input
|
||||||
<input
|
|
||||||
v-model="query"
|
v-model="query"
|
||||||
type="text"
|
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="results">
|
<div class="results">
|
||||||
@@ -26,6 +21,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import SearchInput from '@/components/SearchInput.vue'
|
||||||
import useNotes from '@/composables/useNotes'
|
import useNotes from '@/composables/useNotes'
|
||||||
import NoteRow from '@/components/NoteRow.vue'
|
import NoteRow from '@/components/NoteRow.vue'
|
||||||
import _debounce from 'lodash/debounce'
|
import _debounce from 'lodash/debounce'
|
||||||
@@ -54,52 +50,7 @@ const onInput = _debounce(async () => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.search {
|
main.search {
|
||||||
padding-top: 1.2em;
|
padding-top: var(--nav-height);
|
||||||
|
|
||||||
.back {
|
|
||||||
display: block;
|
|
||||||
opacity: 0.25;
|
|
||||||
margin-top: 9px;
|
|
||||||
}
|
|
||||||
.input-wrap {
|
|
||||||
margin-top: 19px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
padding: 5px 15px 6px;
|
|
||||||
background: var(--theme-bg);
|
|
||||||
--clip-start: 16px;
|
|
||||||
clip-path: polygon(
|
|
||||||
var(--clip-start) 1px,
|
|
||||||
calc(100% - var(--clip-start)) 1px,
|
|
||||||
calc(100% - 1.5px) 50%,
|
|
||||||
calc(100% - var(--clip-start)) calc(100% - 1px),
|
|
||||||
var(--clip-start) calc(100% - 1px),
|
|
||||||
1.5px 50%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--theme-fg);
|
|
||||||
--clip-start: 15px;
|
|
||||||
clip-path: polygon(
|
|
||||||
var(--clip-start) 0,
|
|
||||||
calc(100% - var(--clip-start)) 0,
|
|
||||||
100% 50%,
|
|
||||||
calc(100% - var(--clip-start)) 100%,
|
|
||||||
var(--clip-start) 100%,
|
|
||||||
0% 50%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|||||||
Reference in New Issue
Block a user