1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-02 02:22:06 -04:00

launcher: add elide helpers for RichText

This commit is contained in:
bbedward
2026-04-21 15:18:41 -04:00
parent cf382c0322
commit c6ed64b24e
2 changed files with 229 additions and 107 deletions

View File

@@ -0,0 +1,93 @@
.pragma library
function stripHtmlTags(html) {
if (!html)
return "";
return String(html)
.replace(/<[^>]+>/g, "")
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, "\"")
.replace(/&#039;/g, "'");
}
function elideRichText(html, visibleBudget) {
if (!html)
return "";
if (visibleBudget <= 0)
return "";
var out = "";
var visible = 0;
var i = 0;
var openTags = [];
var len = html.length;
while (i < len && visible < visibleBudget) {
var ch = html.charAt(i);
if (ch === "<") {
var end = html.indexOf(">", i);
if (end < 0)
break;
var tag = html.substring(i, end + 1);
out += tag;
var isClose = tag.charAt(1) === "/";
var match = tag.match(/^<\/?([a-zA-Z]+)/);
var name = match ? match[1] : "";
if (isClose) {
if (openTags.length > 0 && openTags[openTags.length - 1] === name)
openTags.pop();
} else if (!tag.endsWith("/>") && name) {
openTags.push(name);
}
i = end + 1;
} else if (ch === "&") {
var eend = html.indexOf(";", i);
if (eend < 0 || eend - i > 6) {
out += "&amp;";
visible++;
i++;
} else {
out += html.substring(i, eend + 1);
visible++;
i = eend + 1;
}
} else {
out += ch;
visible++;
i++;
}
}
while (i < len && html.charAt(i) === "<") {
var tend = html.indexOf(">", i);
if (tend < 0)
break;
var ttag = html.substring(i, tend + 1);
out += ttag;
var tisClose = ttag.charAt(1) === "/";
var tmatch = ttag.match(/^<\/?([a-zA-Z]+)/);
var tname = tmatch ? tmatch[1] : "";
if (tisClose) {
if (openTags.length > 0 && openTags[openTags.length - 1] === tname)
openTags.pop();
} else if (!ttag.endsWith("/>") && tname) {
openTags.push(tname);
}
i = tend + 1;
}
if (i < len) {
out = out.replace(/\s+$/, "");
while (openTags.length > 0)
out += "</" + openTags.pop() + ">";
out += "…";
} else {
while (openTags.length > 0)
out += "</" + openTags.pop() + ">";
}
return out;
}

View File

@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
import "../../Common/htmlElide.js" as HtmlElide
Rectangle { Rectangle {
id: root id: root
@@ -72,131 +73,159 @@ Rectangle {
} }
} }
Row { AppIconRenderer {
anchors.fill: parent id: iconRenderer
width: 36
height: 36
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
iconValue: root.iconValue
iconSize: 36
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
materialIconSizeAdjustment: 12
}
Item {
id: textColumn
anchors.left: iconRenderer.right
anchors.leftMargin: Theme.spacingM
anchors.right: rightContent.left
anchors.rightMargin: rightContent.width > 0 ? Theme.spacingM : 0
anchors.verticalCenter: parent.verticalCenter
height: nameText.implicitHeight + (subText.visible ? subText.height + 2 : 0)
Text {
id: nameText
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text: root.item?._hName ?? root.item?.name ?? ""
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
font.family: Theme.fontFamily
color: Theme.surfaceText
wrapMode: Text.WordWrap
maximumLineCount: 1
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
}
TextMetrics {
id: subProbe
font.pixelSize: Theme.fontSizeSmall
font.family: Theme.fontFamily
elide: Qt.ElideRight
elideWidth: textColumn.width
text: root.item?._hRich ? HtmlElide.stripHtmlTags(root.item?._hSub ?? "") : ""
}
readonly property int _richBudget: {
if (!subProbe.text)
return 0;
var e = subProbe.elidedText;
return e.endsWith("…") ? e.length - 1 : e.length;
}
Text {
id: subText
anchors.left: parent.left
anchors.right: parent.right
anchors.top: nameText.bottom
anchors.topMargin: 2
text: root.item?._hRich ? HtmlElide.elideRichText(root.item._hSub ?? "", textColumn._richBudget) : (root.item?.subtitle ?? "")
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeSmall
font.family: Theme.fontFamily
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
maximumLineCount: 1
elide: Text.ElideRight
visible: (root.item?.subtitle ?? "").length > 0
horizontalAlignment: Text.AlignLeft
}
}
Row {
id: rightContent
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingM anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
AppIconRenderer { Rectangle {
width: 36 id: allModeToggle
height: 36 visible: root.item?.type === "plugin_browse"
width: 28
height: 28
radius: 14
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
iconValue: root.iconValue color: allModeToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
iconSize: 36
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
materialIconSizeAdjustment: 12
}
Column { property bool isAllowed: {
anchors.verticalCenter: parent.verticalCenter if (root.item?.type !== "plugin_browse")
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width return false;
spacing: 2 var pluginId = root.item?.data?.pluginId;
if (!pluginId)
Text { return false;
width: parent.width SettingsData.launcherPluginVisibility;
text: root.item?._hName ?? root.item?.name ?? "" return SettingsData.getPluginAllowWithoutTrigger(pluginId);
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
font.family: Theme.fontFamily
color: Theme.surfaceText
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
} }
Text { DankIcon {
width: parent.width anchors.centerIn: parent
text: root.item?._hSub ?? root.item?.subtitle ?? "" name: allModeToggle.isAllowed ? "visibility" : "visibility_off"
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText size: 18
font.pixelSize: Theme.fontSizeSmall color: allModeToggle.isAllowed ? Theme.primary : Theme.surfaceVariantText
font.family: Theme.fontFamily
color: Theme.surfaceVariantText
elide: Text.ElideRight
clip: true
visible: (root.item?.subtitle ?? "").length > 0
horizontalAlignment: Text.AlignLeft
} }
}
Row { MouseArea {
id: rightContent id: allModeToggleArea
anchors.verticalCenter: parent.verticalCenter anchors.fill: parent
spacing: Theme.spacingS hoverEnabled: true
cursorShape: Qt.PointingHandCursor
Rectangle { onClicked: {
id: allModeToggle
visible: root.item?.type === "plugin_browse"
width: 28
height: 28
radius: 14
anchors.verticalCenter: parent.verticalCenter
color: allModeToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
property bool isAllowed: {
if (root.item?.type !== "plugin_browse")
return false;
var pluginId = root.item?.data?.pluginId; var pluginId = root.item?.data?.pluginId;
if (!pluginId) if (!pluginId)
return false; return;
SettingsData.launcherPluginVisibility; SettingsData.setPluginAllowWithoutTrigger(pluginId, !allModeToggle.isAllowed);
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
} }
}
}
DankIcon { Rectangle {
anchors.centerIn: parent visible: !!root.item?.type && root.item.type !== "app" && root.item.type !== "plugin_browse"
name: allModeToggle.isAllowed ? "visibility" : "visibility_off" width: typeBadge.implicitWidth + Theme.spacingS * 2
size: 18 height: 20
color: allModeToggle.isAllowed ? Theme.primary : Theme.surfaceVariantText radius: 10
} color: Theme.surfaceVariantAlpha
anchors.verticalCenter: parent.verticalCenter
MouseArea { StyledText {
id: allModeToggleArea id: typeBadge
anchors.fill: parent anchors.centerIn: parent
hoverEnabled: true text: {
cursorShape: Qt.PointingHandCursor if (!root.item)
onClicked: { return "";
var pluginId = root.item?.data?.pluginId; switch (root.item.type) {
if (!pluginId) case "plugin":
return; return I18n.tr("Plugin");
SettingsData.setPluginAllowWithoutTrigger(pluginId, !allModeToggle.isAllowed); case "file":
return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default:
return "";
} }
} }
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.surfaceVariantText
} }
}
Rectangle { SourceBadge {
visible: !!root.item?.type && root.item.type !== "app" && root.item.type !== "plugin_browse" anchors.verticalCenter: parent.verticalCenter
width: typeBadge.implicitWidth + Theme.spacingS * 2 source: root.item?.type === "app" ? (root.item.source || "") : ""
height: 20 glyphSize: 14
radius: 10
color: Theme.surfaceVariantAlpha
anchors.verticalCenter: parent.verticalCenter
StyledText {
id: typeBadge
anchors.centerIn: parent
text: {
if (!root.item)
return "";
switch (root.item.type) {
case "plugin":
return I18n.tr("Plugin");
case "file":
return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default:
return "";
}
}
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.surfaceVariantText
}
}
SourceBadge {
anchors.verticalCenter: parent.verticalCenter
source: root.item?.type === "app" ? (root.item.source || "") : ""
glyphSize: 14
}
} }
} }
} }