Files
Domipoke 955801c666 noctalia
2026-06-25 19:58:23 +02:00

410 lines
14 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services.UI
import qs.Widgets
Rectangle {
id: root
// Properties
property var pluginApi: null
property var note: null
property int noteIndex: 0
// Color schemes
property var colorSchemes: ({
"yellow": {
bg: "#FFF9C4",
fg: "#000000",
header: "#FDD835"
},
"pink": {
bg: "#FCE4EC",
fg: "#000000",
header: "#F06292"
},
"blue": {
bg: "#E3F2FD",
fg: "#000000",
header: "#42A5F5"
},
"green": {
bg: "#E8F5E9",
fg: "#000000",
header: "#66BB6A"
},
"purple": {
bg: "#F3E5F5",
fg: "#000000",
header: "#AB47BC"
}
})
// Constants for sizing
readonly property int minHeight: 200
readonly property int maxHeight: 600
readonly property int headerHeight: 40
readonly property int margins: 24
// Position and size from note data
x: note ? note.x : 0
y: note ? note.y : 0
width: note ? note.width : 350
height: note ? note.height : minHeight
z: note ? note.zIndex : 0
// Color from note data
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.bg : "#FFF9C4";
}
border.color: Color.mOnSurfaceVariant
border.width: 1
radius: Style.radiusM
// Main layout
ColumnLayout {
anchors.fill: parent
spacing: 0
// Header
Rectangle {
id: headerBar
Layout.fillWidth: true
Layout.preferredHeight: root.headerHeight
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.header : "#FDD835";
}
topLeftRadius: Style.radiusM
topRightRadius: Style.radiusM
bottomLeftRadius: 0
bottomRightRadius: 0
RowLayout {
id: headerContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
anchors.rightMargin: 6
spacing: 10
z: 1
// Icon - DRAG HANDLE
Item {
Layout.preferredWidth: 24
Layout.fillHeight: true
NIcon {
anchors.centerIn: parent
icon: "note"
pointSize: 15
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
}
MouseArea {
id: dragArea
anchors.fill: parent
cursorShape: Qt.SizeAllCursor
drag.target: root
drag.axis: Drag.XAndYAxis
drag.minimumX: 0
drag.maximumX: root.parent ? (root.parent.width - root.width) : 1200
drag.minimumY: 0
drag.maximumY: root.parent ? (root.parent.height - root.height) : 700
onPressed: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.bringNoteToFront(root.note.id);
}
}
onReleased: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
x: root.x,
y: root.y
});
}
}
}
}
// Title
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 100
TextInput {
id: titleInput
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
font.pixelSize: 14
font.bold: false
selectByMouse: true
clip: true
Text {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
text: pluginApi?.tr("notecards.untitled-placeholder")
color: parent.color
opacity: 0.5
visible: titleInput.text.length === 0
font: titleInput.font
}
Component.onCompleted: {
if (note) {
text = note.title || "";
}
}
onAccepted: {
root.syncChanges();
titleInput.focus = false;
}
}
}
NIconButton {
icon: note && note.isPrivate ? "eye-off" : "eye"
tooltipText: pluginApi?.tr("notecards.toggle-privacy-mode")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
isPrivate: !(root.note && root.note.isPrivate)
});
}
}
}
NIconButton {
icon: "palette"
tooltipText: pluginApi?.tr("notecards.change-color")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
const colors = ["yellow", "pink", "blue", "green", "purple"];
const noteColor = (root.note && root.note.color) ? root.note.color : "yellow";
const currentIndex = colors.indexOf(noteColor);
const nextIndex = (currentIndex + 1) % colors.length;
const nextColor = colors[nextIndex];
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
color: nextColor
});
}
}
}
NIconButton {
icon: "file-export"
tooltipText: pluginApi?.tr("notecards.export")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.exportNoteCard(root.note.id);
}
}
}
NIconButton {
icon: "trash"
tooltipText: pluginApi?.tr("notecards.delete")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.deleteNoteCard(root.note.id);
}
}
}
}
}
// Separator
Rectangle {
width: parent.width - 10
Layout.alignment: Qt.AlignHCenter
height: 1
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: "transparent"
}
GradientStop {
position: 0.5
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.header : "#FDD835";
}
}
GradientStop {
position: 1.0
color: "transparent"
}
}
}
// Content area with ScrollView
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 12
clip: true
ScrollView {
anchors.fill: parent
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
TextArea {
id: textArea
width: parent.width
wrapMode: TextArea.Wrap
selectByMouse: true
color: {
if (note.isPrivate && !textArea.activeFocus) {
return "transparent"
}
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
font.pixelSize: 14
background: Rectangle {
color: "transparent"
}
Component.onCompleted: {
if (note) {
text = note.content || "";
}
// Check if we need to expand card on load
Qt.callLater(checkAndExpandHeight);
}
onTextChanged: {
if (note) {
note.content = text;
}
}
}
}
Rectangle {
enabled: false
opacity: 1.0
visible: note.isPrivate && !textArea.activeFocus
anchors.fill: parent
color: "transparent"
NIcon {
anchors.centerIn: parent
icon: "password"
pointSize: 45
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
}
}
}
}
// Check if card needs to be expanded to fit content
function checkAndExpandHeight() {
if (!textArea || !note)
return;
const contentHeight = textArea.contentHeight;
const availableHeight = root.height - root.headerHeight - root.margins - 1; // 1 = separator
// If content doesn't fit, expand card
if (contentHeight > availableHeight) {
let newHeight = root.headerHeight + root.margins + contentHeight + 1;
newHeight = Math.min(newHeight, root.maxHeight);
if (newHeight !== root.height && root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
height: newHeight
});
}
}
}
function syncChanges() {
if (root.pluginApi && root.pluginApi.mainInstance && note) {
root.pluginApi.mainInstance.updateNoteCard(note.id, {
title: titleInput.text,
content: textArea.text
});
}
}
Component.onDestruction: {
root.syncChanges();
}
}