editor WIP
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
201
src/renderer/src/components/editor/Index.vue
Normal file
201
src/renderer/src/components/editor/Index.vue
Normal 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>
|
||||
10
src/renderer/src/components/svg/icon/Hr.vue
Normal file
10
src/renderer/src/components/svg/icon/Hr.vue
Normal 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>
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user