mac csc link

This commit is contained in:
nicwands
2026-02-25 21:37:51 -05:00
parent d21076a785
commit 949f14f0e1
14 changed files with 3035 additions and 1171 deletions

BIN
developerID_installer.cer Normal file

Binary file not shown.

View File

@@ -25,6 +25,7 @@ mac:
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false notarize: false
cscLink: https://drive.google.com/uc?export=download&id=1gd3i3xEhJoh8xfVH52v_0ocKz8IvdI4B
dmg: dmg:
artifactName: ${name}-${version}.${ext} artifactName: ${name}-${version}.${ext}
linux: linux:

View File

@@ -1,9 +1,9 @@
"use strict"; "use strict";
const utils = require("@electron-toolkit/utils"); const utils = require("@electron-toolkit/utils");
const electron = require("electron"); const electron = require("electron");
const path = require("path"); const filesystemPlugin = require("takerofnotes-plugin-filesystem");
const fs = require("fs/promises"); const fs = require("fs/promises");
const matter = require("gray-matter"); const path = require("path");
const flexsearch = require("flexsearch"); const flexsearch = require("flexsearch");
const crypto = require("crypto"); const crypto = require("crypto");
class PluginRegistry { class PluginRegistry {
@@ -23,96 +23,28 @@ class PluginRegistry {
return Array.from(this.plugins.values()); return Array.from(this.plugins.values());
} }
} }
class BaseNotesAdapter { const USER_DATA_STRING = "__DEFAULT_USER_DATA__";
constructor(config = {}) {
this.config = config;
}
async init() {
throw new Error("init() not implemented");
}
async getAll() {
throw new Error("getAll() not implemented");
}
async create(note) {
throw new Error("create() not implemented");
}
async update(note) {
throw new Error("update() not implemented");
}
async delete(id) {
throw new Error("delete() not implemented");
}
}
class FileSystemNotesAdapter extends BaseNotesAdapter {
constructor(config) {
super();
for (const field in config) {
this[field] = config[field];
}
}
async init() {
await fs.mkdir(this.notesDir, { recursive: true });
}
async getAll() {
const files = await fs.readdir(this.notesDir);
const notes = [];
for (const file of files) {
if (!file.endsWith(".md")) continue;
const fullPath = path.join(this.notesDir, file);
const raw = await fs.readFile(fullPath, "utf8");
const parsed = matter(raw);
notes.push({
...parsed.data,
content: parsed.content
});
}
return notes;
}
async create(note) {
await this._write(note);
}
async update(note) {
await this._write(note);
}
async delete(id) {
const filePath = path.join(this.notesDir, `${id}.md`);
await fs.unlink(filePath);
}
async _write(note) {
const filePath = path.join(this.notesDir, `${note.id}.md`);
const fileContent = matter.stringify(note.content, {
id: note.id,
title: note.title,
category: note.category ?? null,
createdAt: note.createdAt,
updatedAt: note.updatedAt
});
await fs.writeFile(filePath, fileContent, "utf8");
}
}
const filesystemPlugin = {
id: "filesystem",
name: "Local Filesystem",
description: "Stores notes as markdown files locally.",
version: "1.0.0",
configSchema: [
{
key: "notesDir",
label: "Notes Directory",
type: "directory",
default: path.join(electron.app.getPath("userData"), "notes"),
required: true
}
],
createAdapter(config) {
return new FileSystemNotesAdapter(config);
}
};
class PluginConfig { class PluginConfig {
constructor(defaultPlugin) { constructor(defaultPlugin) {
this.defaultPlugin = defaultPlugin; this.defaultPlugin = defaultPlugin;
this.configPath = path.join(electron.app.getPath("userData"), "config.json"); this.configPath = path.join(electron.app.getPath("userData"), "config.json");
} }
// Helper to replace placeholders with dynamic values, recursively
_resolveDefaults(config) {
if (Array.isArray(config)) {
return config.map((item) => this._resolveDefaults(item));
} else if (config && typeof config === "object") {
const resolved = {};
for (const [key, value] of Object.entries(config)) {
resolved[key] = this._resolveDefaults(value);
}
return resolved;
} else if (typeof config === "string" && config.includes(USER_DATA_STRING)) {
return config.replace(USER_DATA_STRING, electron.app.getPath("userData"));
} else {
return config;
}
}
async load() { async load() {
let parsed; let parsed;
try { try {
@@ -131,15 +63,21 @@ class PluginConfig {
adapterConfig: defaultConfig adapterConfig: defaultConfig
}; };
await this.write(parsed); await this.write(parsed);
} else {
parsed.adapterConfig = this._resolveDefaults(parsed.adapterConfig);
} }
return parsed; return parsed;
} }
async write(configObject) { async write(configObject) {
const dir = path.dirname(this.configPath); const dir = path.dirname(this.configPath);
await fs.mkdir(dir, { recursive: true }); await fs.mkdir(dir, { recursive: true });
const resolvedConfig = {
...configObject,
adapterConfig: this._resolveDefaults(configObject.adapterConfig)
};
await fs.writeFile( await fs.writeFile(
this.configPath, this.configPath,
JSON.stringify(configObject, null, 2), JSON.stringify(resolvedConfig, null, 2),
"utf8" "utf8"
); );
} }
@@ -297,7 +235,7 @@ electron.app.whenReady().then(async () => {
createNoteWindow(noteId); createNoteWindow(noteId);
}); });
const registry = new PluginRegistry(); const registry = new PluginRegistry();
registry.register(filesystemPlugin); registry.register(filesystemPlugin.default);
const config = await new PluginConfig(filesystemPlugin).load(); const config = await new PluginConfig(filesystemPlugin).load();
const plugin = registry.get(config.activeAdapter); const plugin = registry.get(config.activeAdapter);
const adapter = plugin.createAdapter(config.adapterConfig); const adapter = plugin.createAdapter(config.adapterConfig);

View File

@@ -264,8 +264,8 @@ html.has-scroll-smooth .hide-on-smooth-scroll {
:root { :root {
--layout-column-count: 5; --layout-column-count: 5;
--layout-column-gap: 4.5454545455vw; --layout-column-gap: 5.6497175141vw;
--layout-margin: 6.8181818182vw; --layout-margin: 8.4745762712vw;
--layout-width: calc(100vw - (2 * var(--layout-margin))); --layout-width: calc(100vw - (2 * var(--layout-margin)));
--layout-column-width: calc( --layout-column-width: calc(
( (
@@ -278,13 +278,6 @@ html.has-scroll-smooth .hide-on-smooth-scroll {
var(--layout-column-count) var(--layout-column-count)
); );
} }
@media (min-width: 800px) {
:root {
--layout-column-count: 18;
--layout-column-gap: 1.1574074074vw;
--layout-margin: 3.4722222222vw;
}
}
.layout-block, .layout-grid { .layout-block, .layout-grid {
max-width: var(--layout-width); max-width: var(--layout-width);
@@ -394,8 +387,7 @@ html.lenis {
:root { :root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
background: var(--theme-bg); background: var(--black);
color: var(--theme-fg);
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@@ -417,9 +409,21 @@ h6 {
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 400; font-weight: 400;
letter-spacing: -0.02em; letter-spacing: -0.02em;
line-height: 1; line-height: 1.3;
font-size: 8.4745762712vw; font-size: 8.4745762712vw;
} }
.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: 6.2146892655vw;
}
.p, .p,
p, p,
@@ -433,145 +437,22 @@ pre {
font-size: 3.3898305085vw; font-size: 3.3898305085vw;
} }
#app { .bold {
min-height: 100vh;
}/* Breakpoints */
@keyframes flip-r {
50% {
transform: translateX(100%);
opacity: 0;
}
51% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes flip-l {
50% {
transform: translateX(-100%);
opacity: 0;
}
51% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes flip-d {
50% {
transform: translateY(100%);
opacity: 0;
}
51% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes flip-u {
50% {
transform: translateY(-100%);
opacity: 0;
}
51% {
transform: translateY(100%);
opacity: 0;
}
}
.editor {
padding-top: 2.2598870056vw;
}
.editor h1 {
font-weight: 700 !important;
font-family: var(--font-mono);
font-weight: 400;
line-height: 1;
font-size: 3.3898305085vw;
}
.editor h1:first-child {
font-family: var(--font-mono);
font-size: 3.3898305085vw;
font-weight: 400 !important;
}
.editor h1:first-child:first-child::first-letter {
font-family: var(--font-display);
font-size: 11.8644067797vw;
}
.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: 3.3898305085vw;
font-weight: 400 !important;
}
.editor h1.is-editor-empty:first-child::before:first-child::first-letter {
font-family: var(--font-display);
font-size: 11.8644067797vw;
}
.editor p strong {
font-weight: 700; font-weight: 700;
} }
.editor p em {
font-style: italic; #app {
min-height: 100vh;
} }
.editor hr::before {
content: "-----------------------------------------"; ::selection {
}
.editor ul {
list-style-type: disc;
}
.editor ul li {
display: list-item;
margin-left: 1em;
}
.editor ol {
list-style-type: decimal;
}
.editor ol li {
display: list-item;
margin-left: 1.5em;
}
.editor ol li::marker {
font-family: var(--font-mono);
font-weight: 400;
line-height: 1;
font-size: 3.3898305085vw;
}
.editor a {
color: var(--theme-link);
cursor: pointer;
}
.editor .editor-wrap > div {
display: flex;
flex-direction: column;
gap: 5.6497175141vw;
}
.editor .editor-wrap > div:focus {
outline: none;
}
.editor .floating-menu,
.editor .bubble-menu {
display: flex;
gap: 1.4124293785vw;
border: 1px solid var(--grey-100);
color: var(--grey-100);
border-radius: 0.2em;
background: var(--theme-bg);
}
.editor .floating-menu button,
.editor .bubble-menu button {
cursor: pointer;
padding: 0.2em;
border-radius: 0.2em;
}
.editor .floating-menu button:hover,
.editor .bubble-menu button:hover {
background: var(--grey-100);
color: var(--theme-bg); color: var(--theme-bg);
background: var(--theme-accent);
} }
.editor .floating-menu button.active,
.editor .bubble-menu button.active { ::-moz-selection {
background: var(--theme-fg);
color: var(--theme-bg); color: var(--theme-bg);
background: var(--theme-accent);
}/* Breakpoints */ }/* Breakpoints */
@keyframes flip-r { @keyframes flip-r {
50% { 50% {
@@ -623,4 +504,394 @@ pre {
} }
.container:not(.fonts-ready) { .container:not(.fonts-ready) {
opacity: 0; opacity: 0;
}/* Breakpoints */
@keyframes flip-r {
50% {
transform: translateX(100%);
opacity: 0;
}
51% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes flip-l {
50% {
transform: translateX(-100%);
opacity: 0;
}
51% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes flip-d {
50% {
transform: translateY(100%);
opacity: 0;
}
51% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes flip-u {
50% {
transform: translateY(-100%);
opacity: 0;
}
51% {
transform: translateY(100%);
opacity: 0;
}
}
.category-row {
display: grid;
grid-template-columns: 7.3446327684vw 1fr;
width: 100%;
position: relative;
padding: 1.4124293785vw 0 4.2372881356vw;
cursor: pointer;
}
.category-row .index {
margin-top: 5.3672316384vw;
}
.category-row .title {
display: block;
width: 100%;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
@media (max-width: 799px) {
.category-row .title {
-webkit-line-clamp: 2;
}
}
.category-row::after {
content: "----------------------------------------";
position: absolute;
bottom: 0;
left: 0;
}
.category-row.router-link-exact-active {
cursor: default;
}
.category-row:hover:not(.router-link-exact-active) {
color: var(--theme-accent);
}/* Breakpoints */
@keyframes flip-r {
50% {
transform: translateX(100%);
opacity: 0;
}
51% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes flip-l {
50% {
transform: translateX(-100%);
opacity: 0;
}
51% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes flip-d {
50% {
transform: translateY(100%);
opacity: 0;
}
51% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes flip-u {
50% {
transform: translateY(-100%);
opacity: 0;
}
51% {
transform: translateY(100%);
opacity: 0;
}
}
.note-row {
grid-template-columns: auto 1fr;
display: grid;
gap: 5.6497175141vw;
cursor: pointer;
}
.note-row .title {
width: 44.9152542373vw;
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;
}/* Breakpoints */
@keyframes flip-r {
50% {
transform: translateX(100%);
opacity: 0;
}
51% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes flip-l {
50% {
transform: translateX(-100%);
opacity: 0;
}
51% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes flip-d {
50% {
transform: translateY(100%);
opacity: 0;
}
51% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes flip-u {
50% {
transform: translateY(-100%);
opacity: 0;
}
51% {
transform: translateY(100%);
opacity: 0;
}
}
.directory {
padding-top: 5.0847457627vw;
}
.directory .label {
text-transform: uppercase;
margin: 4.802259887vw 0 6.7796610169vw;
font-family: var(--font-mono);
font-weight: 400;
line-height: 1;
font-size: 3.3898305085vw;
}
.directory .notes {
display: flex;
flex-direction: column;
gap: 3.9548022599vw;
}/* Breakpoints */
@keyframes flip-r {
50% {
transform: translateX(100%);
opacity: 0;
}
51% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes flip-l {
50% {
transform: translateX(-100%);
opacity: 0;
}
51% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes flip-d {
50% {
transform: translateY(100%);
opacity: 0;
}
51% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes flip-u {
50% {
transform: translateY(-100%);
opacity: 0;
}
51% {
transform: translateY(100%);
opacity: 0;
}
}
.editor {
padding-top: 2.2598870056vw;
padding-bottom: 5.6497175141vw;
}
.editor h1 {
font-weight: 700 !important;
font-family: var(--font-mono);
font-weight: 400;
line-height: 1;
font-size: 3.3898305085vw;
}
.editor h1:first-child {
font-family: var(--font-mono);
font-size: 3.3898305085vw;
font-weight: 400 !important;
}
.editor h1:first-child:first-child::first-letter {
font-family: var(--font-display);
font-size: 11.8644067797vw;
}
.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: 3.3898305085vw;
font-weight: 400 !important;
}
.editor h1.is-editor-empty:first-child::before:first-child::first-letter {
font-family: var(--font-display);
font-size: 11.8644067797vw;
}
.editor p strong {
font-weight: 700;
}
.editor p em {
font-style: italic;
}
.editor hr::before {
content: "----------------------------------------";
font-family: var(--font-mono);
font-weight: 400;
line-height: 1;
font-size: 3.3898305085vw;
}
.editor ul {
list-style-type: disc;
}
.editor ul li {
display: list-item;
margin-left: 1em;
}
.editor ol {
list-style-type: decimal;
}
.editor ol li {
display: list-item;
margin-left: 1.5em;
}
.editor ol li::marker {
font-family: var(--font-mono);
font-weight: 400;
line-height: 1;
font-size: 3.3898305085vw;
}
.editor a {
color: var(--theme-link);
cursor: pointer;
}
.editor .editor-wrap > div {
display: flex;
flex-direction: column;
gap: 5.6497175141vw;
}
.editor .editor-wrap > div:focus {
outline: none;
}
.editor .bubble-menu {
display: flex;
gap: 1.4124293785vw;
border: 1px solid var(--grey-100);
color: var(--grey-100);
border-radius: 0.2em;
background: var(--theme-bg);
}
.editor .bubble-menu button {
cursor: pointer;
padding: 0.2em;
border-radius: 0.2em;
}
.editor .bubble-menu button:hover {
background: var(--grey-100);
color: var(--theme-bg);
}
.editor .bubble-menu button.active {
background: var(--theme-fg);
color: var(--theme-bg);
}/* Breakpoints */
@keyframes flip-r {
50% {
transform: translateX(100%);
opacity: 0;
}
51% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes flip-l {
50% {
transform: translateX(-100%);
opacity: 0;
}
51% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes flip-d {
50% {
transform: translateY(100%);
opacity: 0;
}
51% {
transform: translateY(-100%);
opacity: 0;
}
}
@keyframes flip-u {
50% {
transform: translateY(-100%);
opacity: 0;
}
51% {
transform: translateY(100%);
opacity: 0;
}
}
.category .back {
display: block;
opacity: 0.25;
margin-top: 2.5423728814vw;
}
.category .category-row {
margin-top: 1.1299435028vw;
}
.category .notes {
display: flex;
flex-direction: column;
gap: 3.9548022599vw;
margin-top: 2.5423728814vw;
} }

View File

@@ -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-CnVkU_B_.js"></script> <script type="module" crossorigin src="./assets/index-Dv1qfmwr.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-rzd5KlBx.css"> <link rel="stylesheet" crossorigin href="./assets/index-CZWw79gc.css">
</head> </head>
<body> <body>

30
package-lock.json generated
View File

@@ -22,12 +22,12 @@
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
"fecha": "^4.2.3", "fecha": "^4.2.3",
"flexsearch": "^0.8.212", "flexsearch": "^0.8.212",
"gray-matter": "^4.0.3",
"gsap": "^3.14.2", "gsap": "^3.14.2",
"lenis": "^1.3.17", "lenis": "^1.3.17",
"lodash": "^4.17.23", "lodash": "^4.17.23",
"sass": "^1.97.3", "sass": "^1.97.3",
"sass-embedded": "^1.97.3", "sass-embedded": "^1.97.3",
"takerofnotes-plugin-filesystem": "^1.6.0",
"tempus": "^1.0.0-dev.17", "tempus": "^1.0.0-dev.17",
"uuid": "^13.0.0", "uuid": "^13.0.0",
"vue-router": "^5.0.3" "vue-router": "^5.0.3"
@@ -8314,6 +8314,25 @@
"node": ">=16.0.0" "node": ">=16.0.0"
} }
}, },
"node_modules/takerofnotes-plugin-filesystem": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/takerofnotes-plugin-filesystem/-/takerofnotes-plugin-filesystem-1.6.0.tgz",
"integrity": "sha512-wUcH8lV6ges9zkEU7b7h1SkDcvgoJ+TLfogqRirLg38fdJ/LGat6BBfItO3QlalkYobsb4wglEfd7wg4f4erJA==",
"license": "MIT",
"dependencies": {
"gray-matter": "^4.0.3",
"takerofnotes-plugin-sdk": "^0.4.0"
}
},
"node_modules/takerofnotes-plugin-sdk": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/takerofnotes-plugin-sdk/-/takerofnotes-plugin-sdk-0.4.0.tgz",
"integrity": "sha512-C1bXun19Zh603Aq/7sEI4c3dZ8QEUiz51ikDPJm5g+JWoQkH77OH6AY19SN+eOKnqEmY1yw3JYEVd//mJ06UAA==",
"license": "MIT",
"dependencies": {
"zod": "^4.3.6"
}
},
"node_modules/tar": { "node_modules/tar": {
"version": "7.5.9", "version": "7.5.9",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
@@ -9535,6 +9554,15 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }

View File

@@ -37,12 +37,12 @@
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
"fecha": "^4.2.3", "fecha": "^4.2.3",
"flexsearch": "^0.8.212", "flexsearch": "^0.8.212",
"gray-matter": "^4.0.3",
"gsap": "^3.14.2", "gsap": "^3.14.2",
"lenis": "^1.3.17", "lenis": "^1.3.17",
"lodash": "^4.17.23", "lodash": "^4.17.23",
"sass": "^1.97.3", "sass": "^1.97.3",
"sass-embedded": "^1.97.3", "sass-embedded": "^1.97.3",
"takerofnotes-plugin-filesystem": "^1.6.0",
"tempus": "^1.0.0-dev.17", "tempus": "^1.0.0-dev.17",
"uuid": "^13.0.0", "uuid": "^13.0.0",
"vue-router": "^5.0.3" "vue-router": "^5.0.3"

View File

@@ -1,25 +0,0 @@
export default class BaseNotesAdapter {
constructor(config = {}) {
this.config = config
}
async init() {
throw new Error('init() not implemented')
}
async getAll() {
throw new Error('getAll() not implemented')
}
async create(note) {
throw new Error('create() not implemented')
}
async update(note) {
throw new Error('update() not implemented')
}
async delete(id) {
throw new Error('delete() not implemented')
}
}

View File

@@ -2,13 +2,34 @@ import fs from 'fs/promises'
import path from 'path' import path from 'path'
import { app } from 'electron' import { app } from 'electron'
const USER_DATA_STRING = '__DEFAULT_USER_DATA__'
export default class PluginConfig { export default class PluginConfig {
constructor(defaultPlugin) { constructor(defaultPlugin) {
this.defaultPlugin = defaultPlugin this.defaultPlugin = defaultPlugin
this.configPath = path.join(app.getPath('userData'), 'config.json') this.configPath = path.join(app.getPath('userData'), 'config.json')
} }
// Helper to replace placeholders with dynamic values, recursively
_resolveDefaults(config) {
if (Array.isArray(config)) {
return config.map((item) => this._resolveDefaults(item))
} else if (config && typeof config === 'object') {
const resolved = {}
for (const [key, value] of Object.entries(config)) {
resolved[key] = this._resolveDefaults(value)
}
return resolved
} else if (
typeof config === 'string' &&
config.includes(USER_DATA_STRING)
) {
return config.replace(USER_DATA_STRING, app.getPath('userData'))
} else {
return config
}
}
async load() { async load() {
let parsed let parsed
@@ -32,6 +53,9 @@ export default class PluginConfig {
} }
await this.write(parsed) await this.write(parsed)
} else {
// Ensure any "__DEFAULT_USER_DATA__" values are resolved on load
parsed.adapterConfig = this._resolveDefaults(parsed.adapterConfig)
} }
return parsed return parsed
@@ -43,9 +67,15 @@ export default class PluginConfig {
// Ensure directory exists // Ensure directory exists
await fs.mkdir(dir, { recursive: true }) await fs.mkdir(dir, { recursive: true })
// Resolve defaults before writing
const resolvedConfig = {
...configObject,
adapterConfig: this._resolveDefaults(configObject.adapterConfig),
}
await fs.writeFile( await fs.writeFile(
this.configPath, this.configPath,
JSON.stringify(configObject, null, 2), JSON.stringify(resolvedConfig, null, 2),
'utf8', 'utf8',
) )
} }

View File

@@ -1,7 +1,7 @@
import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { app, shell, BrowserWindow, ipcMain } from 'electron' import { app, shell, BrowserWindow, ipcMain } from 'electron'
import filesystemPlugin from 'takerofnotes-plugin-filesystem'
import PluginRegistry from './core/PluginRegistry.js' import PluginRegistry from './core/PluginRegistry.js'
import filesystemPlugin from './plugins/filesystem'
import PluginConfig from './core/PluginConfig.js' import PluginConfig from './core/PluginConfig.js'
import NotesAPI from './core/NotesAPI.js' import NotesAPI from './core/NotesAPI.js'
import { join } from 'path' import { join } from 'path'
@@ -84,7 +84,8 @@ app.whenReady().then(async () => {
const registry = new PluginRegistry() const registry = new PluginRegistry()
// Register built-in plugins // Register built-in plugins
registry.register(filesystemPlugin) // TODO figure out why export is under default
registry.register(filesystemPlugin.default)
// Pull plugin config // Pull plugin config
const config = await new PluginConfig(filesystemPlugin).load() const config = await new PluginConfig(filesystemPlugin).load()

View File

@@ -1,159 +0,0 @@
import fs from 'fs/promises'
import path from 'path'
import { app } from 'electron'
import matter from 'gray-matter'
import { Index } from 'flexsearch'
import crypto from 'crypto'
export default class NotesAPI {
constructor() {
this.notesDir = path.join(app.getPath('userData'), 'notes')
this.notesCache = new Map()
this.index = new Index({
tokenize: 'tolerant',
resolution: 9,
})
}
async init() {
await fs.mkdir(this.notesDir, { recursive: true })
await this._loadAllNotes()
}
async _loadAllNotes() {
const files = await fs.readdir(this.notesDir)
for (const file of files) {
if (!file.endsWith('.md')) continue
const fullPath = path.join(this.notesDir, file)
const raw = await fs.readFile(fullPath, 'utf8')
const parsed = matter(raw)
const note = {
...parsed.data,
content: parsed.content,
}
this.notesCache.set(note.id, note)
this.index.add(note.id, note.title + note.content)
}
}
async _writeNoteFile(note) {
const filePath = path.join(this.notesDir, `${note.id}.md`)
const fileContent = matter.stringify(note.content, {
id: note.id,
title: note.title,
category: note.category ?? null,
createdAt: note.createdAt,
updatedAt: note.updatedAt,
})
await fs.writeFile(filePath, fileContent, 'utf8')
}
/* -----------------------
Public API
------------------------*/
getCategories() {
const categories = new Set()
for (const note of this.notesCache.values()) {
if (note.category) {
categories.add(note.category)
}
}
return Array.from(categories).sort()
}
getCategoryNotes(categoryName) {
return Array.from(this.notesCache.values())
.filter((n) => n.category === categoryName)
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
}
getNote(id) {
return this.notesCache.get(id) ?? null
}
async createNote(metadata = {}, content = '') {
const id = crypto.randomUUID()
const now = new Date().toISOString()
const note = {
id,
title: metadata.title || 'Untitled',
category: metadata.category || null,
createdAt: now,
updatedAt: now,
content,
}
console.log(note)
this.notesCache.set(id, note)
this.index.add(id, note.title + '\n' + content)
await this._writeNoteFile(note)
return note
}
async deleteNote(id) {
const filePath = path.join(this.notesDir, `${id}.md`)
await fs.unlink(filePath)
this.notesCache.delete(id)
this.index.remove(id)
}
async updateNote(id, content) {
const note = this.notesCache.get(id)
if (!note) throw new Error('Note not found')
note.content = content
note.updatedAt = new Date().toISOString()
this.index.update(id, note.title + '\n' + content)
await this._writeNoteFile(note)
return note
}
async updateNoteMetadata(id, updates = {}) {
const note = this.notesCache.get(id)
if (!note) throw new Error('Note not found')
const allowedFields = ['title', 'category']
for (const key of Object.keys(updates)) {
if (!allowedFields.includes(key)) {
throw new Error(`Invalid metadata field: ${key}`)
}
}
if (updates.title !== undefined) {
note.title = updates.title
}
if (updates.category !== undefined) {
note.category = updates.category
}
note.updatedAt = new Date().toISOString()
this.index.update(id, note.title + '\n' + note.content)
await this._writeNoteFile(note)
return note
}
search(query) {
const ids = this.index.search(query)
return ids.map((id) => this.notesCache.get(id))
}
}

View File

@@ -1,65 +0,0 @@
import BaseNotesAdapter from '../../core/BaseNotesAdapter.js'
import matter from 'gray-matter'
import fs from 'fs/promises'
import path from 'path'
export default class FileSystemNotesAdapter extends BaseNotesAdapter {
constructor(config) {
super()
for (const field in config) {
this[field] = config[field]
}
}
async init() {
await fs.mkdir(this.notesDir, { recursive: true })
}
async getAll() {
const files = await fs.readdir(this.notesDir)
const notes = []
for (const file of files) {
if (!file.endsWith('.md')) continue
const fullPath = path.join(this.notesDir, file)
const raw = await fs.readFile(fullPath, 'utf8')
const parsed = matter(raw)
notes.push({
...parsed.data,
content: parsed.content,
})
}
return notes
}
async create(note) {
await this._write(note)
}
async update(note) {
await this._write(note)
}
async delete(id) {
const filePath = path.join(this.notesDir, `${id}.md`)
await fs.unlink(filePath)
}
async _write(note) {
const filePath = path.join(this.notesDir, `${note.id}.md`)
const fileContent = matter.stringify(note.content, {
id: note.id,
title: note.title,
category: note.category ?? null,
createdAt: note.createdAt,
updatedAt: note.updatedAt,
})
await fs.writeFile(filePath, fileContent, 'utf8')
}
}

View File

@@ -1,24 +0,0 @@
import path from 'path'
import { app } from 'electron'
import FileSystemAdapter from './FileSystemAdapter.js'
export default {
id: 'filesystem',
name: 'Local Filesystem',
description: 'Stores notes as markdown files locally.',
version: '1.0.0',
configSchema: [
{
key: 'notesDir',
label: 'Notes Directory',
type: 'directory',
default: path.join(app.getPath('userData'), 'notes'),
required: true,
},
],
createAdapter(config) {
return new FileSystemAdapter(config)
},
}