mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-30 01:22:06 -04:00
launcher: add elide helpers for RichText
This commit is contained in:
93
quickshell/Common/htmlElide.js
Normal file
93
quickshell/Common/htmlElide.js
Normal file
@@ -0,0 +1,93 @@
|
||||
.pragma library
|
||||
|
||||
function stripHtmlTags(html) {
|
||||
if (!html)
|
||||
return "";
|
||||
return String(html)
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, "\"")
|
||||
.replace(/'/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 += "&";
|
||||
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;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import "../../Common/htmlElide.js" as HtmlElide
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -72,131 +73,159 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
AppIconRenderer {
|
||||
id: iconRenderer
|
||||
width: 36
|
||||
height: 36
|
||||
anchors.left: parent.left
|
||||
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
|
||||
spacing: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
AppIconRenderer {
|
||||
width: 36
|
||||
height: 36
|
||||
Rectangle {
|
||||
id: allModeToggle
|
||||
visible: root.item?.type === "plugin_browse"
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
iconValue: root.iconValue
|
||||
iconSize: 36
|
||||
fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?"
|
||||
materialIconSizeAdjustment: 12
|
||||
}
|
||||
color: allModeToggleArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
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
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
property bool isAllowed: {
|
||||
if (root.item?.type !== "plugin_browse")
|
||||
return false;
|
||||
var pluginId = root.item?.data?.pluginId;
|
||||
if (!pluginId)
|
||||
return false;
|
||||
SettingsData.launcherPluginVisibility;
|
||||
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.item?._hSub ?? root.item?.subtitle ?? ""
|
||||
textFormat: root.item?._hRich ? Text.RichText : Text.PlainText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.fontFamily
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
clip: true
|
||||
visible: (root.item?.subtitle ?? "").length > 0
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: allModeToggle.isAllowed ? "visibility" : "visibility_off"
|
||||
size: 18
|
||||
color: allModeToggle.isAllowed ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightContent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Rectangle {
|
||||
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;
|
||||
MouseArea {
|
||||
id: allModeToggleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
var pluginId = root.item?.data?.pluginId;
|
||||
if (!pluginId)
|
||||
return false;
|
||||
SettingsData.launcherPluginVisibility;
|
||||
return SettingsData.getPluginAllowWithoutTrigger(pluginId);
|
||||
return;
|
||||
SettingsData.setPluginAllowWithoutTrigger(pluginId, !allModeToggle.isAllowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: allModeToggle.isAllowed ? "visibility" : "visibility_off"
|
||||
size: 18
|
||||
color: allModeToggle.isAllowed ? Theme.primary : Theme.surfaceVariantText
|
||||
}
|
||||
Rectangle {
|
||||
visible: !!root.item?.type && root.item.type !== "app" && root.item.type !== "plugin_browse"
|
||||
width: typeBadge.implicitWidth + Theme.spacingS * 2
|
||||
height: 20
|
||||
radius: 10
|
||||
color: Theme.surfaceVariantAlpha
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
id: allModeToggleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
var pluginId = root.item?.data?.pluginId;
|
||||
if (!pluginId)
|
||||
return;
|
||||
SettingsData.setPluginAllowWithoutTrigger(pluginId, !allModeToggle.isAllowed);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: !!root.item?.type && root.item.type !== "app" && root.item.type !== "plugin_browse"
|
||||
width: typeBadge.implicitWidth + Theme.spacingS * 2
|
||||
height: 20
|
||||
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
|
||||
}
|
||||
SourceBadge {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: root.item?.type === "app" ? (root.item.source || "") : ""
|
||||
glyphSize: 14
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user