pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Io import qs.Common import qs.Modals.Common import qs.Modals.FileBrowser import qs.Services import qs.Widgets Item { id: root property bool fileDialogOpen: false property string currentFileName: "" property url currentFileUrl property bool confirmationDialogOpen: false property string pendingAction: "" property url pendingFileUrl property string lastSavedFileContent: "" property var currentTab: NotepadStorageService.tabs.length > NotepadStorageService.currentTabIndex ? NotepadStorageService.tabs[NotepadStorageService.currentTabIndex] : null property bool showSettingsMenu: false property string pendingSaveContent: "" signal hideRequested Ref { service: NotepadStorageService } function hasUnsavedChanges() { return textEditor.hasUnsavedChanges(); } function hasUnsavedTemporaryContent() { return hasUnsavedChanges(); } function createNewTab() { performCreateNewTab(); } function performCreateNewTab() { NotepadStorageService.createNewTab(); textEditor.text = ""; textEditor.lastSavedContent = ""; textEditor.contentLoaded = true; textEditor.textArea.forceActiveFocus(); } function closeTab(tabIndex) { if (tabIndex === NotepadStorageService.currentTabIndex && hasUnsavedChanges()) { root.pendingAction = "close_tab_" + tabIndex; root.confirmationDialogOpen = true; confirmationDialogLoader.active = true; if (confirmationDialogLoader.item) confirmationDialogLoader.item.open(); } else { performCloseTab(tabIndex); } } function performCloseTab(tabIndex) { NotepadStorageService.closeTab(tabIndex); Qt.callLater(() => { textEditor.loadCurrentTabContent(); }); } function switchToTab(tabIndex) { if (tabIndex < 0 || tabIndex >= NotepadStorageService.tabs.length) return; if (textEditor.contentLoaded) { textEditor.autoSaveToSession(); } NotepadStorageService.switchToTab(tabIndex); Qt.callLater(() => { textEditor.loadCurrentTabContent(); if (currentTab) { root.currentFileName = currentTab.fileName || ""; root.currentFileUrl = currentTab.fileUrl || ""; } }); } function saveToFile(fileUrl) { if (!currentTab) return; var content = textEditor.text; var filePath = fileUrl.toString().replace(/^file:\/\//, ''); saveFileView.path = ""; pendingSaveContent = content; saveFileView.path = filePath; Qt.callLater(() => { saveFileView.setText(pendingSaveContent); }); } function loadFromFile(fileUrl) { if (hasUnsavedTemporaryContent()) { root.pendingFileUrl = fileUrl; root.pendingAction = "load_file"; root.confirmationDialogOpen = true; confirmationDialogLoader.active = true; if (confirmationDialogLoader.item) confirmationDialogLoader.item.open(); } else { performLoadFromFile(fileUrl); } } function performLoadFromFile(fileUrl) { const filePath = fileUrl.toString().replace(/^file:\/\//, ''); const fileName = filePath.split('/').pop(); loadFileView.path = ""; loadFileView.path = filePath; if (loadFileView.waitForJob()) { Qt.callLater(() => { var content = loadFileView.text(); if (currentTab && content !== undefined && content !== null) { textEditor.text = content; textEditor.lastSavedContent = content; textEditor.contentLoaded = true; root.lastSavedFileContent = content; NotepadStorageService.updateTabMetadata(NotepadStorageService.currentTabIndex, { title: fileName, filePath: filePath, isTemporary: false }); root.currentFileName = fileName; root.currentFileUrl = fileUrl; textEditor.saveCurrentTabContent(); } }); } } Column { anchors.fill: parent spacing: Theme.spacingM NotepadTabs { id: tabBar width: parent.width contentLoaded: textEditor.contentLoaded onTabSwitched: tabIndex => { switchToTab(tabIndex); } onTabClosed: tabIndex => { closeTab(tabIndex); } onNewTabRequested: { createNewTab(); } } NotepadTextEditor { id: textEditor width: parent.width height: parent.height - tabBar.height - Theme.spacingM * 2 onSaveRequested: { if (currentTab && !currentTab.isTemporary && currentTab.filePath) { var fileUrl = "file://" + currentTab.filePath; saveToFile(fileUrl); } else { root.fileDialogOpen = true; saveBrowserLoader.active = true; if (saveBrowserLoader.item) saveBrowserLoader.item.open(); } } onOpenRequested: { textEditor.autoSaveToSession(); if (textEditor.text.length > 0) { createNewTab(); } root.fileDialogOpen = true; loadBrowserLoader.active = true; if (loadBrowserLoader.item) loadBrowserLoader.item.open(); } onNewRequested: { textEditor.autoSaveToSession(); createNewTab(); } onEscapePressed: { root.hideRequested(); } onSettingsRequested: { showSettingsMenu = !showSettingsMenu; } } } NotepadSettings { id: notepadSettings anchors.fill: parent isVisible: showSettingsMenu onSettingsRequested: showSettingsMenu = !showSettingsMenu onFindRequested: { showSettingsMenu = false; textEditor.showSearch(); } } FileView { id: saveFileView blockWrites: true preload: false atomicWrites: true printErrors: true onSaved: { if (currentTab && saveFileView.path && pendingSaveContent) { NotepadStorageService.updateTabMetadata(NotepadStorageService.currentTabIndex, { hasUnsavedChanges: false, lastSavedContent: pendingSaveContent }); root.lastSavedFileContent = pendingSaveContent; pendingSaveContent = ""; } } onSaveFailed: error => { pendingSaveContent = ""; } } FileView { id: loadFileView blockLoading: true preload: true atomicWrites: true printErrors: true onLoadFailed: error => {} } LazyLoader { id: saveBrowserLoader active: false FileBrowserSurfaceModal { id: saveBrowser browserTitle: I18n.tr("Save Notepad File") browserIcon: "save" browserType: "notepad_save" fileExtensions: ["*.txt", "*.md", "*.*"] allowStacking: true saveMode: true defaultFileName: { if (currentTab && currentTab.title && currentTab.title !== "Untitled") { return currentTab.title; } else if (currentTab && !currentTab.isTemporary && currentTab.filePath) { return currentTab.filePath.split('/').pop(); } else { return "note.txt"; } } onFileSelected: path => { root.fileDialogOpen = false; const cleanPath = path.toString().replace(/^file:\/\//, ''); const fileName = cleanPath.split('/').pop(); const fileUrl = "file://" + cleanPath; root.currentFileName = fileName; root.currentFileUrl = fileUrl; if (currentTab) { NotepadStorageService.saveTabAs(NotepadStorageService.currentTabIndex, cleanPath); } saveToFile(fileUrl); if (root.pendingAction === "new") { Qt.callLater(() => { createNewTab(); }); } else if (root.pendingAction === "open") { Qt.callLater(() => { root.fileDialogOpen = true; loadBrowserLoader.active = true; if (loadBrowserLoader.item) loadBrowserLoader.item.open(); }); } else if (root.pendingAction.startsWith("close_tab_")) { Qt.callLater(() => { var tabIndex = parseInt(root.pendingAction.split("_")[2]); performCloseTab(tabIndex); }); } root.pendingAction = ""; close(); } onDialogClosed: { root.fileDialogOpen = false; } } } LazyLoader { id: loadBrowserLoader active: false FileBrowserSurfaceModal { id: loadBrowser browserTitle: I18n.tr("Open Notepad File") browserIcon: "folder_open" browserType: "notepad_load" fileExtensions: ["*.txt", "*.md", "*.*"] allowStacking: true onFileSelected: path => { root.fileDialogOpen = false; const cleanPath = path.toString().replace(/^file:\/\//, ''); const fileName = cleanPath.split('/').pop(); const fileUrl = "file://" + cleanPath; root.currentFileName = fileName; root.currentFileUrl = fileUrl; loadFromFile(fileUrl); close(); } onDialogClosed: { root.fileDialogOpen = false; } } } LazyLoader { id: confirmationDialogLoader active: false DankModal { id: confirmationDialog width: 400 height: 180 shouldBeVisible: false allowStacking: true onBackgroundClicked: { close(); root.confirmationDialogOpen = false; } content: Component { FocusScope { anchors.fill: parent focus: true Keys.onEscapePressed: event => { confirmationDialog.close(); root.confirmationDialogOpen = false; event.accepted = true; } Column { anchors.centerIn: parent width: parent.width - Theme.spacingM * 2 spacing: Theme.spacingM Row { width: parent.width Column { width: parent.width - 40 spacing: Theme.spacingXS StyledText { text: I18n.tr("Unsaved Changes") font.pixelSize: Theme.fontSizeLarge color: Theme.surfaceText font.weight: Font.Medium } StyledText { text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?") font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceTextMedium width: parent.width wrapMode: Text.Wrap } } DankActionButton { iconName: "close" iconSize: Theme.iconSize - 4 iconColor: Theme.surfaceText onClicked: { confirmationDialog.close(); root.confirmationDialogOpen = false; } } } Item { width: parent.width height: 40 Row { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: Theme.spacingM Rectangle { width: Math.max(80, discardText.contentWidth + Theme.spacingM * 2) height: 36 radius: Theme.cornerRadius color: discardArea.containsMouse ? Theme.surfaceTextHover : "transparent" border.color: Theme.surfaceVariantAlpha border.width: 1 StyledText { id: discardText anchors.centerIn: parent text: I18n.tr("Don't Save") font.pixelSize: Theme.fontSizeMedium color: Theme.surfaceText font.weight: Font.Medium } MouseArea { id: discardArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { confirmationDialog.close(); root.confirmationDialogOpen = false; if (root.pendingAction === "new") { createNewTab(); } else if (root.pendingAction === "open") { root.fileDialogOpen = true; loadBrowserLoader.active = true; if (loadBrowserLoader.item) loadBrowserLoader.item.open(); } else if (root.pendingAction === "load_file") { performLoadFromFile(root.pendingFileUrl); } else if (root.pendingAction.startsWith("close_tab_")) { var tabIndex = parseInt(root.pendingAction.split("_")[2]); performCloseTab(tabIndex); } root.pendingAction = ""; root.pendingFileUrl = ""; } } } Rectangle { width: Math.max(70, saveAsText.contentWidth + Theme.spacingM * 2) height: 36 radius: Theme.cornerRadius color: saveAsArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary StyledText { id: saveAsText anchors.centerIn: parent text: I18n.tr("Save") font.pixelSize: Theme.fontSizeMedium color: Theme.background font.weight: Font.Medium } MouseArea { id: saveAsArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { confirmationDialog.close(); root.confirmationDialogOpen = false; root.fileDialogOpen = true; saveBrowserLoader.active = true; if (saveBrowserLoader.item) saveBrowserLoader.item.open(); } } Behavior on color { ColorAnimation { duration: Theme.shortDuration easing.type: Theme.standardEasing } } } } } } } } } } }