Note moving
This commit is contained in:
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["dbaeumer.vscode-eslint"]
|
|
||||||
}
|
|
||||||
@@ -91,12 +91,14 @@ class Config {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const DEFAULT_WINDOW_SIZE = { width: 354, height: 549 };
|
||||||
|
const DEFAULT_MOVE_WINDOW_SIZE = { width: 708, height: 549 };
|
||||||
const preloadPath = join(__dirname, "../preload/index.mjs");
|
const preloadPath = join(__dirname, "../preload/index.mjs");
|
||||||
const rendererPath = join(__dirname, "../renderer/index.html");
|
const rendererPath = join(__dirname, "../renderer/index.html");
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
width: 354,
|
width: DEFAULT_WINDOW_SIZE.width,
|
||||||
height: 549,
|
height: DEFAULT_WINDOW_SIZE.height,
|
||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
@@ -119,8 +121,8 @@ function createWindow() {
|
|||||||
}
|
}
|
||||||
function createNoteWindow(noteId) {
|
function createNoteWindow(noteId) {
|
||||||
const noteWindow = new BrowserWindow({
|
const noteWindow = new BrowserWindow({
|
||||||
width: 354,
|
width: DEFAULT_WINDOW_SIZE.width,
|
||||||
height: 549,
|
height: DEFAULT_WINDOW_SIZE.height,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: preloadPath,
|
preload: preloadPath,
|
||||||
@@ -186,6 +188,26 @@ app.whenReady().then(async () => {
|
|||||||
ipcMain.on("note-changed", (_, event, data) => {
|
ipcMain.on("note-changed", (_, event, data) => {
|
||||||
broadcastNoteChange(event, data);
|
broadcastNoteChange(event, data);
|
||||||
});
|
});
|
||||||
|
ipcMain.handle("move-opened", (_) => {
|
||||||
|
const activeWindow = BrowserWindow.getFocusedWindow();
|
||||||
|
const windowSize = activeWindow.getSize();
|
||||||
|
if (windowSize[0] < DEFAULT_MOVE_WINDOW_SIZE.width) {
|
||||||
|
activeWindow.setSize(
|
||||||
|
DEFAULT_MOVE_WINDOW_SIZE.width,
|
||||||
|
DEFAULT_MOVE_WINDOW_SIZE.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ipcMain.handle("move-closed", (_) => {
|
||||||
|
const activeWindow = BrowserWindow.getFocusedWindow();
|
||||||
|
const windowSize = activeWindow.getSize();
|
||||||
|
if (windowSize[0] === 708) {
|
||||||
|
activeWindow.setSize(
|
||||||
|
DEFAULT_WINDOW_SIZE.width,
|
||||||
|
DEFAULT_WINDOW_SIZE.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
electronApp.setAppUserModelId("com.electron");
|
electronApp.setAppUserModelId("com.electron");
|
||||||
app.on("browser-window-created", (_, window) => {
|
app.on("browser-window-created", (_, window) => {
|
||||||
optimizer.watchWindowShortcuts(window);
|
optimizer.watchWindowShortcuts(window);
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ const api = {
|
|||||||
},
|
},
|
||||||
notifyNoteChanged: (event, data) => {
|
notifyNoteChanged: (event, data) => {
|
||||||
ipcRenderer.send("note-changed", event, data);
|
ipcRenderer.send("note-changed", event, data);
|
||||||
|
},
|
||||||
|
moveOpened: () => {
|
||||||
|
ipcRenderer.invoke("move-opened");
|
||||||
|
},
|
||||||
|
moveClosed: () => {
|
||||||
|
ipcRenderer.invoke("move-closed");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const adapter = {
|
const adapter = {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -337,7 +337,9 @@ a,
|
|||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
pre,
|
pre,
|
||||||
span {
|
span,
|
||||||
|
label,
|
||||||
|
li {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -603,7 +605,8 @@ main.directory .notes {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.note-editor p em {
|
.note-editor p em {
|
||||||
font-style: italic;
|
/* font-style: italic; */
|
||||||
|
color: var(--grey-100);
|
||||||
}
|
}
|
||||||
.note-editor hr {
|
.note-editor hr {
|
||||||
border: 1px dashed currentColor;
|
border: 1px dashed currentColor;
|
||||||
@@ -789,6 +792,14 @@ main.category .new-note {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
margin-bottom: 14px;
|
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 {
|
}main.search .back {
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
@@ -823,4 +834,74 @@ main.search .results {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 14px;
|
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-CoqDP7Z2.js"></script>
|
<script type="module" crossorigin src="./assets/index-CzxWU9vx.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-CVyE7-c9.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-NYhAwsHy.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ import PluginRegistry from './core/PluginRegistry.js'
|
|||||||
import Config from './core/Config.js'
|
import Config from './core/Config.js'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
|
const DEFAULT_WINDOW_SIZE = { width: 354, height: 549 }
|
||||||
|
const DEFAULT_MOVE_WINDOW_SIZE = { width: 708, height: 549 }
|
||||||
|
|
||||||
const preloadPath = join(__dirname, '../preload/index.mjs')
|
const preloadPath = join(__dirname, '../preload/index.mjs')
|
||||||
const rendererPath = join(__dirname, '../renderer/index.html')
|
const rendererPath = join(__dirname, '../renderer/index.html')
|
||||||
|
|
||||||
// Main window
|
// Main window
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
width: 354,
|
width: DEFAULT_WINDOW_SIZE.width,
|
||||||
height: 549,
|
height: DEFAULT_WINDOW_SIZE.height,
|
||||||
show: false,
|
show: false,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
@@ -42,8 +45,8 @@ function createWindow() {
|
|||||||
// Open note in new window
|
// Open note in new window
|
||||||
function createNoteWindow(noteId) {
|
function createNoteWindow(noteId) {
|
||||||
const noteWindow = new BrowserWindow({
|
const noteWindow = new BrowserWindow({
|
||||||
width: 354,
|
width: DEFAULT_WINDOW_SIZE.width,
|
||||||
height: 549,
|
height: DEFAULT_WINDOW_SIZE.height,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: preloadPath,
|
preload: preloadPath,
|
||||||
@@ -138,6 +141,30 @@ app.whenReady().then(async () => {
|
|||||||
broadcastNoteChange(event, data)
|
broadcastNoteChange(event, data)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Handle resizing for note "move" functionality
|
||||||
|
ipcMain.handle('move-opened', (_) => {
|
||||||
|
const activeWindow = BrowserWindow.getFocusedWindow()
|
||||||
|
const windowSize = activeWindow.getSize()
|
||||||
|
|
||||||
|
if (windowSize[0] < DEFAULT_MOVE_WINDOW_SIZE.width) {
|
||||||
|
activeWindow.setSize(
|
||||||
|
DEFAULT_MOVE_WINDOW_SIZE.width,
|
||||||
|
DEFAULT_MOVE_WINDOW_SIZE.height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ipcMain.handle('move-closed', (_) => {
|
||||||
|
const activeWindow = BrowserWindow.getFocusedWindow()
|
||||||
|
const windowSize = activeWindow.getSize()
|
||||||
|
|
||||||
|
if (windowSize[0] === 708) {
|
||||||
|
activeWindow.setSize(
|
||||||
|
DEFAULT_WINDOW_SIZE.width,
|
||||||
|
DEFAULT_WINDOW_SIZE.height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
electronApp.setAppUserModelId('com.electron')
|
electronApp.setAppUserModelId('com.electron')
|
||||||
|
|
||||||
app.on('browser-window-created', (_, window) => {
|
app.on('browser-window-created', (_, window) => {
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ const api = {
|
|||||||
notifyNoteChanged: (event, data) => {
|
notifyNoteChanged: (event, data) => {
|
||||||
ipcRenderer.send('note-changed', event, data)
|
ipcRenderer.send('note-changed', event, data)
|
||||||
},
|
},
|
||||||
|
moveOpened: () => {
|
||||||
|
ipcRenderer.invoke('move-opened')
|
||||||
|
},
|
||||||
|
moveClosed: () => {
|
||||||
|
ipcRenderer.invoke('move-closed')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement adapter API - communicates with plugin adapter in main process
|
// Implement adapter API - communicates with plugin adapter in main process
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
<Nav />
|
<Nav />
|
||||||
|
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<router-view :key="$route.fullPath" />
|
<div class="layout-container">
|
||||||
|
<router-view :key="$route.fullPath" />
|
||||||
|
|
||||||
|
<MoveMenu />
|
||||||
|
</div>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<Menu />
|
<Menu />
|
||||||
@@ -18,6 +22,7 @@ import loadFonts from '@fuzzco/font-loader'
|
|||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } 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 ScrollBar from '@/components/ScrollBar.vue'
|
import ScrollBar from '@/components/ScrollBar.vue'
|
||||||
import useConfig from '@/composables/useConfig'
|
import useConfig from '@/composables/useConfig'
|
||||||
|
|
||||||
@@ -67,6 +72,12 @@ const styles = computed(() => ({
|
|||||||
color: var(--theme-fg);
|
color: var(--theme-fg);
|
||||||
transition: opacity 400ms;
|
transition: opacity 400ms;
|
||||||
|
|
||||||
|
.layout-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
min-height: calc(100 * var(--vh));
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.fonts-ready) {
|
&:not(.fonts-ready) {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: () => false,
|
default: () => false,
|
||||||
},
|
},
|
||||||
|
wrapper: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['edited'])
|
const emit = defineEmits(['edited'])
|
||||||
@@ -70,7 +71,7 @@ const onSave = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const wrapper = computed(() => {
|
const wrapper = computed(() => {
|
||||||
return props.editable ? 'div' : RouterLink
|
return props.wrapper || (props.editable ? 'div' : RouterLink)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const openNewCategory = () => {}
|
|||||||
.menu-wrap {
|
.menu-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 3px;
|
padding-top: 1.2em;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
|
|||||||
57
src/renderer/src/components/MoveMenu.vue
Normal file
57
src/renderer/src/components/MoveMenu.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="open" class="move-menu layout-block">
|
||||||
|
<template v-for="(category, i) in categories">
|
||||||
|
<category-row
|
||||||
|
v-if="category !== fromCategory"
|
||||||
|
:category="category"
|
||||||
|
:index="i"
|
||||||
|
wrapper="button"
|
||||||
|
@click="onCategoryClick(category)"
|
||||||
|
:key="category"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CategoryRow from '@/components/CategoryRow.vue'
|
||||||
|
import { computed, onBeforeUnmount, watch } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import useNotes from '@/composables/useNotes'
|
||||||
|
import useState from '@/composables/useState'
|
||||||
|
import _omit from 'lodash/omit'
|
||||||
|
|
||||||
|
const { categories, updateNote } = useNotes()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const open = computed(() => route.query.move !== undefined)
|
||||||
|
const noteId = computed(() => route.query.move)
|
||||||
|
const fromCategory = computed(() => route.params.id)
|
||||||
|
|
||||||
|
const close = async () => {
|
||||||
|
await router.push({
|
||||||
|
query: _omit(route.query, ['move']),
|
||||||
|
})
|
||||||
|
|
||||||
|
await window.api.moveClosed()
|
||||||
|
}
|
||||||
|
const onCategoryClick = async (category) => {
|
||||||
|
if (!category || !noteId.value) return
|
||||||
|
|
||||||
|
await updateNote(noteId.value, { category: category })
|
||||||
|
|
||||||
|
await close()
|
||||||
|
}
|
||||||
|
watch(open, async () => {
|
||||||
|
if (!open.value) await close()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.move-menu {
|
||||||
|
width: 50vw;
|
||||||
|
height: 100%;
|
||||||
|
border-left: 1px solid var(--grey-100);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import useMenu from '@/composables/useMenu'
|
import useMenu from '@/composables/useMenu'
|
||||||
import { onMounted } from 'vue'
|
|
||||||
|
|
||||||
const { menuOpen, closeMenu, openMenu } = useMenu()
|
const { menuOpen, closeMenu, openMenu } = useMenu()
|
||||||
|
|
||||||
@@ -19,18 +18,11 @@ const toggleMenu = () => {
|
|||||||
openMenu()
|
openMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// Initialize menu state or perform any other necessary setup
|
|
||||||
// Example: Check if the user is logged in and update menu accordingly
|
|
||||||
// if (isLoggedIn()) {
|
|
||||||
// openMenu()
|
|
||||||
// }
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.nav {
|
.nav {
|
||||||
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="note-row" @click="openNote(note.id)">
|
<div :class="['note-row', { 'move-active': moveActive }]">
|
||||||
<span class="date">{{ formatDate(note.createdAt) }}</span>
|
<span class="date">{{ formatDate(note.createdAt) }}</span>
|
||||||
<span class="title bold">{{ note.title }}</span>
|
<div class="title-actions">
|
||||||
</button>
|
<button class="title bold" @click="openNote(note.id)">
|
||||||
|
{{ note.title }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="action bold" @click="openNote(note.id)">
|
||||||
|
(open)
|
||||||
|
</button>
|
||||||
|
<button class="action bold move" @click="onMoveOpened">
|
||||||
|
(move)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import useOpenNote from '@/composables/useOpenNote'
|
import useOpenNote from '@/composables/useOpenNote'
|
||||||
|
import useState from '@/composables/useState'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { computed } from 'vue'
|
||||||
import { format } from 'fecha'
|
import { format } from 'fecha'
|
||||||
|
|
||||||
const props = defineProps({ note: Object })
|
const props = defineProps({ note: Object })
|
||||||
@@ -17,6 +31,21 @@ const formatDate = (date) => {
|
|||||||
const d = new Date(date)
|
const d = new Date(date)
|
||||||
return format(d, 'MM/DD/YYYY')
|
return format(d, 'MM/DD/YYYY')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moving
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const onMoveOpened = async () => {
|
||||||
|
await window.api.moveOpened()
|
||||||
|
await router.push({
|
||||||
|
query: {
|
||||||
|
...route.query,
|
||||||
|
move: props.note.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log(route.query)
|
||||||
|
}
|
||||||
|
const moveActive = computed(() => route.query.move === props.note.id)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -25,28 +54,33 @@ const formatDate = (date) => {
|
|||||||
display: grid;
|
display: grid;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.title {
|
.title-actions {
|
||||||
width: calc(100% - 43.2px);
|
display: grid;
|
||||||
position: relative;
|
grid-template-columns: 1fr auto auto;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
&::after {
|
.action {
|
||||||
content: '(open)';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
transform: translateX(100%);
|
|
||||||
font-weight: 700;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
|
&:not(:hover) {
|
||||||
|
color: var(--grey-100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover,
|
||||||
|
&.move-active {
|
||||||
color: var(--theme-accent);
|
color: var(--theme-accent);
|
||||||
|
|
||||||
.title::after {
|
.title-actions .action {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.move-active {
|
||||||
|
.title-actions .move {
|
||||||
|
color: var(--theme-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
32
src/renderer/src/components/svg/Spinner.vue
Normal file
32
src/renderer/src/components/svg/Spinner.vue
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
class="svg-spinner"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
style="margin: auto; display: block"
|
||||||
|
width="18px"
|
||||||
|
height="18px"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
preserveAspectRatio="xMidYMid"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
cx="50"
|
||||||
|
cy="50"
|
||||||
|
r="40"
|
||||||
|
stroke-width="4"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-dasharray="62 62"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
>
|
||||||
|
<animateTransform
|
||||||
|
attributeName="transform"
|
||||||
|
type="rotate"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
dur="1s"
|
||||||
|
keyTimes="0;1"
|
||||||
|
values="0 50 50;360 50 50"
|
||||||
|
></animateTransform>
|
||||||
|
</circle>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<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>
|
|
||||||
8
src/renderer/src/composables/useState.js
Normal file
8
src/renderer/src/composables/useState.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { createGlobalState } from '@vueuse/core'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default createGlobalState(() => {
|
||||||
|
const moveMenuOpen = ref(false)
|
||||||
|
|
||||||
|
return { moveMenuOpen }
|
||||||
|
})
|
||||||
@@ -61,6 +61,8 @@ const categoryIndex = computed(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.category {
|
main.category {
|
||||||
|
padding-top: 1.24em;
|
||||||
|
|
||||||
.back {
|
.back {
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
|
|||||||
@@ -14,3 +14,9 @@ const onCategoryEdited = (name) => {
|
|||||||
router.push({ name: 'category', params: { id: name } })
|
router.push({ name: 'category', params: { id: name } })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.create-category {
|
||||||
|
padding-top: 1.2em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
:key="category"
|
:key="category"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h2 class="label">Summarium</h2>
|
<h2 v-if="notes?.length" class="label">Summarium</h2>
|
||||||
|
|
||||||
<div class="notes">
|
<div class="notes">
|
||||||
<note-row v-for="note in notes" :note="note" :key="note.id" />
|
<note-row v-for="note in notes" :note="note" :key="note.id" />
|
||||||
@@ -46,7 +46,7 @@ watch(notesChangeCount, async () => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.directory {
|
main.directory {
|
||||||
padding-top: 18px;
|
padding-top: 26px;
|
||||||
padding-bottom: 30px;
|
padding-bottom: 30px;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const renderedContent = md.render(content)
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.instructions {
|
main.instructions {
|
||||||
|
padding-top: 1.2em;
|
||||||
|
|
||||||
.back-link {
|
.back-link {
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ watchEffect(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.note {
|
main.note {
|
||||||
padding-top: 8px;
|
padding-top: 2.2em;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<div v-for="plugin in plugins" class="plugin" :key="plugin.id">
|
<div v-for="plugin in plugins" class="plugin" :key="plugin.id">
|
||||||
<input
|
<input
|
||||||
v-model="activePluginId"
|
v-model="selectedPluginId"
|
||||||
name="plugins"
|
name="plugins"
|
||||||
type="radio"
|
type="radio"
|
||||||
:id="plugin.id"
|
:id="plugin.id"
|
||||||
@@ -16,14 +16,14 @@
|
|||||||
<p class="name bold">{{ plugin.name }}</p>
|
<p class="name bold">{{ plugin.name }}</p>
|
||||||
<p class="description">{{ plugin.description }}</p>
|
<p class="description">{{ plugin.description }}</p>
|
||||||
|
|
||||||
<form v-if="plugin.configSchema.length" class="config">
|
<div v-if="plugin.configSchema.length" class="config">
|
||||||
<div
|
<div
|
||||||
v-for="field in plugin.configSchema"
|
v-for="field in plugin.configSchema"
|
||||||
class="config-field"
|
class="config-field"
|
||||||
:key="field.key"
|
:key="field.key"
|
||||||
>
|
>
|
||||||
<label :for="field.key">
|
<label :for="field.key">
|
||||||
{{ field.label }}
|
{{ field.label }} {{ field.required ? '*' : '' }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
v-model="config.adapters[plugin.id][field.key]"
|
v-model="config.adapters[plugin.id][field.key]"
|
||||||
@@ -33,31 +33,67 @@
|
|||||||
:required="field.required"
|
:required="field.required"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p v-if="validationError" class="error">{{ validationError }}</p>
|
||||||
|
|
||||||
|
<button @click="save" class="save-btn">
|
||||||
|
<svg-spinner v-if="saving" />
|
||||||
|
<span v-else-if="saved">Saved</span>
|
||||||
|
<span v-else>Save</span>
|
||||||
|
</button>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import SvgSpinner from '@/components/svg/Spinner.vue'
|
||||||
import usePlugins from '@/composables/usePlugins'
|
import usePlugins from '@/composables/usePlugins'
|
||||||
import useConfig from '@/composables/useConfig'
|
import useConfig from '@/composables/useConfig'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
const { plugins, setActivePlugin } = await usePlugins()
|
const { plugins, setActivePlugin } = await usePlugins()
|
||||||
const { config, ensureConfig } = useConfig()
|
const { config, ensureConfig } = useConfig()
|
||||||
await ensureConfig()
|
await ensureConfig()
|
||||||
|
|
||||||
const activePluginId = ref(config.value.activeAdapter)
|
const selectedPluginId = ref(config.value.activeAdapter)
|
||||||
|
const validationError = ref('')
|
||||||
|
|
||||||
watch(activePluginId, async (id) => {
|
const selectedPlugin = computed(() => {
|
||||||
await setActivePlugin(id)
|
return plugins.value.find((p) => p.id === selectedPluginId.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const saving = ref(false)
|
||||||
|
const saved = ref(false)
|
||||||
|
const save = async () => {
|
||||||
|
saving.value = true
|
||||||
|
validationError.value = ''
|
||||||
|
|
||||||
|
const plugin = selectedPlugin.value
|
||||||
|
if (plugin && plugin.configSchema.length) {
|
||||||
|
const adapterConfig = config.value.adapters[plugin.id] || {}
|
||||||
|
for (const field of plugin.configSchema) {
|
||||||
|
if (field.required && !adapterConfig[field.key]) {
|
||||||
|
validationError.value = `Please fill in all required fields for ${plugin.name}`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await setActivePlugin(selectedPluginId.value)
|
||||||
|
saving.value = false
|
||||||
|
saved.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
saved.value = false
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.preferences {
|
.preferences {
|
||||||
padding-top: 8px;
|
padding-top: 1.2em;
|
||||||
|
padding-bottom: 60px;
|
||||||
|
|
||||||
.back {
|
.back {
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
@@ -114,5 +150,29 @@ watch(activePluginId, async (id) => {
|
|||||||
padding: 0.2em 0.5em;
|
padding: 0.2em 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
|
||||||
|
.svg-spinner {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: var(--theme-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ const onInput = _debounce(async () => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.search {
|
main.search {
|
||||||
|
padding-top: 1.2em;
|
||||||
|
|
||||||
.back {
|
.back {
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
|
|||||||
Reference in New Issue
Block a user