diff --git a/frontend/src/components/NotesRichTextEditor.vue b/frontend/src/components/NotesRichTextEditor.vue
index 311e78c..4ae25a9 100644
--- a/frontend/src/components/NotesRichTextEditor.vue
+++ b/frontend/src/components/NotesRichTextEditor.vue
@@ -54,6 +54,7 @@ const allowedTags = [
'blockquote',
'br',
'em',
+ 'h1',
'h2',
'h3',
'li',
@@ -93,9 +94,7 @@ const NoteTextSize = Mark.create({
},
renderHTML: (attributes) => {
const size = attributes.size as NoteTextSizeStep | undefined
- return size &&
- noteTextSizeSteps.includes(size) &&
- size !== 'normal'
+ return size && noteTextSizeSteps.includes(size) && size !== 'normal'
? { 'data-note-size': size }
: {}
},
@@ -119,7 +118,16 @@ function sanitizeEditorHtml(body: string): string {
)
}
-let acceptedHtml = sanitizeEditorHtml(props.modelValue)
+function withFirstLineHeading(html: string): string {
+ const match = html.match(
+ /^(\s*)<(p|h[1-3])(?:\s[^>]*)?>([\s\S]*?)<\/\2>([\s\S]*)$/i,
+ )
+ if (!match) return `
${html}`
+
+ return `${match[1]}${match[3]}
${match[4]}`
+}
+
+let acceptedHtml = withFirstLineHeading(sanitizeEditorHtml(props.modelValue))
const editor = useEditor({
content: acceptedHtml,
@@ -127,7 +135,7 @@ const editor = useEditor({
StarterKit.configure({
code: false,
codeBlock: false,
- heading: { levels: [2, 3] },
+ heading: { levels: [1, 2, 3] },
horizontalRule: false,
link: false,
strike: {},
@@ -137,6 +145,20 @@ const editor = useEditor({
Placeholder.configure({ placeholder: props.placeholder }),
],
injectCSS: false,
+ editorProps: {
+ handleKeyDown: (view, event) => {
+ if (event.key !== 'Backspace' || !view.state.selection.empty) return false
+
+ const { doc, selection } = view.state
+ const firstLine = doc.firstChild
+ return (
+ firstLine?.type.name === 'heading' &&
+ firstLine.attrs.level === 1 &&
+ firstLine.content.size === 0 &&
+ selection.$from.parent === firstLine
+ )
+ },
+ },
onUpdate: ({ editor: currentEditor }) => {
const safeHtml = String(
DOMPurify.sanitize(currentEditor.getHTML(), {
@@ -157,8 +179,9 @@ const editor = useEditor({
function adjustTextSize(direction: -1 | 1): void {
if (!editor.value || editor.value.state.selection.empty) return
- const currentSize = editor.value.getAttributes('noteTextSize')
- .size as NoteTextSizeStep | undefined
+ const currentSize = editor.value.getAttributes('noteTextSize').size as
+ | NoteTextSizeStep
+ | undefined
const currentIndex = currentSize
? noteTextSizeSteps.indexOf(currentSize)
: normalTextSizeIndex
@@ -186,7 +209,8 @@ function toggleSelectionQuote(): void {
const { from, to } = editor.value.state.selection
const { doc, tr } = editor.value.state
const selectedText = doc.textBetween(from, to)
- const isQuotedInside = selectedText.startsWith('„') && selectedText.endsWith('“')
+ const isQuotedInside =
+ selectedText.startsWith('„') && selectedText.endsWith('“')
const isQuotedOutside =
from > 1 &&
doc.textBetween(from - 1, from) === '„' &&
@@ -211,7 +235,7 @@ watch(
() => props.modelValue,
(body) => {
if (!editor.value) return
- const safeHtml = sanitizeEditorHtml(body)
+ const safeHtml = withFirstLineHeading(sanitizeEditorHtml(body))
if (editor.value.getHTML() === safeHtml) return
acceptedHtml = safeHtml
editor.value.commands.setContent(safeHtml, { emitUpdate: false })
@@ -421,6 +445,7 @@ onBeforeUnmount(() => editor.value?.destroy())
margin: 0 0 0.55em;
}
+:deep(.tiptap h1),
:deep(.tiptap h2),
:deep(.tiptap h3) {
margin: 0.75em 0 0.35em;
@@ -428,6 +453,10 @@ onBeforeUnmount(() => editor.value?.destroy())
line-height: 1.15;
}
+:deep(.tiptap h1) {
+ font-size: 32px;
+}
+
:deep(.tiptap h2) {
font-size: 28px;
}
diff --git a/frontend/src/views/apps/NotesApp.menu.contract.test.ts b/frontend/src/views/apps/NotesApp.menu.contract.test.ts
index e347608..d94f1f1 100644
--- a/frontend/src/views/apps/NotesApp.menu.contract.test.ts
+++ b/frontend/src/views/apps/NotesApp.menu.contract.test.ts
@@ -7,6 +7,26 @@ const menuSource = source.slice(
source.indexOf('') + ''.length,
)
+const listSource = source.slice(
+ source.indexOf(' {
+ it('places the Sky searchbar and create action together at the bottom', () => {
+ const composerSource = listSource.slice(
+ listSource.indexOf(''.length,
+ )
+
+ expect(composerSource).toContain('')
+ })
+})
describe('NotesApp more menu', () => {
it('uses the shared Feather-style action sheet', () => {
diff --git a/frontend/src/views/apps/NotesApp.vue b/frontend/src/views/apps/NotesApp.vue
index 4e54d72..20b10a0 100644
--- a/frontend/src/views/apps/NotesApp.vue
+++ b/frontend/src/views/apps/NotesApp.vue
@@ -5,12 +5,10 @@ import {
kLink,
kList,
kListButton,
- kListInput,
kListItem,
kNavbar,
kNavbarBackLink,
kPage,
- kSearchbar,
} from 'konsta/vue'
import {
Ellipsis,
@@ -26,7 +24,7 @@ import NotesRichTextEditor from '@/components/NotesRichTextEditor.vue'
import { useNotesStore } from '@/stores/notes'
import { useEasyShareStore } from '@/stores/easyshare'
import { usePhoneStore } from '@/stores/phone'
-import { SkyActionSheet, SkyButton } from '@/ui'
+import { SkyActionSheet, SkyButton, SkyFab, SkySearchbar } from '@/ui'
import type { Note } from '@/utils/notes'
import { noteBodyToPlainText } from '@/utils/noteRichText'
@@ -36,7 +34,6 @@ const easyShare = useEasyShareStore()
const searchQuery = ref('')
const editorId = ref(null)
const editorOpened = ref(false)
-const draftTitle = ref('')
const draftBody = ref('')
const menuOpened = ref(false)
const currentNote = computed(() =>
@@ -100,24 +97,18 @@ function noteSubtitle(note: Note): string {
return `${noteDate(note)} · ${notePreview(note)}`
}
-function updateSearch(event: Event): void {
- searchQuery.value = (event.target as HTMLInputElement).value
-}
-
-function updateTitle(event: Event): void {
- draftTitle.value = (event.target as HTMLInputElement).value
+function titleFromDraftBody(body: string): string {
+ return noteBodyToPlainText(body).split('\n')[0]?.trim() ?? ''
}
function createNote(): void {
editorId.value = null
- draftTitle.value = ''
draftBody.value = ''
editorOpened.value = true
}
function editNote(note: Note): void {
editorId.value = note.id
- draftTitle.value = note.title
draftBody.value = note.body
editorOpened.value = true
}
@@ -125,7 +116,7 @@ function editNote(note: Note): void {
function persistDraft(): Note | undefined {
const draft = {
body: draftBody.value,
- title: draftTitle.value.trim(),
+ title: titleFromDraftBody(draftBody.value) || currentNote.value?.title.trim() || '',
}
if (editorId.value) {
@@ -183,29 +174,10 @@ function shareNote(): void {
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -264,17 +258,6 @@ function shareNote(): void {
-
-
-