@conduction/nextcloud-vue 2.127.0 → 2.128.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +3 -113
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +12 -12
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +34 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +16 -8
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +153 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +169 -58
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTileWidget/CnTileWidget.vue.js +1 -1
- package/dist/esm/components/CnTileWidget/CnTileWidget.vue2.js +11 -0
- package/dist/esm/components/CnTileWidget/CnTileWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTileWidget/CnTileWidget.vue3.js +14 -4
- package/dist/esm/components/CnTileWidget/CnTileWidget.vue3.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/mixins/pageFilters.js +142 -0
- package/dist/esm/mixins/pageFilters.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +269 -74
- package/dist/esm/utils/actionLink.js +9 -3
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1176 -828
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +269 -74
- package/package.json +1 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +3 -113
- package/src/components/CnDataTable/CnDataTable.vue +40 -1
- package/src/components/CnDetailPage/CnDetailPage.md +2 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +237 -8
- package/src/components/CnTileWidget/CnTileWidget.vue +30 -2
- package/src/css/detail-page.css +42 -0
- package/src/css/dialog.css +13 -0
- package/src/css/look-board-detail.css +1 -1
- package/src/css/look-board-index.css +8 -0
- package/src/css/look-board.css +97 -0
- package/src/css/table.css +12 -0
- package/src/mixins/pageFilters.js +138 -0
- package/src/schemas/app-manifest-v2.schema.json +41 -3
- package/src/utils/actionLink.js +9 -3
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -2,7 +2,7 @@ import script from './CnTileWidget.vue2.js';
|
|
|
2
2
|
import { render } from './CnTileWidget.vue3.js';
|
|
3
3
|
|
|
4
4
|
script.render = render;
|
|
5
|
-
script.__scopeId = "data-v-
|
|
5
|
+
script.__scopeId = "data-v-a62ef4d8";
|
|
6
6
|
const __sfc_main = script;
|
|
7
7
|
|
|
8
8
|
export { __sfc_main as default };
|
|
@@ -56,6 +56,17 @@ var script = {
|
|
|
56
56
|
return this.tile.title ? fn(this.tile.title) : this.tile.title
|
|
57
57
|
},
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The optional line under the title (`tile.subtitle`), translated like
|
|
61
|
+
* the title. Empty when the tile names none.
|
|
62
|
+
*
|
|
63
|
+
* @return {string}
|
|
64
|
+
*/
|
|
65
|
+
resolvedSubtitle() {
|
|
66
|
+
const fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k;
|
|
67
|
+
return this.tile.subtitle ? fn(this.tile.subtitle) : ''
|
|
68
|
+
},
|
|
69
|
+
|
|
59
70
|
/**
|
|
60
71
|
* The anchor href. `route` tiles resolve through the host router so
|
|
61
72
|
* middle-click / ctrl-click open the correct full URL (including the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTileWidget.vue2.js","sources":["../../../../src/components/CnTileWidget/CnTileWidget.vue"],"sourcesContent":["<!--\n CnTileWidget — Quick-access tile with icon and link.\n\n A simple, colorful tile for app shortcuts or external links.\n Supports SVG paths, CSS icon classes, image URLs, and emoji icons.\n-->\n<template>\n\t<div\n\t\tv-if=\"tile\"\n\t\tclass=\"cn-tile-widget\"\n\t\t:class=\"{ 'cn-tile-widget--tinted': isTinted }\"\n\t\t:style=\"tileStyles\">\n\t\t<a\n\t\t\t:href=\"tileUrl\"\n\t\t\tclass=\"cn-tile-widget__link\"\n\t\t\t:target=\"tile.linkType === 'url' ? '_blank' : '_self'\"\n\t\t\trel=\"noopener noreferrer\"\n\t\t\t@click=\"onLinkClick\">\n\t\t\t<!-- SVG icon -->\n\t\t\t<svg\n\t\t\t\tv-if=\"tile.iconType === 'svg'\"\n\t\t\t\tclass=\"cn-tile-widget__icon cn-tile-widget__icon--svg\"\n\t\t\t\t:style=\"{ fill: resolvedTextColor }\"\n\t\t\t\tviewBox=\"0 0 24 24\">\n\t\t\t\t<path :d=\"tile.icon\" />\n\t\t\t</svg>\n\t\t\t<!-- Other icon types -->\n\t\t\t<div v-else class=\"cn-tile-widget__icon\">\n\t\t\t\t<!-- A registry key (`Trophy`, `ViewDashboard`, …) comes FIRST:\n\t\t\t\t with no icon catalogue wired, that is what CnIconBrowser\n\t\t\t\t emits, and the tile form files it under `class` because it\n\t\t\t\t is neither a URL nor an SVG path. Rendered as a CSS class it\n\t\t\t\t is a rule nobody wrote, so the tile showed no icon at all.\n\t\t\t\t `hasRegistryIcon` excludes `icon-*`, which really are\n\t\t\t\t Nextcloud CSS-class icons. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"registryIcon\"\n\t\t\t\t\tv-if=\"registryIcon\"\n\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t:fillColor=\"resolvedTextColor\" />\n\t\t\t\t<span v-else-if=\"tile.iconType === 'class'\" class=\"icon\" :class=\"[tile.icon]\" />\n\t\t\t\t<img v-else-if=\"tile.iconType === 'url'\" :src=\"tile.icon\" alt=\"\">\n\t\t\t\t<span v-else-if=\"tile.iconType === 'emoji'\" class=\"cn-tile-widget__emoji\">{{ tile.icon }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-tile-widget__title\" :style=\"{ color: resolvedTextColor }\">\n\t\t\t\t{{ resolvedTitle }}\n\t\t\t</div>\n\t\t</a>\n\t</div>\n</template>\n\n<script>\nimport { generateUrl } from '@nextcloud/router'\nimport { getIconComponent, hasRegistryIcon } from '../CnWidgetGrid/widgetIcons.js'\n\n/**\n * CnTileWidget — Quick-access tile with icon and link.\n *\n * ```vue\n * <CnTileWidget :tile=\"{\n * title: 'Files',\n * icon: 'M12,2C6.48,...',\n * iconType: 'svg',\n * backgroundColor: '#0082c9',\n * textColor: '#ffffff',\n * linkType: 'app',\n * linkValue: 'files',\n * }\" />\n * ```\n */\nexport default {\n\tname: 'CnTileWidget',\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored tile title is run through it. Defaults to an\n\t\t * identity function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Tile configuration object. `linkType` decides how `linkValue` is\n\t\t * followed: `app` builds a full-page `/apps/…` URL, `url` opens an\n\t\t * external link, and `route` pushes `linkValue` through the host\n\t\t * app's vue-router so SPA state (e.g. an in-memory vault key)\n\t\t * survives the navigation.\n\t\t *\n\t\t * @type {{ title: string, icon: string, iconType: 'svg'|'class'|'url'|'emoji', backgroundColor: string, textColor: string, linkType: 'app'|'url'|'route', linkValue: string }}\n\t\t */\n\t\ttile: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The tile caption run through the host translate function.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\treturn this.tile.title ? fn(this.tile.title) : this.tile.title\n\t\t},\n\n\t\t/**\n\t\t * The anchor href. `route` tiles resolve through the host router so\n\t\t * middle-click / ctrl-click open the correct full URL (including the\n\t\t * router base); plain clicks are intercepted by `onLinkClick` instead.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttileUrl() {\n\t\t\tif (this.tile.linkType === 'route') {\n\t\t\t\tif (this.$router) {\n\t\t\t\t\treturn this.$router.resolve(this.tile.linkValue || '/').href\n\t\t\t\t}\n\t\t\t\t// No router on the host page — degrade to a plain link.\n\t\t\t\treturn this.tile.linkValue || '#'\n\t\t\t}\n\t\t\tif (this.tile.linkType === 'app') {\n\t\t\t\treturn generateUrl('/apps/' + this.tile.linkValue)\n\t\t\t}\n\t\t\treturn this.tile.linkValue || '#'\n\t\t},\n\n\t\t/**\n\t\t * The icon component for a registry-key icon value, or null when the\n\t\t * value is something the other branches render.\n\t\t *\n\t\t * @return {object|null} the icon component.\n\t\t */\n\t\tregistryIcon() {\n\t\t\treturn hasRegistryIcon(this.tile.icon)\n\t\t\t\t? getIconComponent(this.tile.icon)\n\t\t\t\t: null\n\t\t},\n\n\t\t/**\n\t\t * Whether the author actually chose a background colour.\n\t\t *\n\t\t * @return {boolean} true when the tile is tinted.\n\t\t */\n\t\tisTinted() {\n\t\t\treturn Boolean(this.tile.backgroundColor)\n\t\t},\n\n\t\t/**\n\t\t * The text/icon colour. White on a tinted tile (any author-chosen\n\t\t * colour is assumed dark enough, as it always was); the ordinary text\n\t\t * colour otherwise, since an untinted tile sits on the page background.\n\t\t *\n\t\t * @return {string} a CSS colour.\n\t\t */\n\t\tresolvedTextColor() {\n\t\t\tif (this.tile.textColor) {\n\t\t\t\treturn this.tile.textColor\n\t\t\t}\n\t\t\treturn this.isTinted ? '#ffffff' : 'var(--color-main-text)'\n\t\t},\n\n\t\t/**\n\t\t * Tile colours. An untinted tile paints NO background: a tile whose\n\t\t * author never picked a colour should look like the surface it sits\n\t\t * on, not invent one — it used to impose a hardcoded Nextcloud blue.\n\t\t *\n\t\t * @return {object} the CSS custom properties.\n\t\t */\n\t\ttileStyles() {\n\t\t\treturn {\n\t\t\t\t'--cn-tile-bg': this.tile.backgroundColor || 'transparent',\n\t\t\t\t'--cn-tile-text': this.resolvedTextColor,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Push `route` tiles through the host router instead of letting the\n\t\t * anchor trigger a full page load — a full load tears down the SPA\n\t\t * (and with it any in-memory state such as an unlocked vault).\n\t\t * Mirrors RouterLink's guard: modified clicks (new tab/window) and\n\t\t * non-left buttons fall through to the resolved href.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonLinkClick(event) {\n\t\t\tif (this.tile.linkType !== 'route' || !this.$router) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.defaultPrevented\n\t\t\t\t|| (event.button !== undefined && event.button !== 0)\n\t\t\t\t|| event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\t// vue-router rejects the push promise on a blocked or duplicate\n\t\t\t// navigation; swallow it so it never surfaces as an unhandled rejection.\n\t\t\tthis.$router.push(this.tile.linkValue || '/').catch(() => {})\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tile-widget {\n\theight: 100%;\n\twidth: 100%;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\toverflow: hidden;\n\tbackground-color: var(--cn-tile-bg);\n}\n\n.cn-tile-widget__link {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\theight: 100%;\n\twidth: 100%;\n\ttext-decoration: none;\n\tpadding: 20px;\n\tgap: 12px;\n\ttransition: transform 0.2s ease, opacity 0.2s ease;\n\tbackground-color: var(--cn-tile-bg);\n\tcolor: var(--cn-tile-text);\n}\n\n.cn-tile-widget__link:hover {\n\ttransform: scale(1.02);\n\topacity: 0.95;\n}\n\n.cn-tile-widget__icon {\n\tfont-size: 48px;\n\twidth: 48px;\n\theight: 48px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n}\n\n.cn-tile-widget__icon--svg {\n\twidth: 48px;\n\theight: 48px;\n}\n\n.cn-tile-widget__icon span.icon {\n\tdisplay: inline-block;\n\twidth: 48px;\n\theight: 48px;\n\tbackground-size: 48px;\n}\n\n/* A CSS-class icon is a dark background-image, so it is inverted to white to\n read against the tint. An untinted tile sits on the page background, where\n white would be invisible in a light theme, so it keeps its own colour. */\n.cn-tile-widget--tinted .cn-tile-widget__icon span.icon {\n\tfilter: brightness(0) invert(1);\n}\n\n.cn-tile-widget__icon img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: contain;\n}\n\n.cn-tile-widget__emoji {\n\tfont-size: 48px;\n}\n\n.cn-tile-widget__title {\n\tfont-size: 16px;\n\tfont-weight: 700;\n\ttext-align: center;\n\toverflow-wrap: anywhere;\n\tline-height: 1.3;\n\tcolor: var(--cn-tile-text);\n}\n</style>\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnTileWidget.vue2.js","sources":["../../../../src/components/CnTileWidget/CnTileWidget.vue"],"sourcesContent":["<!--\n CnTileWidget — Quick-access tile with icon and link.\n\n A simple, colorful tile for app shortcuts or external links.\n Supports SVG paths, CSS icon classes, image URLs, and emoji icons.\n-->\n<template>\n\t<div\n\t\tv-if=\"tile\"\n\t\tclass=\"cn-tile-widget\"\n\t\t:class=\"{ 'cn-tile-widget--tinted': isTinted }\"\n\t\t:style=\"tileStyles\">\n\t\t<a\n\t\t\t:href=\"tileUrl\"\n\t\t\tclass=\"cn-tile-widget__link\"\n\t\t\t:target=\"tile.linkType === 'url' ? '_blank' : '_self'\"\n\t\t\trel=\"noopener noreferrer\"\n\t\t\t@click=\"onLinkClick\">\n\t\t\t<!-- SVG icon -->\n\t\t\t<svg\n\t\t\t\tv-if=\"tile.iconType === 'svg'\"\n\t\t\t\tclass=\"cn-tile-widget__icon cn-tile-widget__icon--svg\"\n\t\t\t\t:style=\"{ fill: resolvedTextColor }\"\n\t\t\t\tviewBox=\"0 0 24 24\">\n\t\t\t\t<path :d=\"tile.icon\" />\n\t\t\t</svg>\n\t\t\t<!-- Other icon types -->\n\t\t\t<div v-else class=\"cn-tile-widget__icon\">\n\t\t\t\t<!-- A registry key (`Trophy`, `ViewDashboard`, …) comes FIRST:\n\t\t\t\t with no icon catalogue wired, that is what CnIconBrowser\n\t\t\t\t emits, and the tile form files it under `class` because it\n\t\t\t\t is neither a URL nor an SVG path. Rendered as a CSS class it\n\t\t\t\t is a rule nobody wrote, so the tile showed no icon at all.\n\t\t\t\t `hasRegistryIcon` excludes `icon-*`, which really are\n\t\t\t\t Nextcloud CSS-class icons. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"registryIcon\"\n\t\t\t\t\tv-if=\"registryIcon\"\n\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t:fillColor=\"resolvedTextColor\" />\n\t\t\t\t<span v-else-if=\"tile.iconType === 'class'\" class=\"icon\" :class=\"[tile.icon]\" />\n\t\t\t\t<img v-else-if=\"tile.iconType === 'url'\" :src=\"tile.icon\" alt=\"\">\n\t\t\t\t<span v-else-if=\"tile.iconType === 'emoji'\" class=\"cn-tile-widget__emoji\">{{ tile.icon }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-tile-widget__text\">\n\t\t\t\t<div class=\"cn-tile-widget__title\" :style=\"{ color: resolvedTextColor }\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"resolvedSubtitle\" class=\"cn-tile-widget__subtitle\" :style=\"{ color: resolvedTextColor }\">\n\t\t\t\t\t{{ resolvedSubtitle }}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</a>\n\t</div>\n</template>\n\n<script>\nimport { generateUrl } from '@nextcloud/router'\nimport { getIconComponent, hasRegistryIcon } from '../CnWidgetGrid/widgetIcons.js'\n\n/**\n * CnTileWidget — Quick-access tile with icon and link.\n *\n * ```vue\n * <CnTileWidget :tile=\"{\n * title: 'Files',\n * icon: 'M12,2C6.48,...',\n * iconType: 'svg',\n * backgroundColor: '#0082c9',\n * textColor: '#ffffff',\n * linkType: 'app',\n * linkValue: 'files',\n * }\" />\n * ```\n */\nexport default {\n\tname: 'CnTileWidget',\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored tile title is run through it. Defaults to an\n\t\t * identity function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Tile configuration object. `linkType` decides how `linkValue` is\n\t\t * followed: `app` builds a full-page `/apps/…` URL, `url` opens an\n\t\t * external link, and `route` pushes `linkValue` through the host\n\t\t * app's vue-router so SPA state (e.g. an in-memory vault key)\n\t\t * survives the navigation.\n\t\t *\n\t\t * @type {{ title: string, icon: string, iconType: 'svg'|'class'|'url'|'emoji', backgroundColor: string, textColor: string, linkType: 'app'|'url'|'route', linkValue: string }}\n\t\t */\n\t\ttile: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The tile caption run through the host translate function.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\treturn this.tile.title ? fn(this.tile.title) : this.tile.title\n\t\t},\n\n\t\t/**\n\t\t * The optional line under the title (`tile.subtitle`), translated like\n\t\t * the title. Empty when the tile names none.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSubtitle() {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\treturn this.tile.subtitle ? fn(this.tile.subtitle) : ''\n\t\t},\n\n\t\t/**\n\t\t * The anchor href. `route` tiles resolve through the host router so\n\t\t * middle-click / ctrl-click open the correct full URL (including the\n\t\t * router base); plain clicks are intercepted by `onLinkClick` instead.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttileUrl() {\n\t\t\tif (this.tile.linkType === 'route') {\n\t\t\t\tif (this.$router) {\n\t\t\t\t\treturn this.$router.resolve(this.tile.linkValue || '/').href\n\t\t\t\t}\n\t\t\t\t// No router on the host page — degrade to a plain link.\n\t\t\t\treturn this.tile.linkValue || '#'\n\t\t\t}\n\t\t\tif (this.tile.linkType === 'app') {\n\t\t\t\treturn generateUrl('/apps/' + this.tile.linkValue)\n\t\t\t}\n\t\t\treturn this.tile.linkValue || '#'\n\t\t},\n\n\t\t/**\n\t\t * The icon component for a registry-key icon value, or null when the\n\t\t * value is something the other branches render.\n\t\t *\n\t\t * @return {object|null} the icon component.\n\t\t */\n\t\tregistryIcon() {\n\t\t\treturn hasRegistryIcon(this.tile.icon)\n\t\t\t\t? getIconComponent(this.tile.icon)\n\t\t\t\t: null\n\t\t},\n\n\t\t/**\n\t\t * Whether the author actually chose a background colour.\n\t\t *\n\t\t * @return {boolean} true when the tile is tinted.\n\t\t */\n\t\tisTinted() {\n\t\t\treturn Boolean(this.tile.backgroundColor)\n\t\t},\n\n\t\t/**\n\t\t * The text/icon colour. White on a tinted tile (any author-chosen\n\t\t * colour is assumed dark enough, as it always was); the ordinary text\n\t\t * colour otherwise, since an untinted tile sits on the page background.\n\t\t *\n\t\t * @return {string} a CSS colour.\n\t\t */\n\t\tresolvedTextColor() {\n\t\t\tif (this.tile.textColor) {\n\t\t\t\treturn this.tile.textColor\n\t\t\t}\n\t\t\treturn this.isTinted ? '#ffffff' : 'var(--color-main-text)'\n\t\t},\n\n\t\t/**\n\t\t * Tile colours. An untinted tile paints NO background: a tile whose\n\t\t * author never picked a colour should look like the surface it sits\n\t\t * on, not invent one — it used to impose a hardcoded Nextcloud blue.\n\t\t *\n\t\t * @return {object} the CSS custom properties.\n\t\t */\n\t\ttileStyles() {\n\t\t\treturn {\n\t\t\t\t'--cn-tile-bg': this.tile.backgroundColor || 'transparent',\n\t\t\t\t'--cn-tile-text': this.resolvedTextColor,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Push `route` tiles through the host router instead of letting the\n\t\t * anchor trigger a full page load — a full load tears down the SPA\n\t\t * (and with it any in-memory state such as an unlocked vault).\n\t\t * Mirrors RouterLink's guard: modified clicks (new tab/window) and\n\t\t * non-left buttons fall through to the resolved href.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonLinkClick(event) {\n\t\t\tif (this.tile.linkType !== 'route' || !this.$router) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.defaultPrevented\n\t\t\t\t|| (event.button !== undefined && event.button !== 0)\n\t\t\t\t|| event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\t// vue-router rejects the push promise on a blocked or duplicate\n\t\t\t// navigation; swallow it so it never surfaces as an unhandled rejection.\n\t\t\tthis.$router.push(this.tile.linkValue || '/').catch(() => {})\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tile-widget {\n\theight: 100%;\n\twidth: 100%;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\toverflow: hidden;\n\tbackground-color: var(--cn-tile-bg);\n}\n\n.cn-tile-widget__link {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\theight: 100%;\n\twidth: 100%;\n\ttext-decoration: none;\n\tpadding: 20px;\n\tgap: 12px;\n\ttransition: transform 0.2s ease, opacity 0.2s ease;\n\tbackground-color: var(--cn-tile-bg);\n\tcolor: var(--cn-tile-text);\n}\n\n.cn-tile-widget__link:hover {\n\ttransform: scale(1.02);\n\topacity: 0.95;\n}\n\n.cn-tile-widget__icon {\n\tfont-size: 48px;\n\twidth: 48px;\n\theight: 48px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n}\n\n.cn-tile-widget__icon--svg {\n\twidth: 48px;\n\theight: 48px;\n}\n\n.cn-tile-widget__icon span.icon {\n\tdisplay: inline-block;\n\twidth: 48px;\n\theight: 48px;\n\tbackground-size: 48px;\n}\n\n/* A CSS-class icon is a dark background-image, so it is inverted to white to\n read against the tint. An untinted tile sits on the page background, where\n white would be invisible in a light theme, so it keeps its own colour. */\n.cn-tile-widget--tinted .cn-tile-widget__icon span.icon {\n\tfilter: brightness(0) invert(1);\n}\n\n.cn-tile-widget__icon img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: contain;\n}\n\n.cn-tile-widget__emoji {\n\tfont-size: 48px;\n}\n\n/* The text block takes no box of its own outside the board look, so the\n title stays a direct flex item of the link. */\n.cn-tile-widget__text {\n\tdisplay: contents;\n}\n\n.cn-tile-widget__subtitle {\n\tfont-size: 13px;\n\ttext-align: center;\n\topacity: 0.85;\n}\n\n.cn-tile-widget__title {\n\tfont-size: 16px;\n\tfont-weight: 700;\n\ttext-align: center;\n\toverflow-wrap: anywhere;\n\tline-height: 1.3;\n\tcolor: var(--cn-tile-text);\n}\n</style>\n"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ const _hoisted_5 = {
|
|
|
11
11
|
key: 3,
|
|
12
12
|
class: "cn-tile-widget__emoji"
|
|
13
13
|
};
|
|
14
|
+
const _hoisted_6 = { class: "cn-tile-widget__text" };
|
|
14
15
|
|
|
15
16
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
16
17
|
return ($props.tile)
|
|
@@ -59,10 +60,19 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
59
60
|
? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString($props.tile.icon), 1 /* TEXT */))
|
|
60
61
|
: createCommentVNode("v-if", true)
|
|
61
62
|
])),
|
|
62
|
-
createElementVNode("div",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
createElementVNode("div", _hoisted_6, [
|
|
64
|
+
createElementVNode("div", {
|
|
65
|
+
class: "cn-tile-widget__title",
|
|
66
|
+
style: normalizeStyle({ color: $options.resolvedTextColor })
|
|
67
|
+
}, toDisplayString($options.resolvedTitle), 5 /* TEXT, STYLE */),
|
|
68
|
+
($options.resolvedSubtitle)
|
|
69
|
+
? (openBlock(), createElementBlock("div", {
|
|
70
|
+
key: 0,
|
|
71
|
+
class: "cn-tile-widget__subtitle",
|
|
72
|
+
style: normalizeStyle({ color: $options.resolvedTextColor })
|
|
73
|
+
}, toDisplayString($options.resolvedSubtitle), 5 /* TEXT, STYLE */))
|
|
74
|
+
: createCommentVNode("v-if", true)
|
|
75
|
+
])
|
|
66
76
|
], 8 /* PROPS */, _hoisted_1)
|
|
67
77
|
], 6 /* CLASS, STYLE */))
|
|
68
78
|
: createCommentVNode("v-if", true)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTileWidget.vue3.js","sources":["../../../../src/components/CnTileWidget/CnTileWidget.vue?vue&type=template&id=28963e83&lang.js"],"sourcesContent":["<!--\n CnTileWidget — Quick-access tile with icon and link.\n\n A simple, colorful tile for app shortcuts or external links.\n Supports SVG paths, CSS icon classes, image URLs, and emoji icons.\n-->\n<template>\n\t<div\n\t\tv-if=\"tile\"\n\t\tclass=\"cn-tile-widget\"\n\t\t:class=\"{ 'cn-tile-widget--tinted': isTinted }\"\n\t\t:style=\"tileStyles\">\n\t\t<a\n\t\t\t:href=\"tileUrl\"\n\t\t\tclass=\"cn-tile-widget__link\"\n\t\t\t:target=\"tile.linkType === 'url' ? '_blank' : '_self'\"\n\t\t\trel=\"noopener noreferrer\"\n\t\t\t@click=\"onLinkClick\">\n\t\t\t<!-- SVG icon -->\n\t\t\t<svg\n\t\t\t\tv-if=\"tile.iconType === 'svg'\"\n\t\t\t\tclass=\"cn-tile-widget__icon cn-tile-widget__icon--svg\"\n\t\t\t\t:style=\"{ fill: resolvedTextColor }\"\n\t\t\t\tviewBox=\"0 0 24 24\">\n\t\t\t\t<path :d=\"tile.icon\" />\n\t\t\t</svg>\n\t\t\t<!-- Other icon types -->\n\t\t\t<div v-else class=\"cn-tile-widget__icon\">\n\t\t\t\t<!-- A registry key (`Trophy`, `ViewDashboard`, …) comes FIRST:\n\t\t\t\t with no icon catalogue wired, that is what CnIconBrowser\n\t\t\t\t emits, and the tile form files it under `class` because it\n\t\t\t\t is neither a URL nor an SVG path. Rendered as a CSS class it\n\t\t\t\t is a rule nobody wrote, so the tile showed no icon at all.\n\t\t\t\t `hasRegistryIcon` excludes `icon-*`, which really are\n\t\t\t\t Nextcloud CSS-class icons. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"registryIcon\"\n\t\t\t\t\tv-if=\"registryIcon\"\n\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t:fillColor=\"resolvedTextColor\" />\n\t\t\t\t<span v-else-if=\"tile.iconType === 'class'\" class=\"icon\" :class=\"[tile.icon]\" />\n\t\t\t\t<img v-else-if=\"tile.iconType === 'url'\" :src=\"tile.icon\" alt=\"\">\n\t\t\t\t<span v-else-if=\"tile.iconType === 'emoji'\" class=\"cn-tile-widget__emoji\">{{ tile.icon }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-tile-widget__title\" :style=\"{ color: resolvedTextColor }\">\n\t\t\t\t{{ resolvedTitle }}\n\t\t\t</div>\n\t\t</a>\n\t</div>\n</template>\n\n<script>\nimport { generateUrl } from '@nextcloud/router'\nimport { getIconComponent, hasRegistryIcon } from '../CnWidgetGrid/widgetIcons.js'\n\n/**\n * CnTileWidget — Quick-access tile with icon and link.\n *\n * ```vue\n * <CnTileWidget :tile=\"{\n * title: 'Files',\n * icon: 'M12,2C6.48,...',\n * iconType: 'svg',\n * backgroundColor: '#0082c9',\n * textColor: '#ffffff',\n * linkType: 'app',\n * linkValue: 'files',\n * }\" />\n * ```\n */\nexport default {\n\tname: 'CnTileWidget',\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored tile title is run through it. Defaults to an\n\t\t * identity function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Tile configuration object. `linkType` decides how `linkValue` is\n\t\t * followed: `app` builds a full-page `/apps/…` URL, `url` opens an\n\t\t * external link, and `route` pushes `linkValue` through the host\n\t\t * app's vue-router so SPA state (e.g. an in-memory vault key)\n\t\t * survives the navigation.\n\t\t *\n\t\t * @type {{ title: string, icon: string, iconType: 'svg'|'class'|'url'|'emoji', backgroundColor: string, textColor: string, linkType: 'app'|'url'|'route', linkValue: string }}\n\t\t */\n\t\ttile: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The tile caption run through the host translate function.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\treturn this.tile.title ? fn(this.tile.title) : this.tile.title\n\t\t},\n\n\t\t/**\n\t\t * The anchor href. `route` tiles resolve through the host router so\n\t\t * middle-click / ctrl-click open the correct full URL (including the\n\t\t * router base); plain clicks are intercepted by `onLinkClick` instead.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttileUrl() {\n\t\t\tif (this.tile.linkType === 'route') {\n\t\t\t\tif (this.$router) {\n\t\t\t\t\treturn this.$router.resolve(this.tile.linkValue || '/').href\n\t\t\t\t}\n\t\t\t\t// No router on the host page — degrade to a plain link.\n\t\t\t\treturn this.tile.linkValue || '#'\n\t\t\t}\n\t\t\tif (this.tile.linkType === 'app') {\n\t\t\t\treturn generateUrl('/apps/' + this.tile.linkValue)\n\t\t\t}\n\t\t\treturn this.tile.linkValue || '#'\n\t\t},\n\n\t\t/**\n\t\t * The icon component for a registry-key icon value, or null when the\n\t\t * value is something the other branches render.\n\t\t *\n\t\t * @return {object|null} the icon component.\n\t\t */\n\t\tregistryIcon() {\n\t\t\treturn hasRegistryIcon(this.tile.icon)\n\t\t\t\t? getIconComponent(this.tile.icon)\n\t\t\t\t: null\n\t\t},\n\n\t\t/**\n\t\t * Whether the author actually chose a background colour.\n\t\t *\n\t\t * @return {boolean} true when the tile is tinted.\n\t\t */\n\t\tisTinted() {\n\t\t\treturn Boolean(this.tile.backgroundColor)\n\t\t},\n\n\t\t/**\n\t\t * The text/icon colour. White on a tinted tile (any author-chosen\n\t\t * colour is assumed dark enough, as it always was); the ordinary text\n\t\t * colour otherwise, since an untinted tile sits on the page background.\n\t\t *\n\t\t * @return {string} a CSS colour.\n\t\t */\n\t\tresolvedTextColor() {\n\t\t\tif (this.tile.textColor) {\n\t\t\t\treturn this.tile.textColor\n\t\t\t}\n\t\t\treturn this.isTinted ? '#ffffff' : 'var(--color-main-text)'\n\t\t},\n\n\t\t/**\n\t\t * Tile colours. An untinted tile paints NO background: a tile whose\n\t\t * author never picked a colour should look like the surface it sits\n\t\t * on, not invent one — it used to impose a hardcoded Nextcloud blue.\n\t\t *\n\t\t * @return {object} the CSS custom properties.\n\t\t */\n\t\ttileStyles() {\n\t\t\treturn {\n\t\t\t\t'--cn-tile-bg': this.tile.backgroundColor || 'transparent',\n\t\t\t\t'--cn-tile-text': this.resolvedTextColor,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Push `route` tiles through the host router instead of letting the\n\t\t * anchor trigger a full page load — a full load tears down the SPA\n\t\t * (and with it any in-memory state such as an unlocked vault).\n\t\t * Mirrors RouterLink's guard: modified clicks (new tab/window) and\n\t\t * non-left buttons fall through to the resolved href.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonLinkClick(event) {\n\t\t\tif (this.tile.linkType !== 'route' || !this.$router) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.defaultPrevented\n\t\t\t\t|| (event.button !== undefined && event.button !== 0)\n\t\t\t\t|| event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\t// vue-router rejects the push promise on a blocked or duplicate\n\t\t\t// navigation; swallow it so it never surfaces as an unhandled rejection.\n\t\t\tthis.$router.push(this.tile.linkValue || '/').catch(() => {})\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tile-widget {\n\theight: 100%;\n\twidth: 100%;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\toverflow: hidden;\n\tbackground-color: var(--cn-tile-bg);\n}\n\n.cn-tile-widget__link {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\theight: 100%;\n\twidth: 100%;\n\ttext-decoration: none;\n\tpadding: 20px;\n\tgap: 12px;\n\ttransition: transform 0.2s ease, opacity 0.2s ease;\n\tbackground-color: var(--cn-tile-bg);\n\tcolor: var(--cn-tile-text);\n}\n\n.cn-tile-widget__link:hover {\n\ttransform: scale(1.02);\n\topacity: 0.95;\n}\n\n.cn-tile-widget__icon {\n\tfont-size: 48px;\n\twidth: 48px;\n\theight: 48px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n}\n\n.cn-tile-widget__icon--svg {\n\twidth: 48px;\n\theight: 48px;\n}\n\n.cn-tile-widget__icon span.icon {\n\tdisplay: inline-block;\n\twidth: 48px;\n\theight: 48px;\n\tbackground-size: 48px;\n}\n\n/* A CSS-class icon is a dark background-image, so it is inverted to white to\n read against the tint. An untinted tile sits on the page background, where\n white would be invisible in a light theme, so it keeps its own colour. */\n.cn-tile-widget--tinted .cn-tile-widget__icon span.icon {\n\tfilter: brightness(0) invert(1);\n}\n\n.cn-tile-widget__icon img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: contain;\n}\n\n.cn-tile-widget__emoji {\n\tfont-size: 48px;\n}\n\n.cn-tile-widget__title {\n\tfont-size: 16px;\n\tfont-weight: 700;\n\ttext-align: center;\n\toverflow-wrap: anywhere;\n\tline-height: 1.3;\n\tcolor: var(--cn-tile-text);\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_openBlock","_createBlock","_resolveDynamicComponent","_toDisplayString"],"mappings":";;;;;;AAiCe,EAAA,KAAK,EAAC,sBAAsB;;;;;AAerC,EAAA,KAAA,EAAA,uBAAA;;;;UAlCE,MAAI,CAAA,IAAA;oBADTA;;aAEI,EAAAC,cAAA,CAAA,CAAC,gBAAgB,EAAA,EAAA,wBAAA,EACG,QAAA,CAAA,QAAA,EAAA,CAAA,CAAA;AACxB,QAAA,KAAK,iBAAE,QAAU,CAAA,UAAA,CAAA;;;AAEhB,UAAA,IAAE,EAAA,QAAA,CAAA,OAAA;eACE,EAAC,sBAAsB;UAC3B,MAAM,EAAE,MAAI,CAAA,IAAA,CAAC,QAAQ,KAAA,KAAA,GAAA,QAAA,GAAA,OAAA;AACrB,UAAA,GAAE,EAAC,qBAAqB;AACxB,UAAA,OAAK,0CAAE,QAAW,CAAA,WAAA,IAAA,QAAA,CAAA,WAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;;AAGZ,UAAA,CAAA,MAAA,CAAA,IAAI,CAAC,QAAQ,KAAA,KAAA;4BADpBD,kBAMM,CAAA,KAAA,EAAA;;AAJL,gBAAA,KAAK,EAAC,gDAAiC;AACtC,gBAAA,KAAM,yBAAS,QAAiB,CAAA,iBAAA,EAAA,CAAA;uBAC1B,EAAC,WAAW;;kCACI,CAAA,MAAA,EAAA;kBAAhB,CAAC,EAAE,MAAI,CAAA,IAAA;;;;iBAaV,QAAA,CAAA,YAAA;AAFJ,qBAAAE,SAAA,EAAA,EAAAC,WAAA,CAIkBC,wBAHZ,QAAY,CAAA,YAAA,CAAA,EAAA;;AAEhB,sBAAA,IAAI,EAAE,EAAE;AACP,sBAAA,SAAQ,EAAE,QAAK,CAAA,iBAAA;;AACd,oBAAA,CAAA,MAAA,CAAA,IAAA,CAAA,QAAA,KAAA,OAAA;oCAAJJ,kBAAI,CAAA,MAAA,EAAA;;AAAA,wBAAA,KAAA,EAAAC,cAAA,CAAA,CAAA,MAAA,EAAA,CAAA,MAAA,CAAA,IAAA,CAAA,IAAA,CAAA,CAAA,CAAA;;AACH,sBAAA,CAAA,MAAA,CAAA,IAAA,CAAA,QAAA,KAAA,KAAA;sCAADD,kBAAC,CAAA,KAAA,EAAA;;0BAAA,GAAA,EAAA,MAAA,CAAA,IAAA,CAAA,IAAA;AAAA,0BAAA,GAAA,EAAA,EAAA;;AACC,wBAAA,CAAA,MAAA,CAAA,IAAA,CAAA,QAAA,KAAA,OAAA;AAAF,2BAAAE,SAAA,EAAA,EAAAF,kBAAA,CAAE,MAAF,EAAA,UAAE,EAAAK,eAAA,CAAA,MAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnTileWidget.vue3.js","sources":["../../../../src/components/CnTileWidget/CnTileWidget.vue?vue&type=template&id=a62ef4d8&lang.js"],"sourcesContent":["<!--\n CnTileWidget — Quick-access tile with icon and link.\n\n A simple, colorful tile for app shortcuts or external links.\n Supports SVG paths, CSS icon classes, image URLs, and emoji icons.\n-->\n<template>\n\t<div\n\t\tv-if=\"tile\"\n\t\tclass=\"cn-tile-widget\"\n\t\t:class=\"{ 'cn-tile-widget--tinted': isTinted }\"\n\t\t:style=\"tileStyles\">\n\t\t<a\n\t\t\t:href=\"tileUrl\"\n\t\t\tclass=\"cn-tile-widget__link\"\n\t\t\t:target=\"tile.linkType === 'url' ? '_blank' : '_self'\"\n\t\t\trel=\"noopener noreferrer\"\n\t\t\t@click=\"onLinkClick\">\n\t\t\t<!-- SVG icon -->\n\t\t\t<svg\n\t\t\t\tv-if=\"tile.iconType === 'svg'\"\n\t\t\t\tclass=\"cn-tile-widget__icon cn-tile-widget__icon--svg\"\n\t\t\t\t:style=\"{ fill: resolvedTextColor }\"\n\t\t\t\tviewBox=\"0 0 24 24\">\n\t\t\t\t<path :d=\"tile.icon\" />\n\t\t\t</svg>\n\t\t\t<!-- Other icon types -->\n\t\t\t<div v-else class=\"cn-tile-widget__icon\">\n\t\t\t\t<!-- A registry key (`Trophy`, `ViewDashboard`, …) comes FIRST:\n\t\t\t\t with no icon catalogue wired, that is what CnIconBrowser\n\t\t\t\t emits, and the tile form files it under `class` because it\n\t\t\t\t is neither a URL nor an SVG path. Rendered as a CSS class it\n\t\t\t\t is a rule nobody wrote, so the tile showed no icon at all.\n\t\t\t\t `hasRegistryIcon` excludes `icon-*`, which really are\n\t\t\t\t Nextcloud CSS-class icons. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"registryIcon\"\n\t\t\t\t\tv-if=\"registryIcon\"\n\t\t\t\t\t:size=\"32\"\n\t\t\t\t\t:fillColor=\"resolvedTextColor\" />\n\t\t\t\t<span v-else-if=\"tile.iconType === 'class'\" class=\"icon\" :class=\"[tile.icon]\" />\n\t\t\t\t<img v-else-if=\"tile.iconType === 'url'\" :src=\"tile.icon\" alt=\"\">\n\t\t\t\t<span v-else-if=\"tile.iconType === 'emoji'\" class=\"cn-tile-widget__emoji\">{{ tile.icon }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-tile-widget__text\">\n\t\t\t\t<div class=\"cn-tile-widget__title\" :style=\"{ color: resolvedTextColor }\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"resolvedSubtitle\" class=\"cn-tile-widget__subtitle\" :style=\"{ color: resolvedTextColor }\">\n\t\t\t\t\t{{ resolvedSubtitle }}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</a>\n\t</div>\n</template>\n\n<script>\nimport { generateUrl } from '@nextcloud/router'\nimport { getIconComponent, hasRegistryIcon } from '../CnWidgetGrid/widgetIcons.js'\n\n/**\n * CnTileWidget — Quick-access tile with icon and link.\n *\n * ```vue\n * <CnTileWidget :tile=\"{\n * title: 'Files',\n * icon: 'M12,2C6.48,...',\n * iconType: 'svg',\n * backgroundColor: '#0082c9',\n * textColor: '#ffffff',\n * linkType: 'app',\n * linkValue: 'files',\n * }\" />\n * ```\n */\nexport default {\n\tname: 'CnTileWidget',\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored tile title is run through it. Defaults to an\n\t\t * identity function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * Tile configuration object. `linkType` decides how `linkValue` is\n\t\t * followed: `app` builds a full-page `/apps/…` URL, `url` opens an\n\t\t * external link, and `route` pushes `linkValue` through the host\n\t\t * app's vue-router so SPA state (e.g. an in-memory vault key)\n\t\t * survives the navigation.\n\t\t *\n\t\t * @type {{ title: string, icon: string, iconType: 'svg'|'class'|'url'|'emoji', backgroundColor: string, textColor: string, linkType: 'app'|'url'|'route', linkValue: string }}\n\t\t */\n\t\ttile: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The tile caption run through the host translate function.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\treturn this.tile.title ? fn(this.tile.title) : this.tile.title\n\t\t},\n\n\t\t/**\n\t\t * The optional line under the title (`tile.subtitle`), translated like\n\t\t * the title. Empty when the tile names none.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedSubtitle() {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k\n\t\t\treturn this.tile.subtitle ? fn(this.tile.subtitle) : ''\n\t\t},\n\n\t\t/**\n\t\t * The anchor href. `route` tiles resolve through the host router so\n\t\t * middle-click / ctrl-click open the correct full URL (including the\n\t\t * router base); plain clicks are intercepted by `onLinkClick` instead.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttileUrl() {\n\t\t\tif (this.tile.linkType === 'route') {\n\t\t\t\tif (this.$router) {\n\t\t\t\t\treturn this.$router.resolve(this.tile.linkValue || '/').href\n\t\t\t\t}\n\t\t\t\t// No router on the host page — degrade to a plain link.\n\t\t\t\treturn this.tile.linkValue || '#'\n\t\t\t}\n\t\t\tif (this.tile.linkType === 'app') {\n\t\t\t\treturn generateUrl('/apps/' + this.tile.linkValue)\n\t\t\t}\n\t\t\treturn this.tile.linkValue || '#'\n\t\t},\n\n\t\t/**\n\t\t * The icon component for a registry-key icon value, or null when the\n\t\t * value is something the other branches render.\n\t\t *\n\t\t * @return {object|null} the icon component.\n\t\t */\n\t\tregistryIcon() {\n\t\t\treturn hasRegistryIcon(this.tile.icon)\n\t\t\t\t? getIconComponent(this.tile.icon)\n\t\t\t\t: null\n\t\t},\n\n\t\t/**\n\t\t * Whether the author actually chose a background colour.\n\t\t *\n\t\t * @return {boolean} true when the tile is tinted.\n\t\t */\n\t\tisTinted() {\n\t\t\treturn Boolean(this.tile.backgroundColor)\n\t\t},\n\n\t\t/**\n\t\t * The text/icon colour. White on a tinted tile (any author-chosen\n\t\t * colour is assumed dark enough, as it always was); the ordinary text\n\t\t * colour otherwise, since an untinted tile sits on the page background.\n\t\t *\n\t\t * @return {string} a CSS colour.\n\t\t */\n\t\tresolvedTextColor() {\n\t\t\tif (this.tile.textColor) {\n\t\t\t\treturn this.tile.textColor\n\t\t\t}\n\t\t\treturn this.isTinted ? '#ffffff' : 'var(--color-main-text)'\n\t\t},\n\n\t\t/**\n\t\t * Tile colours. An untinted tile paints NO background: a tile whose\n\t\t * author never picked a colour should look like the surface it sits\n\t\t * on, not invent one — it used to impose a hardcoded Nextcloud blue.\n\t\t *\n\t\t * @return {object} the CSS custom properties.\n\t\t */\n\t\ttileStyles() {\n\t\t\treturn {\n\t\t\t\t'--cn-tile-bg': this.tile.backgroundColor || 'transparent',\n\t\t\t\t'--cn-tile-text': this.resolvedTextColor,\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Push `route` tiles through the host router instead of letting the\n\t\t * anchor trigger a full page load — a full load tears down the SPA\n\t\t * (and with it any in-memory state such as an unlocked vault).\n\t\t * Mirrors RouterLink's guard: modified clicks (new tab/window) and\n\t\t * non-left buttons fall through to the resolved href.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonLinkClick(event) {\n\t\t\tif (this.tile.linkType !== 'route' || !this.$router) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (event.defaultPrevented\n\t\t\t\t|| (event.button !== undefined && event.button !== 0)\n\t\t\t\t|| event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\t// vue-router rejects the push promise on a blocked or duplicate\n\t\t\t// navigation; swallow it so it never surfaces as an unhandled rejection.\n\t\t\tthis.$router.push(this.tile.linkValue || '/').catch(() => {})\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-tile-widget {\n\theight: 100%;\n\twidth: 100%;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\toverflow: hidden;\n\tbackground-color: var(--cn-tile-bg);\n}\n\n.cn-tile-widget__link {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\theight: 100%;\n\twidth: 100%;\n\ttext-decoration: none;\n\tpadding: 20px;\n\tgap: 12px;\n\ttransition: transform 0.2s ease, opacity 0.2s ease;\n\tbackground-color: var(--cn-tile-bg);\n\tcolor: var(--cn-tile-text);\n}\n\n.cn-tile-widget__link:hover {\n\ttransform: scale(1.02);\n\topacity: 0.95;\n}\n\n.cn-tile-widget__icon {\n\tfont-size: 48px;\n\twidth: 48px;\n\theight: 48px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n}\n\n.cn-tile-widget__icon--svg {\n\twidth: 48px;\n\theight: 48px;\n}\n\n.cn-tile-widget__icon span.icon {\n\tdisplay: inline-block;\n\twidth: 48px;\n\theight: 48px;\n\tbackground-size: 48px;\n}\n\n/* A CSS-class icon is a dark background-image, so it is inverted to white to\n read against the tint. An untinted tile sits on the page background, where\n white would be invisible in a light theme, so it keeps its own colour. */\n.cn-tile-widget--tinted .cn-tile-widget__icon span.icon {\n\tfilter: brightness(0) invert(1);\n}\n\n.cn-tile-widget__icon img {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: contain;\n}\n\n.cn-tile-widget__emoji {\n\tfont-size: 48px;\n}\n\n/* The text block takes no box of its own outside the board look, so the\n title stays a direct flex item of the link. */\n.cn-tile-widget__text {\n\tdisplay: contents;\n}\n\n.cn-tile-widget__subtitle {\n\tfont-size: 13px;\n\ttext-align: center;\n\topacity: 0.85;\n}\n\n.cn-tile-widget__title {\n\tfont-size: 16px;\n\tfont-weight: 700;\n\ttext-align: center;\n\toverflow-wrap: anywhere;\n\tline-height: 1.3;\n\tcolor: var(--cn-tile-text);\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_openBlock","_createBlock","_resolveDynamicComponent","_toDisplayString","_createElementVNode"],"mappings":";;;;;;AAiCe,EAAA,KAAK,EAAC,sBAAsB;;;;;AAeK,EAAA,KAAK,EAAC,uBAAuB;;AAErE,MAAA,UAAA,GAAA,EAAA,KAAC,EAAA,sBAAA,GAAA;;;UApCD,MAAI,CAAA,IAAA;oBADTA;;aAEI,EAAAC,cAAA,CAAA,CAAC,gBAAgB,EAAA,EAAA,wBAAA,EACG,QAAA,CAAA,QAAA,EAAA,CAAA,CAAA;AACxB,QAAA,KAAK,iBAAE,QAAU,CAAA,UAAA,CAAA;;;AAEhB,UAAA,IAAE,EAAA,QAAA,CAAA,OAAA;eACE,EAAC,sBAAsB;UAC3B,MAAM,EAAE,MAAI,CAAA,IAAA,CAAC,QAAQ,KAAA,KAAA,GAAA,QAAA,GAAA,OAAA;AACrB,UAAA,GAAE,EAAC,qBAAqB;AACxB,UAAA,OAAK,0CAAE,QAAW,CAAA,WAAA,IAAA,QAAA,CAAA,WAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;;AAGZ,UAAA,CAAA,MAAA,CAAA,IAAI,CAAC,QAAQ,KAAA,KAAA;4BADpBD,kBAMM,CAAA,KAAA,EAAA;;AAJL,gBAAA,KAAK,EAAC,gDAAiC;AACtC,gBAAA,KAAM,yBAAS,QAAiB,CAAA,iBAAA,EAAA,CAAA;uBAC1B,EAAC,WAAW;;kCACI,CAAA,MAAA,EAAA;kBAAhB,CAAC,EAAE,MAAI,CAAA,IAAA;;;+CAmBT,iBAAA,EAAA;iBAND,QAAA,CAAA,YAAA;AAFJ,qBAAAE,SAAA,EAAA,EAAAC,WAAA,CAIiCC,wBAH3B,QAAY,CAAA,YAAA,CAAA,EAAA;;AAEhB,sBAAA,IAAI,EAAE,EAAE;AACR,sBAAA,SAAS,EAAE,QAAiB,CAAA,iBAAA;;gCACX,CAAA,QAAA,KAAA,OAAA;oCAAlBJ,kBAAkB,CAAA,MAAA,EAAA;;AAAA,wBAAA,KAAA,EAAAC,cAAA,CAAA,CAAA,MAAA,EAAA,CAAA,MAAA,CAAA,IAAA,CAAA,IAAA,CAAA,CAAA,CAAA;;AACd,sBAAA,CAAA,MAAA,CAAA,IAAA,CAAA,QAAA,KAAA,KAAA;sCAALD,kBAAK,CAAA,KAAA,EAAA;;0BAAA,GAAA,EAAA,MAAA,CAAA,IAAA,CAAA,IAAA;AAAA,0BAAA,GAAA,EAAA,EAAA;;AACY,wBAAA,CAAA,MAAA,CAAA,IAAI,CAAC,QAAQ,KAAA,OAAA;AAA9B,2BAAAE,SAAA,EAAA,EAAAF,kBAAA,CAA+F,MAA/F,EAAA,UAA+F,EAAlBK,eAAA,CAAA,MAAA,CAAA,IAAI,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;;;;YAGtFC,kBAEE,CAAA,KAAA,EAAA;AAFE,cAAA,KAAA,EAAA,uBAAA;AAAA,cAAA,KAAA,0BAAA,QAAA,CAAA,iBAAA,EAAA,CAAA;+BACH,QAAA,CAAA,aAAA,CAAA,EAAA,CAAA,mBAAA;;;;;;;;;;;;;;;;"}
|