-
+
-
-
+
+
@@ -34,8 +34,7 @@
v-if="notesStore.currentNote && notesStore.currentNote.hasChildren() && settingsStore.settings.subNotesDisplayType !== 'NONE'"
:notes="notesStore.currentNote.children as Note[]" @select="notesStore.selectNote($event as Note)"
@delete="notesStore.deleteNote($event as Note)" @create="handleCreateNote($event as Note)" />
-
-
@@ -48,9 +47,11 @@ import { useSettingsStore } from '../../stores/useSettingsStore';
import { Note } from '../../business/domain/Note';
import { ref } from 'vue';
import debounce from 'debounce';
-import BullseyeIcon from 'icons/Bullseye.vue';
-import BullseyeArrowIcon from 'icons/BullseyeArrow.vue';
+import DockLeftIcon from 'icons/DockLeft.vue';
+import DockLeftNoFillIcon from '../../assets/icons/DockLeftNoFill.vue';
import Editor from './Editor.vue';
+import NoteChildren from './note-children/NoteChildren.vue';
+
const notesStore = useNotesStore();
const settingsStore = useSettingsStore();
@@ -60,6 +61,7 @@ const isFocused = ref(false);
const renameError = ref
(null);
const isLoading = ref(false);
+const editorComp = ref(null);
const debouncedSave = debounce(async (content: string) => {
@@ -100,6 +102,17 @@ function handleNoteContentChange(content: string) {
debouncedSave(content);
}
+function handleEditorWrapperClick(event: MouseEvent) {
+ if (!editorComp.value) return;
+ const target = event.target as HTMLElement | null;
+ if (target && (target.closest('.note-name') || target.closest('.options'))) {
+ return; // ignore clicks inside note-name or options
+ }
+ editorComp.value.handleParentClick();
+}
+
+defineExpose({ handleEditorWrapperClick });
+
function toggleFocusMode() {
settingsStore.updateSetting('focusMode', !settingsStore.settings.focusMode);
}
@@ -114,7 +127,6 @@ function handleUpdateLoadingState(loading: boolean) {
}
import '../../styles/editor.css';
-import NoteChildren from './note-children/NoteChildren.vue';
diff --git a/src/components/settings/Settings.vue b/src/components/settings/Settings.vue
index e9366ce..215f4e4 100644
--- a/src/components/settings/Settings.vue
+++ b/src/components/settings/Settings.vue
@@ -10,6 +10,10 @@
Remember last note:
+
+ Show floating menu on empty line:
+
+
Subnotes display:
@@ -64,6 +68,13 @@ const subNotesOption = computed({
}
});
+const showFloatingMenu = computed({
+ get: () => settingsStore.settings.showFloatingMenu,
+ set: (value: boolean) => {
+ settingsStore.updateSetting('showFloatingMenu', value);
+ }
+});
+
async function handleClose() {
await settingsStore.saveSettings();
emit('close');
diff --git a/src/persistence/repository/SettingsRepository.ts b/src/persistence/repository/SettingsRepository.ts
index 3d4ad0b..3a620ae 100644
--- a/src/persistence/repository/SettingsRepository.ts
+++ b/src/persistence/repository/SettingsRepository.ts
@@ -20,7 +20,8 @@ export class SettingsRepository {
rememberLastWorkspace: settings.rememberLastWorkspace,
rememberLastNote: settings.rememberLastNote,
focusMode: settings.focusMode,
- subNotesDisplayType: settings.subNotesDisplayType
+ subNotesDisplayType: settings.subNotesDisplayType,
+ showFloatingMenu: settings.showFloatingMenu
});
}
diff --git a/src/preload.ts b/src/preload.ts
index 0db2f63..57ca95a 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -7,8 +7,10 @@ import { WorkspaceI } from './business/domain/Workspace';
// During tests, we need to import the wdio-electron-service preload script
// It should not be imported in production
-// The import should not be done while building the application and is intented only for testing
-import('wdio-electron-service/preload');
+// The import should not be done while building the application and is intented only for testing.
+if (process.env.ENABLE_WDIO === 'true') {
+ import('wdio-electron-service/preload');
+}
contextBridge.exposeInMainWorld('fileAPI', {
// This needs to be done since the filesystem cannot be accessed directly from the renderer process
diff --git a/src/renderer.ts b/src/renderer.ts
index 9db5c83..79f15d8 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -24,7 +24,7 @@ app.config.errorHandler = (error: Error) => {
window.addEventListener('error', (event) => {
// Only show dialog for non-split errors, since there is no way to avoid them
- if (!event.filename.includes('split') && !event.filename.includes('404'))
+ if (!event.filename.includes('split') && !event.message.includes("reading 'max'") && !event.filename.includes('404'))
errorStore.setError(event.error || event.message);
console.error('Window error event:', event);
console.error('Error filename:', event.filename);
diff --git a/src/stores/useSettingsStore.ts b/src/stores/useSettingsStore.ts
index 6655656..335eff3 100644
--- a/src/stores/useSettingsStore.ts
+++ b/src/stores/useSettingsStore.ts
@@ -8,7 +8,8 @@ export const useSettingsStore = defineStore('settings', () => {
rememberLastWorkspace: false,
rememberLastNote: false,
focusMode: false,
- subNotesDisplayType: 'DEFAULT'
+ subNotesDisplayType: 'DEFAULT',
+ showFloatingMenu: true
});
const subNotesOptions = [
@@ -41,7 +42,8 @@ export const useSettingsStore = defineStore('settings', () => {
rememberLastWorkspace: settings.value.rememberLastWorkspace,
rememberLastNote: settings.value.rememberLastNote,
focusMode: false,
- subNotesDisplayType: settings.value.subNotesDisplayType
+ subNotesDisplayType: settings.value.subNotesDisplayType,
+ showFloatingMenu: settings.value.showFloatingMenu
});
}
@@ -59,7 +61,8 @@ export const useSettingsStore = defineStore('settings', () => {
rememberLastWorkspace: false,
rememberLastNote: false,
focusMode: false,
- subNotesDisplayType: 'DEFAULT'
+ subNotesDisplayType: 'DEFAULT',
+ showFloatingMenu: true
};
}
diff --git a/src/utils/ipc/settingsHandler.ts b/src/utils/ipc/settingsHandler.ts
index 1e57a9f..f4531b0 100644
--- a/src/utils/ipc/settingsHandler.ts
+++ b/src/utils/ipc/settingsHandler.ts
@@ -12,6 +12,7 @@ const defaults: SettingsSchema = {
rememberLastNote: false,
focusMode: false,
subNotesDisplayType: 'DEFAULT',
+ showFloatingMenu: true
};
/**
diff --git a/src/views/NoteSpace.vue b/src/views/NoteSpace.vue
index 0c509fa..f06114d 100644
--- a/src/views/NoteSpace.vue
+++ b/src/views/NoteSpace.vue
@@ -13,8 +13,16 @@
-
-
+
@@ -28,8 +36,9 @@ import { Splitpanes, Pane } from 'splitpanes';
import { useWorkspaceStore } from '../stores/useWorkspaceStore';
import { useSettingsStore } from '../stores/useSettingsStore';
import { useNotesStore } from '../stores/useNotesStore';
-import { ref, onMounted, onUnmounted } from 'vue';
-
+import GenericButton from '../components/generic/GenericButton.vue';
+import { ref, onMounted, onUnmounted, computed } from 'vue';
+import FilePlusOutline from 'icons/FilePlusOutline.vue';
const workspaceStore = useWorkspaceStore();
const settingsStore = useSettingsStore();
const notesStore = useNotesStore();
@@ -38,6 +47,18 @@ const notesStore = useNotesStore();
const smallScreenPaneMinSize = 20;
const largeScreenPaneMinSize = 12.5;
const paneMinSize = ref(largeScreenPaneMinSize);
+const editorViewRef = ref
(null);
+
+const hasNotes = computed(() => {
+ return (notesStore.noteTree?.length ?? 0) > 0;
+});
+
+async function handleNewNote() {
+ const newNote = await notesStore.createNote();
+ if (newNote) {
+ notesStore.selectNote(newNote);
+ }
+}
function updateMinSize() {
if (window.innerWidth < 1200) {
@@ -62,6 +83,10 @@ onUnmounted(() => {
window.removeEventListener('resize', updateMinSize);
});
+function onEditorPaneClick(event: MouseEvent) {
+ editorViewRef.value?.handleEditorWrapperClick?.(event);
+}
+
\ No newline at end of file
diff --git a/test/e2e/pageobjects/PO_Editor.ts b/test/e2e/pageobjects/PO_Editor.ts
index db7a37e..69c84f0 100644
--- a/test/e2e/pageobjects/PO_Editor.ts
+++ b/test/e2e/pageobjects/PO_Editor.ts
@@ -181,10 +181,10 @@ export default class POEditor extends POApp {
}
static async clickBubbleButton(label: string) {
- // Find button by aria-label inside bubble menu
+ // Find button by title attribute on the bubble button
const menu = await POEditor.getBubbleMenu();
- const btn = menu.$(`.bubble-button span[aria-label="${label}"]`);
- await btn.parentElement().click();
+ const btn = menu.$(`.bubble-button[title="${label}"]`);
+ await btn.click();
}
static async #assertElementText(selector: string, expected: string) {
@@ -216,7 +216,7 @@ export default class POEditor extends POApp {
static async openAddLinkDialog() {
const menu = await POEditor.getBubbleMenu();
- const linkBtn = menu.$('.bubble-button:has(span[aria-label="Link"])');
+ const linkBtn = menu.$('.bubble-button[title="Link"]');
await linkBtn.click();
}
diff --git a/test/e2e/pageobjects/PO_Settings.ts b/test/e2e/pageobjects/PO_Settings.ts
index 8c370ea..efde88c 100644
--- a/test/e2e/pageobjects/PO_Settings.ts
+++ b/test/e2e/pageobjects/PO_Settings.ts
@@ -30,25 +30,34 @@ export default class POSettings extends POApp {
await $("//div[@class='n-radio__label' and text()='None']").click();
}
-
static async restoreDefaults() {
await $(`//button[contains(@class, 'generic-btn') and contains(@class, 'secondary') and text()='Restore Defaults']`).click();
}
-
static getLatWorkspaceSwitch() {
return $("//div[contains(@class, 'settings-option')][.//text()[contains(., 'Remember last workspace:')]]//div[@role='switch']");
}
+
static getRememberLastNoteSwitch() {
return $("//div[contains(@class, 'settings-option')][.//text()[contains(., 'Remember last note:')]]//div[@role='switch']");
}
+ static getShowFloatingMenuSwitch() {
+ return $("//div[contains(@class, 'settings-option')][.//text()[contains(., 'Show floating menu on empty line:')]]//div[@role='switch']");
+ }
+
static async selectRememberLastNote(expectedValue: string = 'true') {
const switchDiv = this.getRememberLastNoteSwitch();
await switchDiv.click();
await expect(switchDiv).toHaveAttribute('aria-checked', expectedValue);
}
+ static async selectShowFloatingMenu(expectedValue: string = 'true') {
+ const switchDiv = this.getShowFloatingMenuSwitch();
+ await switchDiv.click();
+ await expect(switchDiv).toHaveAttribute('aria-checked', expectedValue);
+ }
+
static async selectRememberWorkspace(expectedValue: string = 'true') {
const switchDiv = this.getLatWorkspaceSwitch();
await switchDiv.click();
@@ -70,6 +79,7 @@ export default class POSettings extends POApp {
static async checkDefaultValuesHelper() {
const lastNoteSwitch = this.getRememberLastNoteSwitch();
const lastWorkspaceSwitch = this.getLatWorkspaceSwitch();
+ const showFloatingSwitch = this.getShowFloatingMenuSwitch();
const subnotesDefaultRadioValue = this.getSubnotesDisplayRadioValue();
let isDefault = true;
@@ -84,6 +94,11 @@ export default class POSettings extends POApp {
isDefault = false;
}
+ const showFloatingChecked = await showFloatingSwitch.getAttribute('aria-checked');
+ if (showFloatingChecked !== 'true') {
+ isDefault = false;
+ }
+
if (await subnotesDefaultRadioValue !== 'DEFAULT') {
isDefault = false;
}
diff --git a/test/e2e/settings.e2e.ts b/test/e2e/settings.e2e.ts
index 8baa0fa..6c53f35 100644
--- a/test/e2e/settings.e2e.ts
+++ b/test/e2e/settings.e2e.ts
@@ -131,6 +131,7 @@ describe('Settings Testing', () => {
// Change settings from defaults
await POSettings.openSettings();
await POSettings.selectSubnotesBigOnly();
+ await POSettings.selectShowFloatingMenu('false');
await POSettings.selectRememberWorkspace();
await POSettings.selectRememberLastNote();
await POSettings.checkNonDefaultValues();
diff --git a/test/unit/stores/useSettings.test.ts b/test/unit/stores/useSettings.test.ts
index 1661490..90a9b63 100644
--- a/test/unit/stores/useSettings.test.ts
+++ b/test/unit/stores/useSettings.test.ts
@@ -11,7 +11,8 @@ window.settingsAPI = {
rememberLastWorkspace: true,
rememberLastNote: true,
focusMode: true,
- subNotesDisplayType: 'DEFAULT'
+ subNotesDisplayType: 'DEFAULT',
+ showFloatingMenu: true
};
return settings;
}),
@@ -30,6 +31,7 @@ describe('GIVEN the useSettingsStore store', () => {
const store = useSettingsStore();
expect(store.settings.rememberLastWorkspace).toBe(false);
expect(store.settings.focusMode).toBe(false);
+ expect(store.settings.showFloatingMenu).toBe(true);
});
});
@@ -41,6 +43,7 @@ describe('GIVEN the useSettingsStore store', () => {
expect(window.settingsAPI.getSettings).toHaveBeenCalled();
expect(store.settings.rememberLastWorkspace).toBe(true);
expect(store.settings.focusMode).toBe(true);
+ expect(store.settings.showFloatingMenu).toBe(true);
});
});
@@ -61,6 +64,8 @@ describe('GIVEN the useSettingsStore store', () => {
expect(store.settings.focusMode).toBe(true);
store.updateSetting('rememberLastWorkspace', true);
expect(store.settings.rememberLastWorkspace).toBe(true);
+ store.updateSetting('showFloatingMenu', true);
+ expect(store.settings.showFloatingMenu).toBe(true);
});
});
@@ -75,11 +80,14 @@ describe('GIVEN the useSettingsStore store', () => {
expect(store.settings.rememberLastWorkspace).toBe(true);
store.updateSetting('subNotesDisplayType', 'BIG_ONLY');
expect(store.settings.subNotesDisplayType).toBe('BIG_ONLY');
+ store.updateSetting('showFloatingMenu', true);
+ expect(store.settings.showFloatingMenu).toBe(true);
store.restoreDefaultSettings();
expect(store.settings.focusMode).toBe(false);
expect(store.settings.rememberLastWorkspace).toBe(false);
expect(store.settings.subNotesDisplayType).toBe('DEFAULT');
+ expect(store.settings.showFloatingMenu).toBe(true);
});
});