editor WIP

This commit is contained in:
nicwands
2026-02-16 14:40:57 -05:00
parent a4fb3f2a87
commit b89d41e47a
23 changed files with 4495 additions and 511 deletions

View File

@@ -4,70 +4,70 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 354,
height: 549,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
},
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// IPC test
ipcMain.on('ping', () => console.log('pong'))
// IPC test
ipcMain.on('ping', () => console.log('pong'))
createWindow()
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app's specific main process

View File

@@ -1,10 +1,7 @@
<template>
<lenis root :options="{ duration: 1 }">
<div :class="classes" :style="styles">
<main class="layout-block">
<h1>Taker of Notes</h1>
<p>Lorem ipsum</p>
</main>
<editor />
</div>
</lenis>
</template>
@@ -14,6 +11,7 @@ import Lenis from './components/Lenis.vue'
import { ref, computed, onMounted } from 'vue'
import loadFonts from '@fuzzco/font-loader'
import { useWindowSize } from '@vueuse/core'
import Editor from './components/editor/Index.vue'
const { height } = useWindowSize()

View File

@@ -0,0 +1,201 @@
<template>
<div v-if="editor" class="editor layout-block">
<bubble-menu :editor="editor">
<div class="bubble-menu">
<button
@click="editor.chain().focus().toggleBold().run()"
:class="{ active: editor.isActive('bold') }"
>
Bold
</button>
<button
@click="editor.chain().focus().toggleItalic().run()"
:class="{ active: editor.isActive('italic') }"
>
Italic
</button>
</div>
</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" />
</div>
</template>
<script setup>
import { onBeforeUnmount } from 'vue'
import { Editor, EditorContent } from '@tiptap/vue-3'
import { Markdown } from '@tiptap/markdown'
import Image from '@tiptap/extension-image'
import Document from '@tiptap/extension-document'
import { Placeholder } from '@tiptap/extensions'
import StarterKit from '@tiptap/starter-kit'
import { BubbleMenu, FloatingMenu } from '@tiptap/vue-3/menus'
// import SvgIconHr from '../svg/icon/Hr.vue'
// Initial markdown string
const initialMarkdown = `# My Document
This is a paragraph.
# Section
- Item one
- Item two
---
# Header Three
[Link](https://google.com)
`
// const initialMarkdown = ``
const CustomDocument = Document.extend({
content: 'heading block*',
})
const editor = new Editor({
extensions: [
CustomDocument,
StarterKit.configure({
document: false,
heading: { levels: [1] },
trailingNode: {
node: 'paragraph',
},
}),
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === 'heading') {
return 'Title'
}
},
}),
Markdown,
Image,
],
content: initialMarkdown,
contentType: 'markdown',
})
onBeforeUnmount(() => {
editor.destroy()
})
</script>
<style lang="scss">
.editor {
padding-top: size-vw(8px);
// padding-top: 100px;
h1 {
font-weight: 700 !important;
@include p;
&:first-child {
@include drop-cap;
}
}
h1.is-editor-empty:first-child::before {
color: var(--grey-100);
content: attr(data-placeholder);
pointer-events: none;
@include drop-cap;
}
p strong {
font-weight: 700;
}
p em {
font-style: italic;
}
hr::before {
content: '-----------------------------------------';
}
ul {
list-style-type: disc;
li {
display: list-item;
margin-left: 1em;
}
}
ol {
list-style-type: decimal;
li {
display: list-item;
margin-left: 1.5em;
&::marker {
@include p;
}
}
}
a {
color: var(--theme-link);
cursor: pointer;
}
.editor-wrap {
> div {
display: flex;
flex-direction: column;
gap: size-vw(20px);
&:focus {
outline: none;
}
}
}
.floating-menu,
.bubble-menu {
display: flex;
gap: size-vw(5px);
border: 1px solid var(--grey-100);
color: var(--grey-100);
border-radius: 0.2em;
background: var(--theme-bg);
button {
cursor: pointer;
padding: 0.2em;
border-radius: 0.2em;
&:hover {
background: var(--grey-100);
color: var(--theme-bg);
}
&.active {
background: var(--theme-fg);
color: var(--theme-bg);
}
}
}
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<svg
class="svg-icon-hr"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M2 11H4V13H2V11ZM6 11H18V13H6V11ZM20 11H22V13H20V11Z"></path>
</svg>
</template>

View File

@@ -1,20 +1,23 @@
const colors = {
black: '#000000',
white: '#d9d9d9',
pink: '#DAC3C3',
green: '#D8E3D3',
cream: '#F2F1F1',
grey: '#2B2B2B',
black: '#181818',
white: '#D5D5D5',
'grey-100': '#747474',
green: '#87FF5B',
blue: '#5B92FF',
}
const themes = {
light: {
bg: colors.white,
fg: colors.black,
accent: colors.green,
link: colors.blue,
},
dark: {
bg: colors.black,
fg: colors.white,
accent: colors.green,
link: colors.blue,
},
}

View File

@@ -1,31 +1,23 @@
@use 'functions' as *;
@mixin size-font($ds, $ms) {
font-size: mobile-vw($ms);
&.vh {
font-size: mobile-vh($ms);
}
@include desktop {
font-size: desktop-vw($ds);
&.vh {
font-size: desktop-vh($ds);
}
}
}
@mixin h1 {
font-family: var(--font-display);
font-weight: 400;
letter-spacing: 0.1em;
@include size-font(42px, 27px);
letter-spacing: -0.02em;
line-height: 1;
font-size: size-vw(30px);
}
@mixin h1-mono {
font-family: var(--font-mono);
font-weight: 400;
line-height: 1;
font-size: size-vw(22px);
}
@mixin p {
font-family: var(--font-mono);
font-weight: 400;
letter-spacing: 0.03em;
@include size-font(25px, 18px);
line-height: 1;
font-size: size-vw(12px);
}

View File

@@ -47,6 +47,12 @@ $mobile-height: get('viewports.mobile.height');
@return calc($perc * 100vh);
}
@function size-vw($pixels, $base-vw: 354) {
$px: math.div($pixels, $base-vw);
$perc: math.div($px, 1px);
@return calc($perc * 100vw);
}
@function columns($columns) {
@return calc(
(#{$columns} * var(--layout-column-width)) +
@@ -186,7 +192,13 @@ $mobile-height: get('viewports.mobile.height');
}
}
@mixin bonk-animation($delay: 0s, $duration: 1s, $angle: 25deg) {
--bonk-angle: #{$angle};
animation: bonk $duration $delay infinite step-end;
@mixin drop-cap() {
font-family: var(--font-mono);
font-size: size-vw(12px);
font-weight: 400 !important;
&:first-child::first-letter {
font-family: var(--font-display);
font-size: size-vw(42px);
}
}

View File

@@ -14,8 +14,8 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
background: var(--white);
color: var(--black);
background: var(--theme-bg);
color: var(--theme-fg);
font-synthesis: none;
text-rendering: optimizeLegibility;
@@ -47,65 +47,6 @@ pre {
@include p;
}
.entry {
img {
max-width: 100%;
height: auto;
}
p {
min-height: 1px;
}
a {
text-decoration: underline;
}
& > * {
margin-bottom: 1em;
margin-top: 1em;
}
& > *:first-child {
margin-top: 0;
}
& > *:last-child {
margin-bottom: 0;
}
ul,
ol {
li {
margin-bottom: 1em;
width: desktop-vw(577px);
}
}
ul {
list-style: disc;
padding-left: 1em;
}
}
#app {
min-height: 100vh;
}
@keyframes blink {
5% {
opacity: 0;
}
40% {
opacity: 0;
}
60% {
opacity: 1;
}
95% {
opacity: 1;
}
}
@keyframes bonk {
0% {
transform: rotate(calc(var(--bonk-angle) * -1));
}
50% {
transform: rotate(var(--bonk-angle));
}
}