Personal noctalia plugins collection

Improve todo rendering

+475 -28
+33 -1
weekly-calendar/Main.qml
··· 294 294 if (!showCompletedTodos && todo.status === "COMPLETED") continue 295 295 296 296 var isDueAllDay = (dueDate.getHours() === 0 && dueDate.getMinutes() === 0) 297 - // Render timed todos as short deadline markers (~8–10 px tall) 297 + // Render timed todos as horizontal line markers (keep a short span for ordering/click hitbox) 298 298 var endDate = isDueAllDay ? new Date(dueDate.getTime() + 86400000) 299 299 : new Date(dueDate.getTime() + 30 * 60000) 300 300 ··· 313 313 calendarUid: todo.calendarUid, 314 314 todoStatus: todo.status, 315 315 todoPriority: todo.priority, 316 + isLineTodo: !isDueAllDay, 316 317 // Helper flags for compact rendering in Panel.qml 317 318 isDeadlineMarker: !isDueAllDay 318 319 } ··· 470 471 var events = [] 471 472 for (var i = 0; i < eventsModel.count; i++) { 472 473 var e = eventsModel.get(i) 474 + if (e.isTodo) continue // Timed todos render as overlay lines; don't occupy lanes 473 475 if (getDisplayDayIndexForDate(e.startTime) === day) { 474 476 events.push({index: i, start: e.startTime.getTime(), end: e.endTime.getTime()}) 475 477 } ··· 637 639 property var selectedEvent: null 638 640 property bool showEventDetail: false 639 641 642 + // Todo detail popup state 643 + property var selectedTodo: null 644 + property bool showTodoDetail: false 645 + 640 646 function handleEventClick(event) { 641 647 selectedEvent = { 642 648 title: event.title || "", ··· 672 678 if (endTs > 0) { args.push("--end"); args.push(String(endTs)) } 673 679 updateEventProcess.command = args 674 680 updateEventProcess.running = true 681 + } 682 + 683 + function handleTodoClick(todoData) { 684 + selectedTodo = { 685 + summary: todoData.title || todoData.summary || "", 686 + description: todoData.description || "", 687 + todoUid: todoData.todoUid || "", 688 + calendarUid: todoData.calendarUid || "", 689 + status: todoData.todoStatus || "", 690 + priority: todoData.todoPriority || 0, 691 + due: todoData.startTime || null 692 + } 693 + showTodoDetail = true 694 + } 695 + 696 + function updateTodoFields(taskListUid, todoUid, summary, description, dueTs, priority) { 697 + if (!pluginApi) return 698 + var scriptPath = pluginApi.pluginDir + "/scripts/update-todo.py" 699 + var args = ["python3", scriptPath, 700 + "--task-list", taskListUid, "--uid", todoUid, "--action", "update"] 701 + if (summary !== undefined && summary !== null) { args.push("--summary"); args.push(summary) } 702 + if (description !== undefined && description !== null) { args.push("--description"); args.push(description) } 703 + if (dueTs > 0) { args.push("--due"); args.push(String(dueTs)) } 704 + if (priority >= 0) { args.push("--priority"); args.push(String(priority)) } 705 + updateTodoProcess.command = args 706 + updateTodoProcess.running = true 675 707 } 676 708 677 709 function goToToday() { currentDate = new Date() }
+400 -24
weekly-calendar/Panel.qml
··· 25 25 property bool showEventDetailDialog: false 26 26 property bool eventDetailEditMode: false 27 27 property bool showDeleteConfirmation: false 28 + property bool showTodoDetailDialog: false 29 + property bool todoDetailEditMode: false 30 + property bool showTodoDeleteConfirmation: false 28 31 29 32 property real defaultHourHeight: 50 * Style.uiScaleRatio 30 33 property real minHourHeight: 32 * Style.uiScaleRatio ··· 711 714 } 712 715 } 713 716 717 + // Todo detail/edit popup 718 + Rectangle { 719 + id: todoDetailOverlay 720 + anchors.fill: parent 721 + color: Qt.rgba(0, 0, 0, 0.5) 722 + visible: showTodoDetailDialog 723 + z: 2000 724 + 725 + MouseArea { anchors.fill: parent; onClicked: { showTodoDetailDialog = false; todoDetailEditMode = false; showTodoDeleteConfirmation = false } } 726 + 727 + Rectangle { 728 + anchors.centerIn: parent 729 + width: 420 * Style.uiScaleRatio 730 + height: todoDetailColumn.implicitHeight + 2 * Style.marginM 731 + color: Color.mSurface 732 + radius: Style.radiusM 733 + 734 + MouseArea { anchors.fill: parent } 735 + 736 + ColumnLayout { 737 + id: todoDetailColumn 738 + anchors.fill: parent 739 + anchors.margins: Style.marginM 740 + spacing: Style.marginS 741 + 742 + property var todo: mainInstance?.selectedTodo || {} 743 + 744 + // View mode 745 + ColumnLayout { 746 + visible: !todoDetailEditMode && !showTodoDeleteConfirmation 747 + spacing: Style.marginS 748 + Layout.fillWidth: true 749 + 750 + NText { 751 + text: (todoDetailColumn.todo.status === "COMPLETED" ? "\u2611 " : "\u2610 ") + (todoDetailColumn.todo.summary || "") 752 + font.pointSize: Style.fontSizeL; font.weight: Font.Bold 753 + color: Color.mOnSurface 754 + wrapMode: Text.Wrap; Layout.fillWidth: true 755 + } 756 + 757 + RowLayout { 758 + visible: todoDetailColumn.todo.due != null 759 + spacing: Style.marginS 760 + NIcon { icon: "clock"; pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant } 761 + NText { 762 + text: todoDetailColumn.todo.due ? mainInstance?.formatDateTime(new Date(todoDetailColumn.todo.due)) || "" : "" 763 + font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 764 + } 765 + } 766 + 767 + RowLayout { 768 + visible: (todoDetailColumn.todo.priority || 0) > 0 769 + spacing: Style.marginS 770 + NIcon { icon: "flag"; pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant } 771 + NText { 772 + text: { 773 + var p = todoDetailColumn.todo.priority || 0 774 + return p <= 4 ? (pluginApi.tr("panel.priority") + ": " + pluginApi.tr("panel.priority_high")) : 775 + p <= 6 ? (pluginApi.tr("panel.priority") + ": " + pluginApi.tr("panel.priority_medium")) : 776 + (pluginApi.tr("panel.priority") + ": " + pluginApi.tr("panel.priority_low")) 777 + } 778 + font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 779 + } 780 + } 781 + 782 + NText { 783 + visible: (todoDetailColumn.todo.description || "") !== "" 784 + text: todoDetailColumn.todo.description || "" 785 + font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant 786 + wrapMode: Text.Wrap; Layout.fillWidth: true 787 + } 788 + 789 + RowLayout { 790 + Layout.fillWidth: true 791 + spacing: Style.marginS 792 + 793 + // Toggle complete button 794 + Rectangle { 795 + Layout.preferredWidth: toggleTodoBtn.implicitWidth + 2 * Style.marginM 796 + Layout.preferredHeight: toggleTodoBtn.implicitHeight + Style.marginS 797 + color: Color.mSecondary; radius: Style.radiusS 798 + NText { 799 + id: toggleTodoBtn; anchors.centerIn: parent 800 + text: todoDetailColumn.todo.status === "COMPLETED" ? "\u2610" : "\u2611" 801 + color: Color.mOnSecondary; font.weight: Font.Bold 802 + } 803 + MouseArea { 804 + anchors.fill: parent; cursorShape: Qt.PointingHandCursor 805 + onClicked: { 806 + var t = todoDetailColumn.todo 807 + if (t.status === "COMPLETED") 808 + mainInstance?.uncompleteTodo(t.calendarUid, t.todoUid) 809 + else 810 + mainInstance?.completeTodo(t.calendarUid, t.todoUid) 811 + showTodoDetailDialog = false 812 + } 813 + } 814 + } 815 + 816 + Item { Layout.fillWidth: true } 817 + 818 + Rectangle { 819 + Layout.preferredWidth: editTodoBtn.implicitWidth + 2 * Style.marginM 820 + Layout.preferredHeight: editTodoBtn.implicitHeight + Style.marginS 821 + color: Color.mPrimary; radius: Style.radiusS 822 + NText { 823 + id: editTodoBtn; anchors.centerIn: parent 824 + text: pluginApi.tr("panel.edit"); color: Color.mOnPrimary; font.weight: Font.Bold 825 + } 826 + MouseArea { 827 + anchors.fill: parent; cursorShape: Qt.PointingHandCursor 828 + onClicked: { 829 + var t = todoDetailColumn.todo 830 + editTodoSummary.text = t.summary || "" 831 + editTodoDescription.text = t.description || "" 832 + if (t.due) { 833 + var d = new Date(t.due) 834 + editTodoDueDate.text = d.getFullYear() + "-" + String(d.getMonth()+1).padStart(2,'0') + "-" + String(d.getDate()).padStart(2,'0') 835 + editTodoDueTime.text = String(d.getHours()).padStart(2,'0') + ":" + String(d.getMinutes()).padStart(2,'0') 836 + } else { 837 + editTodoDueDate.text = "" 838 + editTodoDueTime.text = "" 839 + } 840 + editTodoPriority = t.priority || 0 841 + todoDetailEditMode = true 842 + } 843 + } 844 + } 845 + 846 + Rectangle { 847 + Layout.preferredWidth: deleteTodoBtn.implicitWidth + 2 * Style.marginM 848 + Layout.preferredHeight: deleteTodoBtn.implicitHeight + Style.marginS 849 + color: Color.mError; radius: Style.radiusS 850 + NText { 851 + id: deleteTodoBtn; anchors.centerIn: parent 852 + text: pluginApi.tr("panel.delete"); color: Color.mOnError; font.weight: Font.Bold 853 + } 854 + MouseArea { 855 + anchors.fill: parent; cursorShape: Qt.PointingHandCursor 856 + onClicked: showTodoDeleteConfirmation = true 857 + } 858 + } 859 + 860 + Rectangle { 861 + Layout.preferredWidth: closeTodoBtn.implicitWidth + 2 * Style.marginM 862 + Layout.preferredHeight: closeTodoBtn.implicitHeight + Style.marginS 863 + color: Color.mSurfaceVariant; radius: Style.radiusS 864 + NText { 865 + id: closeTodoBtn; anchors.centerIn: parent 866 + text: pluginApi.tr("panel.close"); color: Color.mOnSurfaceVariant 867 + } 868 + MouseArea { 869 + anchors.fill: parent; cursorShape: Qt.PointingHandCursor 870 + onClicked: { showTodoDetailDialog = false; todoDetailEditMode = false } 871 + } 872 + } 873 + } 874 + } 875 + 876 + // Delete confirmation 877 + ColumnLayout { 878 + visible: showTodoDeleteConfirmation 879 + spacing: Style.marginS 880 + Layout.fillWidth: true 881 + 882 + NText { 883 + text: pluginApi.tr("panel.delete_task_confirm") 884 + font.pointSize: Style.fontSizeM; font.weight: Font.Bold; color: Color.mOnSurface 885 + } 886 + NText { text: todoDetailColumn.todo.summary || ""; font.pointSize: Style.fontSizeS; color: Color.mOnSurfaceVariant } 887 + 888 + RowLayout { 889 + Layout.fillWidth: true; spacing: Style.marginS 890 + Item { Layout.fillWidth: true } 891 + Rectangle { 892 + Layout.preferredWidth: confirmDeleteTodoBtn.implicitWidth + 2 * Style.marginM 893 + Layout.preferredHeight: confirmDeleteTodoBtn.implicitHeight + Style.marginS 894 + color: Color.mError; radius: Style.radiusS 895 + NText { id: confirmDeleteTodoBtn; anchors.centerIn: parent; text: pluginApi.tr("panel.delete"); color: Color.mOnError; font.weight: Font.Bold } 896 + MouseArea { 897 + anchors.fill: parent; cursorShape: Qt.PointingHandCursor 898 + onClicked: { 899 + var t = todoDetailColumn.todo 900 + mainInstance?.deleteTodo(t.calendarUid, t.todoUid) 901 + showTodoDetailDialog = false; showTodoDeleteConfirmation = false; todoDetailEditMode = false 902 + } 903 + } 904 + } 905 + Rectangle { 906 + Layout.preferredWidth: cancelDeleteTodoBtn.implicitWidth + 2 * Style.marginM 907 + Layout.preferredHeight: cancelDeleteTodoBtn.implicitHeight + Style.marginS 908 + color: Color.mSurfaceVariant; radius: Style.radiusS 909 + NText { id: cancelDeleteTodoBtn; anchors.centerIn: parent; text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant } 910 + MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: showTodoDeleteConfirmation = false } 911 + } 912 + } 913 + } 914 + 915 + // Edit mode 916 + property int editTodoPriority: 0 917 + ColumnLayout { 918 + visible: todoDetailEditMode && !showTodoDeleteConfirmation 919 + spacing: Style.marginS 920 + Layout.fillWidth: true 921 + 922 + NText { text: pluginApi.tr("panel.edit_task"); font.pointSize: Style.fontSizeL; font.weight: Font.Bold; color: Color.mOnSurface } 923 + 924 + NText { text: pluginApi.tr("panel.task_summary"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 925 + TextField { 926 + id: editTodoSummary; Layout.fillWidth: true; color: Color.mOnSurface 927 + background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 928 + } 929 + 930 + NText { text: pluginApi.tr("panel.due_date"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 931 + TextField { 932 + id: editTodoDueDate; Layout.fillWidth: true; placeholderText: "YYYY-MM-DD"; color: Color.mOnSurface 933 + background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 934 + } 935 + 936 + NText { text: pluginApi.tr("panel.end_time"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 937 + TextField { 938 + id: editTodoDueTime; Layout.fillWidth: true; placeholderText: "HH:MM"; color: Color.mOnSurface 939 + background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 940 + } 941 + 942 + NText { text: pluginApi.tr("panel.description"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 943 + TextField { 944 + id: editTodoDescription; Layout.fillWidth: true; color: Color.mOnSurface 945 + background: Rectangle { color: Color.mSurfaceVariant; radius: Style.radiusS } 946 + } 947 + 948 + NText { text: pluginApi.tr("panel.priority"); color: Color.mOnSurfaceVariant; font.pointSize: Style.fontSizeS } 949 + RowLayout { 950 + spacing: Style.marginS 951 + Repeater { 952 + model: [ 953 + { label: pluginApi.tr("panel.priority_high"), value: 1 }, 954 + { label: pluginApi.tr("panel.priority_medium"), value: 5 }, 955 + { label: pluginApi.tr("panel.priority_low"), value: 9 } 956 + ] 957 + Rectangle { 958 + Layout.preferredWidth: editPriLabel.implicitWidth + 2 * Style.marginM 959 + Layout.preferredHeight: editPriLabel.implicitHeight + Style.marginS 960 + color: todoDetailColumn.editTodoPriority === modelData.value ? Color.mPrimary : Color.mSurfaceVariant 961 + radius: Style.radiusS 962 + NText { 963 + id: editPriLabel; anchors.centerIn: parent 964 + text: modelData.label 965 + color: todoDetailColumn.editTodoPriority === modelData.value ? Color.mOnPrimary : Color.mOnSurfaceVariant 966 + font.weight: Font.Medium 967 + } 968 + MouseArea { 969 + anchors.fill: parent; cursorShape: Qt.PointingHandCursor 970 + onClicked: todoDetailColumn.editTodoPriority = 971 + todoDetailColumn.editTodoPriority === modelData.value ? 0 : modelData.value 972 + } 973 + } 974 + } 975 + } 976 + 977 + RowLayout { 978 + Layout.fillWidth: true; spacing: Style.marginS 979 + Item { Layout.fillWidth: true } 980 + Rectangle { 981 + Layout.preferredWidth: saveTodoBtn.implicitWidth + 2 * Style.marginM 982 + Layout.preferredHeight: saveTodoBtn.implicitHeight + Style.marginS 983 + color: Color.mPrimary; radius: Style.radiusS 984 + NText { id: saveTodoBtn; anchors.centerIn: parent; text: pluginApi.tr("panel.save"); color: Color.mOnPrimary; font.weight: Font.Bold } 985 + MouseArea { 986 + anchors.fill: parent; cursorShape: Qt.PointingHandCursor 987 + onClicked: { 988 + var t = todoDetailColumn.todo 989 + var dueTs = 0 990 + if (editTodoDueDate.text.trim() !== "") { 991 + var dateParts = editTodoDueDate.text.split("-") 992 + var timeParts = editTodoDueTime.text.split(":") 993 + var h = editTodoDueTime.text.trim() === "" ? 0 : parseInt(timeParts[0]) 994 + var m = editTodoDueTime.text.trim() === "" ? 0 : parseInt(timeParts[1] || "0") 995 + var d = new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2]), h, m, 0) 996 + if (!isNaN(d.getTime())) dueTs = Math.floor(d.getTime() / 1000) 997 + } 998 + mainInstance?.updateTodoFields( 999 + t.calendarUid, t.todoUid, 1000 + editTodoSummary.text.trim(), 1001 + editTodoDescription.text.trim(), 1002 + dueTs, todoDetailColumn.editTodoPriority) 1003 + showTodoDetailDialog = false; todoDetailEditMode = false 1004 + } 1005 + } 1006 + } 1007 + Rectangle { 1008 + Layout.preferredWidth: editTodoCancelBtn.implicitWidth + 2 * Style.marginM 1009 + Layout.preferredHeight: editTodoCancelBtn.implicitHeight + Style.marginS 1010 + color: Color.mSurfaceVariant; radius: Style.radiusS 1011 + NText { id: editTodoCancelBtn; anchors.centerIn: parent; text: pluginApi.tr("panel.cancel"); color: Color.mOnSurfaceVariant } 1012 + MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: todoDetailEditMode = false } 1013 + } 1014 + } 1015 + } 1016 + } 1017 + } 1018 + } 1019 + 714 1020 // UI 715 1021 Rectangle { 716 1022 id: panelContainer ··· 950 1256 } 951 1257 onClicked: { 952 1258 if (isTodoItem) { 953 - if (eventData.todoStatus === "COMPLETED") 954 - mainInstance?.uncompleteTodo(eventData.calendarUid, eventData.todoUid) 955 - else 956 - mainInstance?.completeTodo(eventData.calendarUid, eventData.todoUid) 1259 + mainInstance?.handleTodoClick(eventData) 1260 + showTodoDetailDialog = true 1261 + todoDetailEditMode = false 1262 + showTodoDeleteConfirmation = false 957 1263 } else { 958 1264 mainInstance?.handleEventClick(eventData) 959 1265 showEventDetailDialog = true ··· 1103 1409 property real eventXOffset: overlapInfo.xOffset 1104 1410 1105 1411 property bool isTodoItem: model.isTodo || false 1106 - property bool isDeadline: model.isDeadlineMarker || false 1412 + property bool isTodoLine: isTodoItem && (model.isDeadlineMarker || model.isLineTodo || false) 1413 + property bool isDeadline: (model.isDeadlineMarker || false) && !isTodoLine 1107 1414 property color eventColor: isDeadline ? Color.mSecondary : (isTodoItem ? Color.mSecondary : Color.mPrimary) 1108 1415 property color eventTextColor: isDeadline ? Color.mOnSecondary : (isTodoItem ? Color.mOnSecondary : Color.mOnPrimary) 1416 + property real todoLineThickness: Math.max(2, 2 * Style.uiScaleRatio) 1417 + property real todoLabelGutter: { 1418 + var available = Math.max(eventWidth - 12, 0) 1419 + var base = Math.min(Math.max(eventWidth * 0.5, 80 * Style.uiScaleRatio), 150 * Style.uiScaleRatio) 1420 + var gutter = Math.min(base, available) 1421 + if (gutter <= 0 && available > 0) gutter = Math.min(available, 40 * Style.uiScaleRatio) 1422 + return gutter 1423 + } 1109 1424 1110 1425 visible: dayIndex >= 0 && dayIndex < 7 && duration > 0 1111 1426 width: eventWidth 1112 - height: isDeadline ? Math.max(8, Math.min(12, exactHeight)) : exactHeight 1427 + height: isTodoLine ? Math.max(todoLineThickness + 10, 18 * Style.uiScaleRatio) 1428 + : (isDeadline ? Math.max(8, Math.min(12, exactHeight)) : exactHeight) 1113 1429 x: dayIndex * ((mainInstance?.dayColumnWidth) + (root.daySpacing)) + eventXOffset 1114 1430 y: startHour * (root.hourHeight) 1115 - z: 100 + overlapInfo.lane 1431 + z: (isTodoLine ? 300 : 100) + overlapInfo.lane 1116 1432 1117 - Rectangle { 1433 + Item { 1118 1434 anchors.fill: parent 1119 - color: eventColor 1120 - radius: Style.radiusS 1121 - opacity: isDeadline ? 0.95 : (isTodoItem && model.todoStatus === "COMPLETED" ? 0.5 : 0.9) 1122 - clip: true 1435 + clip: false 1436 + 1123 1437 Rectangle { 1124 - visible: exactHeight < 5 && overlapInfo.lane > 0 1125 1438 anchors.fill: parent 1126 - color: "transparent" 1127 - radius: parent.radius 1128 - border.width: 1 1129 - border.color: eventColor 1439 + visible: !isTodoLine 1440 + color: eventColor 1441 + radius: Style.radiusS 1442 + opacity: isDeadline ? 0.95 : (isTodoItem && model.todoStatus === "COMPLETED" ? 0.5 : 0.9) 1443 + clip: true 1444 + Rectangle { 1445 + visible: exactHeight < 5 && overlapInfo.lane > 0 1446 + anchors.fill: parent 1447 + color: "transparent" 1448 + radius: parent.radius 1449 + border.width: 1 1450 + border.color: eventColor 1451 + } 1130 1452 } 1453 + 1131 1454 Loader { 1132 1455 anchors.fill: parent 1133 - anchors.margins: exactHeight < 10 ? 1 : Style.marginS 1134 - anchors.leftMargin: exactHeight < 10 ? 1 : Style.marginS + 3 1135 - sourceComponent: isDeadline ? deadlineLayout : (isCompact ? compactLayout : normalLayout) 1456 + anchors.margins: isTodoLine ? 0 : (exactHeight < 10 ? 1 : Style.marginS) 1457 + anchors.leftMargin: isTodoLine ? 0 : (exactHeight < 10 ? 1 : Style.marginS + 3) 1458 + sourceComponent: isTodoLine ? todoLineLayout : (isDeadline ? deadlineLayout : (isCompact ? compactLayout : normalLayout)) 1136 1459 } 1137 1460 } 1138 1461 ··· 1185 1508 } 1186 1509 1187 1510 Component { 1511 + id: todoLineLayout 1512 + Item { 1513 + anchors.fill: parent 1514 + clip: false 1515 + 1516 + Rectangle { 1517 + id: line 1518 + anchors.left: parent.left 1519 + anchors.right: parent.right 1520 + anchors.rightMargin: todoLabelGutter 1521 + anchors.verticalCenter: parent.verticalCenter 1522 + height: todoLineThickness 1523 + radius: todoLineThickness / 2 1524 + color: eventColor 1525 + opacity: model.todoStatus === "COMPLETED" ? 0.45 : 1.0 1526 + } 1527 + 1528 + Rectangle { 1529 + id: leader 1530 + width: 12 * Style.uiScaleRatio 1531 + height: todoLineThickness 1532 + anchors.left: line.right 1533 + anchors.verticalCenter: line.verticalCenter 1534 + color: eventColor 1535 + opacity: line.opacity 1536 + } 1537 + 1538 + Rectangle { 1539 + id: labelBox 1540 + anchors.left: leader.right 1541 + anchors.verticalCenter: line.verticalCenter 1542 + width: Math.max(todoLabelGutter - leader.width, 70 * Style.uiScaleRatio) 1543 + height: Math.max(todoLineThickness + Style.marginS, 18 * Style.uiScaleRatio) 1544 + radius: Style.radiusS 1545 + color: Color.mSurface 1546 + border.color: Qt.alpha(eventColor, 0.9) 1547 + border.width: 1 1548 + NText { 1549 + anchors.fill: parent 1550 + anchors.margins: Style.marginS / 2 1551 + text: (model.todoStatus === "COMPLETED" ? "\u2611 " : "\u2610 ") + (model.title || "") 1552 + color: eventColor 1553 + font.pointSize: Style.fontSizeXXS 1554 + font.weight: Font.Medium 1555 + font.strikeout: model.todoStatus === "COMPLETED" 1556 + elide: Text.ElideRight 1557 + verticalAlignment: Text.AlignVCenter 1558 + } 1559 + } 1560 + } 1561 + } 1562 + 1563 + Component { 1188 1564 id: deadlineLayout 1189 1565 Rectangle { 1190 1566 anchors.fill: parent ··· 1204 1580 } 1205 1581 onClicked: { 1206 1582 if (isTodoItem) { 1207 - if (model.todoStatus === "COMPLETED") 1208 - mainInstance?.uncompleteTodo(model.calendarUid, model.todoUid) 1209 - else 1210 - mainInstance?.completeTodo(model.calendarUid, model.todoUid) 1583 + mainInstance?.handleTodoClick(model) 1584 + showTodoDetailDialog = true 1585 + todoDetailEditMode = false 1586 + showTodoDeleteConfirmation = false 1211 1587 } else { 1212 1588 mainInstance?.handleEventClick(eventData) 1213 1589 showEventDetailDialog = true
+9 -1
weekly-calendar/i18n/en.json
··· 39 39 "task_created": "Task created", 40 40 "task_error": "Failed to create task", 41 41 "show_completed": "Show completed", 42 - "overdue": "Overdue" 42 + "overdue": "Overdue", 43 + "edit": "Edit", 44 + "delete": "Delete", 45 + "close": "Close", 46 + "save": "Save", 47 + "edit_event": "Edit Event", 48 + "edit_task": "Edit Task", 49 + "delete_confirm": "Delete this event?", 50 + "delete_task_confirm": "Delete this task?" 43 51 }, 44 52 "settings": { 45 53 "weekStart": "First day of week",
+9 -1
weekly-calendar/i18n/zh-CN.json
··· 39 39 "task_created": "任务已创建", 40 40 "task_error": "创建任务失败", 41 41 "show_completed": "显示已完成", 42 - "overdue": "已逾期" 42 + "overdue": "已逾期", 43 + "edit": "编辑", 44 + "delete": "删除", 45 + "close": "关闭", 46 + "save": "保存", 47 + "edit_event": "编辑活动", 48 + "edit_task": "编辑任务", 49 + "delete_confirm": "确定删除此活动?", 50 + "delete_task_confirm": "确定删除此任务?" 43 51 }, 44 52 "settings": { 45 53 "weekStart": "一周起始日",
+24 -1
weekly-calendar/scripts/update-todo.py
··· 35 35 parser = argparse.ArgumentParser(description="Update/delete EDS VTODO item") 36 36 parser.add_argument("--task-list", required=True, help="Task list UID") 37 37 parser.add_argument("--uid", required=True, help="VTODO UID") 38 - parser.add_argument("--action", required=True, choices=["complete", "uncomplete", "delete"], 38 + parser.add_argument("--action", required=True, choices=["complete", "uncomplete", "delete", "update"], 39 39 help="Action to perform") 40 + parser.add_argument("--summary", help="New task summary (for update)") 41 + parser.add_argument("--description", help="New task description (for update)") 42 + parser.add_argument("--due", type=int, help="New due date as unix timestamp (for update)") 43 + parser.add_argument("--priority", type=int, help="New priority 0-9 (for update)") 40 44 args = parser.parse_args() 41 45 42 46 try: ··· 91 95 92 96 # Remove COMPLETED timestamp 93 97 remove_property(ical, ICalGLib.PropertyKind.COMPLETED_PROPERTY) 98 + 99 + elif args.action == "update": 100 + if args.summary is not None: 101 + ical.set_summary(args.summary) 102 + if args.description is not None: 103 + ical.set_description(args.description) 104 + if args.due is not None: 105 + dt = datetime.fromtimestamp(args.due, tz=timezone.utc) 106 + t = ICalGLib.Time.new_null_time() 107 + t.set_date(dt.year, dt.month, dt.day) 108 + t.set_time(dt.hour, dt.minute, dt.second) 109 + t.set_timezone(ICalGLib.Timezone.get_utc_timezone()) 110 + remove_property(ical, ICalGLib.PropertyKind.DUE_PROPERTY) 111 + prop = ICalGLib.Property.new_due(t) 112 + ical.add_property(prop) 113 + if args.priority is not None: 114 + remove_property(ical, ICalGLib.PropertyKind.PRIORITY_PROPERTY) 115 + prop = ICalGLib.Property.new_priority(args.priority) 116 + ical.add_property(prop) 94 117 95 118 client.modify_object_sync(comp, ECal.ObjModType.ALL, ECal.OperationFlags.NONE, None) 96 119 print(json.dumps({"success": True}))