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:
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 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user