From e4fc873bf0429c9852455e60751455ecee490427 Mon Sep 17 00:00:00 2001
From: Type <79042381+TypeFor@users.noreply.github.com>
Date: Sun, 16 Aug 2026 13:06:10 +0200
Subject: [PATCH] FIX - align notes editor with Sky controls
Migrates the Notes list and formatting tools to shared Sky controls, restores a neutral glass FAB variant, and gives the note list a dedicated scroll owner so every entry remains reachable.
---
.../NotesRichTextEditor.contract.test.ts | 9 +-
.../src/components/NotesRichTextEditor.vue | 294 +++++++++---------
frontend/src/ui/controls.css | 15 +
frontend/src/ui/controls/SkyFab.test.ts | 18 ++
frontend/src/ui/controls/SkyFab.vue | 3 +
.../views/apps/NotesApp.menu.contract.test.ts | 10 +-
frontend/src/views/apps/NotesApp.vue | 131 ++++----
7 files changed, 262 insertions(+), 218 deletions(-)
diff --git a/frontend/src/components/NotesRichTextEditor.contract.test.ts b/frontend/src/components/NotesRichTextEditor.contract.test.ts
index 48ef9ab..d74dea9 100644
--- a/frontend/src/components/NotesRichTextEditor.contract.test.ts
+++ b/frontend/src/components/NotesRichTextEditor.contract.test.ts
@@ -10,10 +10,17 @@ const source = readFileSync(
describe('NotesRichTextEditor formatting tabbar', () => {
it('lists every formatting action through shared tabbar primitives', () => {
expect(source.match(/',
+ )
+ expect(source).toContain('')
+ expect(source).toContain('@click="formatMode = true"')
+ expect(source).toContain('@click="formatMode = false"')
+ expect(source).toContain(':disabled="editor.state.selection.empty"')
expect(source).not.toContain('notes-rich-editor__toolbar-row')
expect(source).not.toContain('scrollToolbar')
})
diff --git a/frontend/src/components/NotesRichTextEditor.vue b/frontend/src/components/NotesRichTextEditor.vue
index 4ae25a9..5894af1 100644
--- a/frontend/src/components/NotesRichTextEditor.vue
+++ b/frontend/src/components/NotesRichTextEditor.vue
@@ -15,8 +15,9 @@ import {
Strikethrough,
Underline,
Undo2,
+ X,
} from 'lucide-vue-next'
-import { onBeforeUnmount, watch } from 'vue'
+import { onBeforeUnmount, ref, watch } from 'vue'
import { SkyIcon, SkyTabBar, SkyTabButton } from '@/ui'
import {
@@ -35,6 +36,7 @@ export type NotesEditorLabels = {
redo: string
strike: string
toolbar: string
+ closeFormatting: string
underline: string
undo: string
}
@@ -50,6 +52,8 @@ const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
+const formatMode = ref(false)
+
const allowedTags = [
'blockquote',
'br',
@@ -231,6 +235,18 @@ function toggleSelectionQuote(): void {
editor.value.view.focus()
}
+function hasActiveTextFormat(): boolean {
+ if (!editor.value) return false
+ const size = editor.value.getAttributes('noteTextSize').size
+ return (
+ Boolean(size) ||
+ editor.value.isActive('bold') ||
+ editor.value.isActive('italic') ||
+ editor.value.isActive('underline') ||
+ editor.value.isActive('strike')
+ )
+}
+
watch(
() => props.modelValue,
(body) => {
@@ -256,149 +272,135 @@ onBeforeUnmount(() => editor.value?.destroy())
:editor="editor"
/>
-
-
-
-
-
-
-
-
- A−
-
-
- A+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ Aa
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A−
+
+
+ A+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -543,4 +545,10 @@ onBeforeUnmount(() => editor.value?.destroy())
.notes-rich-editor__text-tool--large {
font-size: 17px;
}
+
+.notes-rich-editor__format-tool {
+ font-size: 17px;
+ font-weight: 500;
+ letter-spacing: -0.03em;
+}
diff --git a/frontend/src/ui/controls.css b/frontend/src/ui/controls.css
index ed55b44..d75bb86 100644
--- a/frontend/src/ui/controls.css
+++ b/frontend/src/ui/controls.css
@@ -2724,10 +2724,25 @@ label.sky-list-item__row {
padding: 0;
}
+.sky-glass.sky-fab--neutral {
+ background: var(--sky-glass-solid, rgba(247, 247, 248, 0.96));
+ color: var(--sky-text, #000000);
+}
+
+.sky-fab--neutral .sky-fab__accent-layer,
+.sky-fab--neutral .sky-fab__dark-accent-layer,
+.sky-fab--neutral .sky-fab__surface-layer {
+ display: none;
+}
+
.sky-glass.sky-fab:active:not(:disabled) {
background: transparent;
}
+.sky-glass.sky-fab--neutral:active:not(:disabled) {
+ background: var(--sky-glass-solid, rgba(247, 247, 248, 0.96));
+}
+
.sky-fab--disabled {
cursor: default;
opacity: 0.42;
diff --git a/frontend/src/ui/controls/SkyFab.test.ts b/frontend/src/ui/controls/SkyFab.test.ts
index d07d6ba..cc6bf7d 100644
--- a/frontend/src/ui/controls/SkyFab.test.ts
+++ b/frontend/src/ui/controls/SkyFab.test.ts
@@ -55,4 +55,22 @@ describe('SkyFab', () => {
expect(fabTokens).toContain('--sky-fab-accent-inset-start')
expect(fabTokens).not.toContain('rgba(10, 132, 255, 0.25)')
})
+
+ it('offers a neutral glass variant without accent layers', async () => {
+ const html = await renderToString(
+ createSSRApp({
+ render: () => h(SkyFab, { ariaLabel: 'Create', variant: 'neutral' }),
+ }),
+ )
+
+ expect(html).toContain('sky-fab--neutral')
+
+ const controls = readFileSync(
+ fileURLToPath(new URL('../controls.css', import.meta.url)),
+ 'utf8',
+ )
+ expect(controls).toMatch(
+ /\.sky-glass\.sky-fab--neutral\s*\{[^}]*background:\s*var\(--sky-glass-solid/s,
+ )
+ })
})
diff --git a/frontend/src/ui/controls/SkyFab.vue b/frontend/src/ui/controls/SkyFab.vue
index 6979cb9..131b901 100644
--- a/frontend/src/ui/controls/SkyFab.vue
+++ b/frontend/src/ui/controls/SkyFab.vue
@@ -14,6 +14,7 @@ const props = withDefaults(
text?: string
textPosition?: 'after' | 'before'
type?: 'button' | 'reset' | 'submit'
+ variant?: 'neutral' | 'primary'
}>(),
{
ariaLabel: '',
@@ -23,6 +24,7 @@ const props = withDefaults(
text: '',
textPosition: 'after',
type: 'button',
+ variant: 'primary',
},
)
@@ -71,6 +73,7 @@ function handleClick(event: MouseEvent): void {
:class="{
'sky-fab--disabled': disabled,
'sky-fab--icon-only': !hasText,
+ 'sky-fab--neutral': variant === 'neutral',
'sky-fab--with-text': hasText,
}"
role="button"
diff --git a/frontend/src/views/apps/NotesApp.menu.contract.test.ts b/frontend/src/views/apps/NotesApp.menu.contract.test.ts
index d94f1f1..ed2df99 100644
--- a/frontend/src/views/apps/NotesApp.menu.contract.test.ts
+++ b/frontend/src/views/apps/NotesApp.menu.contract.test.ts
@@ -15,16 +15,22 @@ const listSource = source.slice(
describe('NotesApp list controls', () => {
it('places the Sky searchbar and create action together at the bottom', () => {
const composerSource = listSource.slice(
- listSource.indexOf(''.length,
+ listSource.indexOf('') + ''.length,
)
+ expect(composerSource).toContain('component="footer"')
+ expect(listSource).toContain('')
+ expect(listSource).not.toContain('!pt-[44px]')
})
})
diff --git a/frontend/src/views/apps/NotesApp.vue b/frontend/src/views/apps/NotesApp.vue
index 20b10a0..84432ff 100644
--- a/frontend/src/views/apps/NotesApp.vue
+++ b/frontend/src/views/apps/NotesApp.vue
@@ -24,7 +24,14 @@ import NotesRichTextEditor from '@/components/NotesRichTextEditor.vue'
import { useNotesStore } from '@/stores/notes'
import { useEasyShareStore } from '@/stores/easyshare'
import { usePhoneStore } from '@/stores/phone'
-import { SkyActionSheet, SkyButton, SkyFab, SkySearchbar } from '@/ui'
+import {
+ SkyActionSheet,
+ SkyButton,
+ SkyFab,
+ SkyScrollArea,
+ SkySearchbar,
+ SkyToolbar,
+} from '@/ui'
import type { Note } from '@/utils/notes'
import { noteBodyToPlainText } from '@/utils/noteRichText'
@@ -60,6 +67,7 @@ const visibleNotes = computed(() => {
const editorLabels = computed(() => ({
bold: phone.t('Apps.notes.tools.bold'),
bulletList: phone.t('Apps.notes.tools.bulletList'),
+ closeFormatting: phone.t('Common.close'),
decreaseText: phone.t('Apps.notes.tools.decreaseText'),
increaseText: phone.t('Apps.notes.tools.increaseText'),
italic: phone.t('Apps.notes.tools.italic'),
@@ -116,7 +124,10 @@ function editNote(note: Note): void {
function persistDraft(): Note | undefined {
const draft = {
body: draftBody.value,
- title: titleFromDraftBody(draftBody.value) || currentNote.value?.title.trim() || '',
+ title:
+ titleFromDraftBody(draftBody.value) ||
+ currentNote.value?.title.trim() ||
+ '',
}
if (editorId.value) {
@@ -174,65 +185,70 @@ function shareNote(): void {
-
-
-
-
-
-
-
-
-
- {{
- phone.t(searchQuery ? 'Apps.notes.noResults' : 'Apps.notes.emptyTitle')
- }}
- {{
- phone.t(
- searchQuery ? 'Apps.notes.noResultsBody' : 'Apps.notes.emptyBody',
- )
- }}
-
-
- {{ phone.t('Apps.notes.newNote') }}
-
+
+
+
+
+
+
+
-
-
+
+
+
-
+
@@ -281,11 +297,7 @@ function shareNote(): void {
{{ phone.t('Apps.easyShare.name') }}
-
+
{{
phone.t(
@@ -314,35 +326,10 @@ function shareNote(): void {