note window opening

This commit is contained in:
nicwands
2026-02-23 11:42:22 -05:00
parent c436488f9d
commit 660b0825c5
18 changed files with 9687 additions and 9232 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
VITE_DEV_SERVER_URL=http://localhost:5173

View File

@@ -2,30 +2,49 @@
const electron = require("electron"); const electron = require("electron");
const path = require("path"); const path = require("path");
const utils = require("@electron-toolkit/utils"); const utils = require("@electron-toolkit/utils");
const icon = path.join(__dirname, "../../resources/icon.png");
function createWindow() { function createWindow() {
const mainWindow = new electron.BrowserWindow({ const mainWindow2 = new electron.BrowserWindow({
width: 354, width: 354,
height: 549, height: 549,
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
...process.platform === "linux" ? { icon } : {},
webPreferences: { webPreferences: {
preload: path.join(__dirname, "../preload/index.js"), preload: path.join(__dirname, "../preload/index.js"),
sandbox: false sandbox: false
} }
}); });
mainWindow.on("ready-to-show", () => { mainWindow2.on("ready-to-show", () => {
mainWindow.show(); mainWindow2.show();
}); });
mainWindow.webContents.setWindowOpenHandler((details) => { mainWindow2.webContents.setWindowOpenHandler((details) => {
electron.shell.openExternal(details.url); electron.shell.openExternal(details.url);
return { action: "deny" }; return { action: "deny" };
}); });
if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) { if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]); mainWindow2.loadURL(process.env["ELECTRON_RENDERER_URL"]);
} else { } else {
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html")); mainWindow2.loadFile(path.join(__dirname, "../renderer/index.html"));
}
}
function createNoteWindow(noteId) {
const noteWindow = new electron.BrowserWindow({
width: 354,
height: 549,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false
}
});
if (utils.is.dev && process.env["ELECTRON_RENDERER_URL"]) {
noteWindow.loadURL(
`${process.env["ELECTRON_RENDERER_URL"]}/note/${noteId}`
);
} else {
mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"), {
path: `/notes/${noteId}`
});
} }
} }
electron.app.whenReady().then(() => { electron.app.whenReady().then(() => {
@@ -33,11 +52,14 @@ electron.app.whenReady().then(() => {
electron.app.on("browser-window-created", (_, window) => { electron.app.on("browser-window-created", (_, window) => {
utils.optimizer.watchWindowShortcuts(window); utils.optimizer.watchWindowShortcuts(window);
}); });
electron.ipcMain.on("ping", () => console.log("pong")); console.log(electron.app.getPath("userData"));
createWindow(); createWindow();
electron.app.on("activate", function() { electron.app.on("activate", function() {
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow(); if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
}); });
electron.ipcMain.on("open-note-window", (_, noteId) => {
createNoteWindow(noteId);
});
}); });
electron.app.on("window-all-closed", () => { electron.app.on("window-all-closed", () => {
if (process.platform !== "darwin") { if (process.platform !== "darwin") {

View File

@@ -1,7 +1,11 @@
"use strict"; "use strict";
const electron = require("electron"); const electron = require("electron");
const preload = require("@electron-toolkit/preload"); const preload = require("@electron-toolkit/preload");
const api = {}; const api = {
openNoteWindow: (noteId) => {
electron.ipcRenderer.send("open-note-window", noteId);
}
};
if (process.contextIsolated) { if (process.contextIsolated) {
try { try {
electron.contextBridge.exposeInMainWorld("electron", preload.electronAPI); electron.contextBridge.exposeInMainWorld("electron", preload.electronAPI);

18538
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,11 +35,13 @@
"@tiptap/vue-3": "^3.19.0", "@tiptap/vue-3": "^3.19.0",
"@vueuse/core": "^14.2.1", "@vueuse/core": "^14.2.1",
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
"fecha": "^4.2.3",
"gsap": "^3.14.2", "gsap": "^3.14.2",
"lenis": "^1.3.17", "lenis": "^1.3.17",
"sass": "^1.97.3", "sass": "^1.97.3",
"sass-embedded": "^1.97.3", "sass-embedded": "^1.97.3",
"tempus": "^1.0.0-dev.17" "tempus": "^1.0.0-dev.17",
"vue-router": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^6.0.2", "@vitejs/plugin-vue": "^6.0.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,7 +1,6 @@
import { app, shell, BrowserWindow, ipcMain } from 'electron' import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { join } from 'path' import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow() { function createWindow() {
// Create the browser window. // Create the browser window.
@@ -10,7 +9,6 @@ function createWindow() {
height: 549, height: 549,
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
preload: join(__dirname, '../preload/index.js'), preload: join(__dirname, '../preload/index.js'),
sandbox: false, sandbox: false,
@@ -35,6 +33,29 @@ function createWindow() {
} }
} }
function createNoteWindow(noteId) {
const noteWindow = new BrowserWindow({
width: 354,
height: 549,
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
noteWindow.loadURL(
`${process.env['ELECTRON_RENDERER_URL']}/note/${noteId}`,
)
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'), {
path: `/notes/${noteId}`,
})
}
}
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
@@ -49,8 +70,7 @@ app.whenReady().then(() => {
optimizer.watchWindowShortcuts(window) optimizer.watchWindowShortcuts(window)
}) })
// IPC test console.log(app.getPath('userData'))
ipcMain.on('ping', () => console.log('pong'))
createWindow() createWindow()
@@ -59,6 +79,11 @@ app.whenReady().then(() => {
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow() if (BrowserWindow.getAllWindows().length === 0) createWindow()
}) })
// Open note in new window
ipcMain.on('open-note-window', (_, noteId) => {
createNoteWindow(noteId)
})
}) })
// Quit when all windows are closed, except on macOS. There, it's common // Quit when all windows are closed, except on macOS. There, it's common
@@ -69,6 +94,3 @@ app.on('window-all-closed', () => {
app.quit() app.quit()
} }
}) })
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

View File

@@ -1,20 +1,24 @@
import { contextBridge } from 'electron' import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload' import { electronAPI } from '@electron-toolkit/preload'
// Custom APIs for renderer // Custom APIs for renderer
const api = {} const api = {
openNoteWindow: (noteId) => {
ipcRenderer.send('open-note-window', noteId)
},
}
// Use `contextBridge` APIs to expose Electron APIs to // Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise // renderer only if context isolation is enabled, otherwise
// just add to the DOM global. // just add to the DOM global.
if (process.contextIsolated) { if (process.contextIsolated) {
try { try {
contextBridge.exposeInMainWorld('electron', electronAPI) contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api) contextBridge.exposeInMainWorld('api', api)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
} else { } else {
window.electron = electronAPI window.electron = electronAPI
window.api = api window.api = api
} }

View File

@@ -1,7 +1,7 @@
<template> <template>
<lenis root :options="{ duration: 1 }"> <lenis root :options="{ duration: 1 }">
<div :class="classes" :style="styles"> <div :class="classes" :style="styles">
<editor /> <router-view :key="$route.fullPath" />
</div> </div>
</lenis> </lenis>
</template> </template>
@@ -11,7 +11,6 @@ import Lenis from './components/Lenis.vue'
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import loadFonts from '@fuzzco/font-loader' import loadFonts from '@fuzzco/font-loader'
import { useWindowSize } from '@vueuse/core' import { useWindowSize } from '@vueuse/core'
import Editor from './components/editor/Index.vue'
const { height } = useWindowSize() const { height } = useWindowSize()

View File

@@ -0,0 +1,27 @@
import { useRouter } from 'vue-router'
export function useOpenNote() {
const router = useRouter()
function openNote(noteId, options = {}) {
const { newWindow = true } = options
// Electron environment check
const isElectron =
typeof window !== 'undefined' &&
window.api &&
typeof window.api.openNoteWindow === 'function'
if (newWindow && isElectron) {
window.api.openNoteWindow(noteId)
return
}
// Fallback to SPA navigation
router.push(`/note/${noteId}`)
}
return {
openNote,
}
}

View File

@@ -27,12 +27,12 @@ const breakpoints = {
const viewports = { const viewports = {
mobile: { mobile: {
width: 440, width: 200,
height: 956, height: 956,
}, },
desktop: { desktop: {
width: 1728, width: 354,
height: 1117, height: 549,
}, },
} }

View File

@@ -1,5 +1,11 @@
import './styles/main.scss' import './styles/main.scss'
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import { router } from './plugins/router'
createApp(App).mount('#app') const app = createApp(App)
// Plugins
app.use(router)
app.mount('#app')

View File

@@ -0,0 +1,14 @@
import { createRouter, createWebHistory } from 'vue-router'
import Directory from '../views/Directory.vue'
import Editor from '../views/Editor.vue'
const routes = [
{ path: '/', name: 'directory', component: Directory },
{ path: '/note/:id', name: 'note', component: Editor },
]
export const router = createRouter({
history: createWebHistory(),
routes,
})

View File

@@ -4,7 +4,7 @@
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: size-vw(30px); font-size: size-vw(30px);
} }

View File

@@ -36,10 +36,10 @@ $layout: (
//internal process, do not touch //internal process, do not touch
:root { :root {
--layout-column-count: #{list.nth(map.get($layout, 'columns-count'), 1)}; --layout-column-count: #{list.nth(map.get($layout, 'columns-count'), 1)};
--layout-column-gap: #{mobile-vw( --layout-column-gap: #{size-vw(
list.nth(map.get($layout, 'columns-gap'), 1) list.nth(map.get($layout, 'columns-gap'), 1)
)}; )};
--layout-margin: #{mobile-vw(list.nth(map.get($layout, 'margin'), 1))}; --layout-margin: #{size-vw(list.nth(map.get($layout, 'margin'), 1))};
--layout-width: calc(100vw - (2 * var(--layout-margin))); --layout-width: calc(100vw - (2 * var(--layout-margin)));
--layout-column-width: calc( --layout-column-width: calc(
( (
@@ -51,14 +51,6 @@ $layout: (
) / ) /
var(--layout-column-count) var(--layout-column-count)
); );
@include desktop {
--layout-column-count: #{list.nth(map.get($layout, 'columns-count'), 2)};
--layout-column-gap: #{desktop-vw(
list.nth(map.get($layout, 'columns-gap'), 2)
)};
--layout-margin: #{desktop-vw(list.nth(map.get($layout, 'margin'), 2))};
}
} }
.layout-block { .layout-block {

View File

@@ -14,8 +14,7 @@
: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;
@@ -37,6 +36,10 @@ h4,
h5, h5,
h6 { h6 {
@include h1; @include h1;
&.mono {
@include h1-mono;
}
} }
.p, .p,
p, p,
@@ -46,7 +49,20 @@ input,
pre { pre {
@include p; @include p;
} }
.bold {
font-weight: 700;
}
#app { #app {
min-height: 100vh; min-height: 100vh;
} }
// Text selection
::selection {
color: var(--theme-bg);
background: var(--theme-accent);
}
::-moz-selection {
color: var(--theme-bg);
background: var(--theme-accent);
}

View File

@@ -0,0 +1,145 @@
<template>
<main class="directory layout-block">
<button
class="capitula"
v-for="(capitula, i) in capitulum"
@click="openNote(capitula.slug)"
>
<span class="index">{{ String(i + 1).padStart(2, '0') }}.</span>
<span class="title h1">{{ capitula.title }}</span>
</button>
<h2 class="label">Summarium</h2>
<div class="summarium">
<button
class="summaria"
v-for="summaria in summarium"
@click="openNote(summaria.slug)"
>
<span class="date">{{ formatDate(summaria.date) }}</span>
<span class="title bold">{{ summaria.title }}</span>
</button>
</div>
</main>
</template>
<script setup>
import { format } from 'fecha'
import { useOpenNote } from '@/composables/useOpenNote'
const capitulum = [
{
slug: 'category-name',
date: new Date(),
title: 'Category Name',
},
{
slug: 'alternate-tuning',
date: new Date(),
title: 'Alternate Tuning',
},
{
slug: 'hungarian-citizenship',
date: new Date(),
title: 'Hungarian Citizenship Application and More and More',
},
]
const summarium = [
{
slug: 'birthday-dinner',
date: new Date(),
title: 'Birthday Dinner Recipe to make for Gina',
},
{
slug: 'to-do-reminders',
date: new Date(),
title: 'To-Do Reminders',
},
{
slug: 'client-feedback',
date: new Date(),
title: 'Client Feedback',
},
]
const { openNote } = useOpenNote()
const formatDate = (date) => {
return format(date, 'MM/DD/YYYY')
}
</script>
<style lang="scss">
.directory {
padding-top: size-vw(18px);
.capitula {
display: grid;
grid-template-columns: size-vw(26px) 1fr;
width: 100%;
position: relative;
padding: size-vw(5px) 0 size-vw(15px);
cursor: pointer;
.index {
margin-top: size-vw(19px);
}
.title {
display: block;
width: 100%;
@include line-clamp(2);
}
&::after {
content: '----------------------------------------';
position: absolute;
bottom: 0;
left: 0;
}
&:hover {
color: var(--theme-accent);
}
}
.label {
text-transform: uppercase;
margin: size-vw(17px) 0 size-vw(24px);
@include p;
}
.summarium {
display: flex;
flex-direction: column;
gap: size-vw(14px);
.summaria {
grid-template-columns: auto 1fr;
display: grid;
gap: size-vw(20px);
cursor: pointer;
.title {
width: size-vw(159px);
position: relative;
&::after {
content: '(open)';
position: absolute;
bottom: 0;
right: 0;
transform: translateX(100%);
font-weight: 700;
opacity: 0;
}
}
&:hover {
color: var(--theme-accent);
.title::after {
opacity: 1;
}
}
}
}
}
</style>

View File

@@ -17,33 +17,6 @@
</div> </div>
</bubble-menu> </bubble-menu>
<floating-menu :editor="editor">
<div class="floating-menu">
<button
@click="
editor.chain().focus().toggleHeading({ level: 1 }).run()
"
:class="{
active: editor.isActive('heading', { level: 1 }),
}"
>
H1
</button>
<button
@click="editor.chain().focus().toggleBulletList().run()"
:class="{ active: editor.isActive('bulletList') }"
>
Bullet List
</button>
<button
@click="editor.chain().focus().toggleOrderedList().run()"
:class="{ active: editor.isActive('orderedlist') }"
>
Number List
</button>
</div>
</floating-menu>
<editor-content :editor="editor" class="editor-wrap" /> <editor-content :editor="editor" class="editor-wrap" />
</div> </div>
</template> </template>
@@ -111,7 +84,7 @@ onBeforeUnmount(() => {
<style lang="scss"> <style lang="scss">
.editor { .editor {
padding-top: size-vw(8px); padding-top: size-vw(8px);
// padding-top: 100px; padding-bottom: size-vw(20px);
h1 { h1 {
font-weight: 700 !important; font-weight: 700 !important;
@@ -134,7 +107,8 @@ onBeforeUnmount(() => {
font-style: italic; font-style: italic;
} }
hr::before { hr::before {
content: '-----------------------------------------'; content: '----------------------------------------';
@include p;
} }
ul { ul {
list-style-type: disc; list-style-type: disc;
@@ -173,7 +147,6 @@ onBeforeUnmount(() => {
} }
} }
.floating-menu,
.bubble-menu { .bubble-menu {
display: flex; display: flex;
gap: size-vw(5px); gap: size-vw(5px);