download and find WIP
This commit is contained in:
44
package-lock.json
generated
44
package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"@tiptap/extension-document": "^3.19.0",
|
||||
"@tiptap/extension-highlight": "^3.20.0",
|
||||
"@tiptap/extension-list": "^3.20.0",
|
||||
"@tiptap/markdown": "^3.20.4",
|
||||
"@tiptap/starter-kit": "^3.19.0",
|
||||
"@tiptap/vue-3": "^3.19.0",
|
||||
"@vueuse/core": "^14.2.1",
|
||||
@@ -2382,16 +2383,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "3.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.0.tgz",
|
||||
"integrity": "sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ==",
|
||||
"version": "3.20.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.4.tgz",
|
||||
"integrity": "sha512-3i/DG89TFY/b34T5P+j35UcjYuB5d3+9K8u6qID+iUqNPiza015HPIZLuPfE5elNwVdV3EXIoPo0LLeBLgXXAg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/pm": "^3.20.0"
|
||||
"@tiptap/pm": "^3.20.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-blockquote": {
|
||||
@@ -2752,10 +2753,27 @@
|
||||
"@tiptap/pm": "^3.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/markdown": {
|
||||
"version": "3.20.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/markdown/-/markdown-3.20.4.tgz",
|
||||
"integrity": "sha512-1ARtZzJ1skQCZi4LyVSmImgg6JIIMP5dEs0FvHXS3a7M3O+uMOUvY1sWeggVZExg8DXoVyHd7BjRIpm7uNRuSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"marked": "^17.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^3.20.4",
|
||||
"@tiptap/pm": "^3.20.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/pm": {
|
||||
"version": "3.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.0.tgz",
|
||||
"integrity": "sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A==",
|
||||
"version": "3.20.4",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.4.tgz",
|
||||
"integrity": "sha512-rCHYSBToilBEuI6PtjziHDdRkABH/XqwJ7dG4Amn/SD3yGiZKYCiEApQlTUS2zZeo8DsLeuqqqB4vEOeD4OEPg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-changeset": "^2.3.0",
|
||||
@@ -6310,6 +6328,18 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "17.0.4",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz",
|
||||
"integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/matcher": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@tiptap/extension-document": "^3.19.0",
|
||||
"@tiptap/extension-highlight": "^3.20.0",
|
||||
"@tiptap/extension-list": "^3.20.0",
|
||||
"@tiptap/markdown": "^3.20.4",
|
||||
"@tiptap/starter-kit": "^3.19.0",
|
||||
"@tiptap/vue-3": "^3.19.0",
|
||||
"@vueuse/core": "^14.2.1",
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<div :class="classes" :style="styles">
|
||||
<Nav ref="nav" />
|
||||
|
||||
<Suspense>
|
||||
<div class="layout-container">
|
||||
<router-view :key="$route.fullPath" />
|
||||
|
||||
<MoveMenu />
|
||||
<div class="layout">
|
||||
<div class="page">
|
||||
<Nav v-if="$route.name !== 'note'" ref="nav" />
|
||||
<Suspense>
|
||||
<router-view :key="$route.fullPath" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</Suspense>
|
||||
|
||||
<MoveMenu />
|
||||
</div>
|
||||
|
||||
<Menu />
|
||||
|
||||
@@ -74,10 +75,14 @@ const styles = computed(() => ({
|
||||
color: var(--theme-fg);
|
||||
transition: opacity 400ms;
|
||||
|
||||
.layout-container {
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
min-height: calc(100 * var(--vh));
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.fonts-ready) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<div v-if="open" class="move-menu layout-block">
|
||||
<button class="cancel-button" @click="close">Cancel</button>
|
||||
|
||||
<template v-for="(category, i) in categories">
|
||||
<category-row
|
||||
v-if="category !== fromCategory"
|
||||
@@ -53,5 +55,10 @@ watch(open, async () => {
|
||||
width: 50vw;
|
||||
height: 100%;
|
||||
border-left: 1px solid var(--grey-100);
|
||||
|
||||
.cancel-button {
|
||||
color: var(--grey-100);
|
||||
padding: 9px 0 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,7 +42,7 @@ const toggleMenu = () => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
padding-bottom: 16px;
|
||||
color: var(--grey-100);
|
||||
|
||||
.left,
|
||||
|
||||
74
src/renderer/src/components/SearchInput.vue
Normal file
74
src/renderer/src/components/SearchInput.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="search-input">
|
||||
<input
|
||||
v-model="model"
|
||||
type="text"
|
||||
:placeholder="placeholder"
|
||||
ref="input"
|
||||
@input="emit('input', model.value)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: String,
|
||||
})
|
||||
|
||||
const model = defineModel()
|
||||
|
||||
const emit = defineEmits(['input'])
|
||||
|
||||
const input = ref()
|
||||
|
||||
defineExpose({
|
||||
focus: () => input.value.focus(),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.search-input {
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 5px 15px 6px;
|
||||
background: var(--theme-bg);
|
||||
--clip-start: 16px;
|
||||
clip-path: polygon(
|
||||
var(--clip-start) 1px,
|
||||
calc(100% - var(--clip-start)) 1px,
|
||||
calc(100% - 1.5px) 50%,
|
||||
calc(100% - var(--clip-start)) calc(100% - 1px),
|
||||
var(--clip-start) calc(100% - 1px),
|
||||
1.5px 50%
|
||||
);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--grey-100);
|
||||
}
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--theme-fg);
|
||||
--clip-start: 15px;
|
||||
clip-path: polygon(
|
||||
var(--clip-start) 0,
|
||||
calc(100% - var(--clip-start)) 0,
|
||||
100% 50%,
|
||||
calc(100% - var(--clip-start)) 100%,
|
||||
var(--clip-start) 100%,
|
||||
0% 50%
|
||||
);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
61
src/renderer/src/components/note/Download.vue
Normal file
61
src/renderer/src/components/note/Download.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="note-download">
|
||||
<button @click="download">{{ noteTitle }}.md ↓</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import _kebabCase from 'lodash/kebabCase'
|
||||
|
||||
const DEFAULT_TITLE = 'Untitled'
|
||||
|
||||
const props = defineProps({
|
||||
editor: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const noteTitle = computed(() => {
|
||||
if (!props.editor) return DEFAULT_TITLE
|
||||
|
||||
let title
|
||||
const doc = props.editor.state.doc
|
||||
|
||||
const firstNode = doc.firstChild
|
||||
if (!firstNode || firstNode.type.name !== 'heading') title = DEFAULT_TITLE
|
||||
|
||||
title = firstNode.textContent.trim() || DEFAULT_TITLE
|
||||
|
||||
return _kebabCase(title)
|
||||
})
|
||||
|
||||
const download = () => {
|
||||
if (!props.editor) return
|
||||
|
||||
const content = props.editor.getMarkdown()
|
||||
const blob = new Blob([content], { type: 'text/plain' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${noteTitle.value}.md`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.note-download {
|
||||
margin-bottom: 16px;
|
||||
color: var(--grey-100);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
button {
|
||||
&:hover {
|
||||
color: var(--theme-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,7 @@ import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
||||
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
||||
import { TaskList, TaskItem } from '@tiptap/extension-list'
|
||||
import { Highlight } from '@tiptap/extension-highlight'
|
||||
import PageLoading from '@/components/PageLoading.vue'
|
||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import { Placeholder } from '@tiptap/extensions'
|
||||
@@ -20,8 +21,8 @@ import { all, createLowlight } from 'lowlight'
|
||||
import useNotes from '@/composables/useNotes'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import _debounce from 'lodash/debounce'
|
||||
import { Markdown } from '@tiptap/markdown'
|
||||
import EditorMenu from './Menu.vue'
|
||||
import PageLoading from '@/components/PageLoading.vue'
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
@@ -85,6 +86,7 @@ onMounted(async () => {
|
||||
TaskList,
|
||||
TaskItem,
|
||||
Highlight,
|
||||
Markdown,
|
||||
CodeBlockLowlight.configure({
|
||||
lowlight,
|
||||
enableTabIndentation: true,
|
||||
@@ -97,6 +99,10 @@ onMounted(async () => {
|
||||
onBeforeUnmount(() => {
|
||||
editor.value?.destroy?.()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
editor,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -142,7 +148,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
li {
|
||||
display: list-item;
|
||||
margin-left: 1.5em;
|
||||
margin-left: 1.75em;
|
||||
|
||||
&::marker {
|
||||
@include p;
|
||||
@@ -166,59 +172,6 @@ onBeforeUnmount(() => {
|
||||
display: block;
|
||||
color: inherit;
|
||||
padding: 1em;
|
||||
|
||||
/* Code styling */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #f98181;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #fbbc88;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: #b9f18d;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #faf594;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #70cff8;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid var(--grey-100);
|
||||
|
||||
@@ -1,12 +1,242 @@
|
||||
<template>
|
||||
<div class="note-find"></div>
|
||||
<div v-if="visible" class="note-find">
|
||||
<div class="find-bar">
|
||||
<!-- <input
|
||||
@keydown.enter="findNext"
|
||||
@keydown.escape="close"
|
||||
ref="inputRef"
|
||||
/> -->
|
||||
<search-input
|
||||
v-model="searchQuery"
|
||||
placeholder="Find..."
|
||||
ref="inputRef"
|
||||
/>
|
||||
<span class="match-count">
|
||||
{{ currentMatch + 1 }} / {{ matches.length }}
|
||||
</span>
|
||||
<button @click="findPrev" :disabled="matches.length === 0">
|
||||
↑
|
||||
</button>
|
||||
<button @click="findNext" :disabled="matches.length === 0">
|
||||
↓
|
||||
</button>
|
||||
<button @click="close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({})
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import SearchInput from '@/components/SearchInput.vue'
|
||||
import { Highlight } from '@tiptap/extension-highlight'
|
||||
|
||||
const props = defineProps({
|
||||
editor: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const searchQuery = ref('')
|
||||
const matches = ref([])
|
||||
const currentMatch = ref(0)
|
||||
const inputRef = ref(null)
|
||||
|
||||
const findInDocument = () => {
|
||||
if (!props.editor || !searchQuery.value) {
|
||||
clearHighlights()
|
||||
matches.value = []
|
||||
return
|
||||
}
|
||||
|
||||
clearHighlights()
|
||||
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
const doc = props.editor.state.doc
|
||||
const foundMatches = []
|
||||
|
||||
doc.descendants((node, pos) => {
|
||||
if (!node.isText || !node.text) return
|
||||
|
||||
const text = node.text.toLowerCase()
|
||||
let start = 0
|
||||
let index
|
||||
|
||||
while ((index = text.indexOf(query, start)) !== -1) {
|
||||
foundMatches.push({
|
||||
from: pos + index,
|
||||
to: pos + index + query.length,
|
||||
})
|
||||
start = index + 1
|
||||
}
|
||||
})
|
||||
|
||||
matches.value = foundMatches
|
||||
currentMatch.value = 0
|
||||
|
||||
if (foundMatches.length > 0) {
|
||||
highlightMatch(0)
|
||||
scrollToMatch(0)
|
||||
}
|
||||
}
|
||||
|
||||
const highlightMatch = (index) => {
|
||||
if (!props.editor || !matches.value[index]) return
|
||||
|
||||
const { from, to } = matches.value[index]
|
||||
|
||||
props.editor
|
||||
.chain()
|
||||
.setTextSelection({ from, to })
|
||||
.setHighlight({ color: 'var(--theme-accent)' })
|
||||
.run()
|
||||
|
||||
inputRef.value?.focus()
|
||||
}
|
||||
|
||||
const clearHighlights = () => {
|
||||
if (!props.editor) return
|
||||
|
||||
const { state } = props.editor
|
||||
const { doc } = state
|
||||
|
||||
doc.descendants((node, pos) => {
|
||||
if (node.marks && node.marks.length > 0) {
|
||||
node.marks.forEach((mark) => {
|
||||
if (mark.type.name === 'highlight') {
|
||||
props.editor.chain().focus().unsetHighlight().run()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
props.editor.commands.unsetHighlight()
|
||||
}
|
||||
|
||||
const scrollToMatch = (index) => {
|
||||
if (!props.editor || !matches.value[index]) return
|
||||
|
||||
const { from } = matches.value[index]
|
||||
props.editor.commands.setTextSelection({ from, to: from })
|
||||
|
||||
const dom = props.editor.view.dom
|
||||
const selection = window.getSelection()
|
||||
|
||||
if (selection && selection.rangeCount > 0) {
|
||||
const range = selection.getRangeAt(0)
|
||||
range.commonAncestorContainer.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const findNext = () => {
|
||||
if (matches.value.length === 0) return
|
||||
|
||||
clearHighlights()
|
||||
currentMatch.value = (currentMatch.value + 1) % matches.value.length
|
||||
highlightMatch(currentMatch.value)
|
||||
scrollToMatch(currentMatch.value)
|
||||
}
|
||||
|
||||
const findPrev = () => {
|
||||
if (matches.value.length === 0) return
|
||||
|
||||
clearHighlights()
|
||||
currentMatch.value =
|
||||
(currentMatch.value - 1 + matches.value.length) % matches.value.length
|
||||
highlightMatch(currentMatch.value)
|
||||
scrollToMatch(currentMatch.value)
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
clearHighlights()
|
||||
searchQuery.value = ''
|
||||
matches.value = []
|
||||
emit('close')
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
await nextTick()
|
||||
inputRef.value?.focus()
|
||||
if (props.editor && searchQuery.value) {
|
||||
console.log('visible change')
|
||||
findInDocument()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.editor,
|
||||
(newEditor) => {
|
||||
if (newEditor && props.visible && searchQuery.value) {
|
||||
console.log('editor change')
|
||||
findInDocument()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(searchQuery, () => {
|
||||
console.log('search change')
|
||||
findInDocument()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.note-find {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
|
||||
.find-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
background: var(--theme-bg);
|
||||
border-bottom: 1px solid var(--grey-100);
|
||||
padding: 12px 10px;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
.match-count {
|
||||
font-size: 12px;
|
||||
color: var(--grey-100);
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
button {
|
||||
background: transparent;
|
||||
border: 1px solid var(--grey-100);
|
||||
border-radius: 4px;
|
||||
color: var(--theme-fg);
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--grey-300);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
52
src/renderer/src/styles/_syntax.scss
Normal file
52
src/renderer/src/styles/_syntax.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
/* Code styling */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: var(--grey-100);
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #ff5b69;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #ffa55b;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet {
|
||||
color: var(--theme-accent);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #fcff5b;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #5b9aff;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
@use 'font-style' as *;
|
||||
@use 'layout' as *;
|
||||
@use 'scroll' as *;
|
||||
@use 'syntax' as *;
|
||||
@use 'transitions' as *;
|
||||
|
||||
:root {
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
<template>
|
||||
<main class="note layout-block">
|
||||
<note-editor :id="id" />
|
||||
<note-download :editor="editorRef?.editor" />
|
||||
|
||||
<note-find
|
||||
:editor="editorRef?.editor"
|
||||
:visible="findVisible"
|
||||
@close="findVisible = false"
|
||||
/>
|
||||
<note-editor ref="editorRef" :id="id" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { watchEffect } from 'vue'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import { useMagicKeys } from '@vueuse/core'
|
||||
import { useRoute } from 'vue-router'
|
||||
import NoteEditor from '@/components/note/Editor.vue'
|
||||
import NoteFind from '@/components/note/Find.vue'
|
||||
import NoteDownload from '@/components/note/Download.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const id = route.params.id
|
||||
|
||||
const editorRef = ref(null)
|
||||
const findVisible = ref(false)
|
||||
|
||||
const { ctrl, f } = useMagicKeys()
|
||||
watchEffect(() => {
|
||||
if (ctrl.value && f.value) {
|
||||
console.log('find')
|
||||
findVisible.value = !findVisible.value
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
main.note {
|
||||
padding-top: var(--nav-height);
|
||||
padding-top: 8px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<template>
|
||||
<main class="search layout-block">
|
||||
<form @submit.prevent="onSearch">
|
||||
<div class="input-wrap">
|
||||
<input
|
||||
v-model="query"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
ref="searchInput"
|
||||
@input="onInput"
|
||||
/>
|
||||
</div>
|
||||
<search-input
|
||||
v-model="query"
|
||||
placeholder="Search"
|
||||
ref="searchInput"
|
||||
@input="onInput"
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div class="results">
|
||||
@@ -24,6 +21,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import SearchInput from '@/components/SearchInput.vue'
|
||||
import useNotes from '@/composables/useNotes'
|
||||
import NoteRow from '@/components/NoteRow.vue'
|
||||
import _debounce from 'lodash/debounce'
|
||||
@@ -54,46 +52,6 @@ const onInput = _debounce(async () => {
|
||||
main.search {
|
||||
padding-top: var(--nav-height);
|
||||
|
||||
.input-wrap {
|
||||
margin-top: 19px;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 5px 15px 6px;
|
||||
background: var(--theme-bg);
|
||||
--clip-start: 16px;
|
||||
clip-path: polygon(
|
||||
var(--clip-start) 1px,
|
||||
calc(100% - var(--clip-start)) 1px,
|
||||
calc(100% - 1.5px) 50%,
|
||||
calc(100% - var(--clip-start)) calc(100% - 1px),
|
||||
var(--clip-start) calc(100% - 1px),
|
||||
1.5px 50%
|
||||
);
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--theme-fg);
|
||||
--clip-start: 15px;
|
||||
clip-path: polygon(
|
||||
var(--clip-start) 0,
|
||||
calc(100% - var(--clip-start)) 0,
|
||||
100% 50%,
|
||||
calc(100% - var(--clip-start)) 100%,
|
||||
var(--clip-start) 100%,
|
||||
0% 50%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.results {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user