1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-16 16:15:23 -04:00
Files
DankMaterialShell/quickshell/Modules/DankDash/Overview/CalendarEventDetail.qml
T
bbedward 59998e9fd2 calendar(dank): Add support for DankCalendar backend
- Add keyboard navigation to overview
- Add edit events to overview
- Add create events to overview
- Add setting for auto/khal/dankcalendar backend selection
2026-06-15 14:02:35 -04:00

312 lines
11 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property var eventData: null
property bool canEdit: false
signal editRequested
signal deleteRequested
signal closeRequested
readonly property bool _descriptionIsHtml: /<[a-z][^>]*>/i.test((eventData && eventData.description) || "")
function _styleAnchors(html) {
return html.replace(/<a\s([^>]*)>/gi, (m, attrs) => {
const cleaned = attrs.replace(/style="[^"]*"/gi, "");
return "<a style=\"text-decoration:none; color:" + Theme.primary + ";\" " + cleaned + ">";
});
}
function _inlineMarkdown(line) {
let out = line.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
out = out.replace(/\\([\\`*_{}[\]()#+\-.!~>])/g, "$1");
out = out.replace(/(?:https?:\/\/|www\.)[^\s<>)\]]*[^\s<>)\].,;:!?"']/g, (m, offset, s) => {
const prev = offset > 0 ? s[offset - 1] : "";
if (prev === "(" || prev === "[" || prev === "\"" || prev === "'")
return m;
const href = m.startsWith("www.") ? "https://" + m : m;
return "<a href=\"" + href + "\">" + m + "</a>";
});
out = out.replace(/\[([^\]]+)\]\(([^()\s]+)\)/g, "<a href=\"$2\">$1</a>");
out = out.replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>");
out = out.replace(/(^|[^*])\*([^*\s][^*]*)\*/g, "$1<i>$2</i>");
return out;
}
// Descriptions arrive as HTML (Google) or markdown/plain text; both render
// as RichText so links become clickable anchors recolored to the theme.
function _descriptionRichText() {
const raw = ((eventData && eventData.description) || "").trim();
if (raw === "")
return "";
if (_descriptionIsHtml)
return _styleAnchors(raw);
const parts = [];
let list = "";
const closeList = () => {
if (list === "")
return;
parts.push("</" + list + ">");
list = "";
};
const lines = raw.split("\n");
for (let i = 0; i < lines.length; i++) {
const ul = lines[i].match(/^\s*[-*+]\s+(.+)$/);
const ol = lines[i].match(/^\s*\d+[.)]\s+(.+)$/);
if (ul || ol) {
const tag = ul ? "ul" : "ol";
if (list !== tag) {
closeList();
parts.push("<" + tag + ">");
list = tag;
}
parts.push("<li>" + _inlineMarkdown((ul || ol)[1]) + "</li>");
continue;
}
closeList();
parts.push(_inlineMarkdown(lines[i]) + "<br/>");
}
closeList();
return _styleAnchors(parts.join("").replace(/<br\/>$/, ""));
}
function _timeText() {
if (!eventData)
return "";
const dateStr = Qt.formatDate(eventData.start, "ddd, MMM d");
if (eventData.allDay)
return I18n.tr("All day") + " · " + dateStr;
const fmt = SettingsData.use24HourClock ? "HH:mm" : "h:mm AP";
const startStr = Qt.formatTime(eventData.start, fmt);
if (eventData.start.getTime() === eventData.end.getTime())
return dateStr + " · " + startStr;
return dateStr + " · " + startStr + " " + Qt.formatTime(eventData.end, fmt);
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Qt.rgba(0, 0, 0, 0.45)
MouseArea {
anchors.fill: parent
onClicked: root.closeRequested()
}
}
Rectangle {
anchors.centerIn: parent
width: Math.min(parent.width - Theme.spacingL * 2, 380)
height: Math.min(parent.height - Theme.spacingM * 2, body.implicitHeight + Theme.spacingL * 2)
radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh
border.color: Theme.outlineMedium
border.width: 1
clip: true
MouseArea {
anchors.fill: parent
}
DankActionButton {
id: closeButton
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Theme.spacingXS
circular: false
iconName: "close"
iconSize: 16
z: 1
onClicked: root.closeRequested()
}
DankFlickable {
anchors.fill: parent
anchors.margins: Theme.spacingL
anchors.topMargin: Theme.spacingL
contentWidth: width
contentHeight: body.implicitHeight
clip: true
Column {
id: body
width: parent.width
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
Rectangle {
width: 4
height: titleText.implicitHeight
radius: 2
anchors.top: parent.top
color: (root.eventData && root.eventData.color) ? root.eventData.color : Theme.primary
}
StyledText {
id: titleText
width: parent.width - 4 - Theme.spacingS - closeButton.width
text: root.eventData ? root.eventData.title : ""
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
horizontalAlignment: Text.AlignLeft
wrapMode: Text.Wrap
maximumLineCount: 3
elide: Text.ElideRight
}
}
StyledText {
width: parent.width
text: root._timeText()
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.Wrap
}
Row {
width: parent.width
spacing: Theme.spacingXS
visible: root.eventData && root.eventData.calendar
DankIcon {
name: "calendar_month"
size: 14
color: Theme.surfaceVariantText
anchors.top: parent.top
anchors.topMargin: 2
}
StyledText {
width: parent.width - 14 - Theme.spacingXS
text: {
if (!root.eventData)
return "";
const acc = root.eventData.account || "";
return root.eventData.calendar + (acc ? " · " + acc : "");
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.Wrap
maximumLineCount: 2
elide: Text.ElideRight
}
}
Row {
width: parent.width
spacing: Theme.spacingXS
visible: root.eventData && root.eventData.location
DankIcon {
name: "place"
size: 14
color: Theme.surfaceVariantText
anchors.top: parent.top
anchors.topMargin: 2
}
StyledText {
width: parent.width - 14 - Theme.spacingXS
text: root.eventData ? root.eventData.location : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.Wrap
maximumLineCount: 2
elide: Text.ElideRight
}
}
Row {
width: parent.width
spacing: Theme.spacingXS
visible: root.eventData && root.eventData.url
DankIcon {
name: "link"
size: 14
color: Theme.primary
anchors.top: parent.top
anchors.topMargin: 2
}
StyledText {
width: parent.width - 14 - Theme.spacingXS
text: root.eventData ? root.eventData.url : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
wrapMode: Text.WrapAnywhere
maximumLineCount: 2
elide: Text.ElideRight
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.eventData && root.eventData.url)
Qt.openUrlExternally(root.eventData.url);
}
}
}
}
StyledText {
id: descriptionText
width: parent.width
text: root._descriptionRichText()
visible: root.eventData && root.eventData.description
textFormat: Text.RichText
linkColor: Theme.primary
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
horizontalAlignment: Text.AlignLeft
wrapMode: Text.Wrap
onLinkActivated: link => Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: descriptionText.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
Row {
width: parent.width
spacing: Theme.spacingS
visible: root.canEdit
topPadding: Theme.spacingXS
DankButton {
text: I18n.tr("Edit")
iconName: "edit"
buttonHeight: 32
onClicked: root.editRequested()
}
DankButton {
text: I18n.tr("Delete")
iconName: "delete"
buttonHeight: 32
backgroundColor: Theme.withAlpha(Theme.error, 0.15)
textColor: Theme.error
onClicked: root.deleteRequested()
}
}
}
}
}
}